mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix Windows build process and add Windows asset extraction scripts
- Add missing MSYS2 packages to CI: vulkan-loader, vulkan-headers, shaderc, stormlib (both x86-64 and arm64), unicorn (arm64) - Make vulkan-1.dll copy conditional via find_file (fixes MSYS2 builds) - Use find_library for wininet/bz2 in asset_extract (graceful fallback) - Add extract_assets.ps1 and extract_assets.bat for Windows users - Expand BUILD_INSTRUCTIONS.md with MSYS2, vcpkg, and macOS sections - Update README.md to reference Windows scripts and platforms
This commit is contained in:
parent
06979e5c5c
commit
eb549a9b7a
7 changed files with 269 additions and 17 deletions
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
|
@ -182,6 +182,11 @@ jobs:
|
||||||
mingw-w64-clang-aarch64-openssl
|
mingw-w64-clang-aarch64-openssl
|
||||||
mingw-w64-clang-aarch64-zlib
|
mingw-w64-clang-aarch64-zlib
|
||||||
mingw-w64-clang-aarch64-ffmpeg
|
mingw-w64-clang-aarch64-ffmpeg
|
||||||
|
mingw-w64-clang-aarch64-unicorn
|
||||||
|
mingw-w64-clang-aarch64-vulkan-loader
|
||||||
|
mingw-w64-clang-aarch64-vulkan-headers
|
||||||
|
mingw-w64-clang-aarch64-shaderc
|
||||||
|
mingw-w64-clang-aarch64-stormlib
|
||||||
git
|
git
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
|
|
@ -237,6 +242,10 @@ jobs:
|
||||||
mingw-w64-x86_64-zlib
|
mingw-w64-x86_64-zlib
|
||||||
mingw-w64-x86_64-ffmpeg
|
mingw-w64-x86_64-ffmpeg
|
||||||
mingw-w64-x86_64-unicorn
|
mingw-w64-x86_64-unicorn
|
||||||
|
mingw-w64-x86_64-vulkan-loader
|
||||||
|
mingw-w64-x86_64-vulkan-headers
|
||||||
|
mingw-w64-x86_64-shaderc
|
||||||
|
mingw-w64-x86_64-stormlib
|
||||||
mingw-w64-x86_64-nsis
|
mingw-w64-x86_64-nsis
|
||||||
git
|
git
|
||||||
|
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -64,7 +64,7 @@ cache/
|
||||||
saves/
|
saves/
|
||||||
wowee_[0-9][0-9][0-9][0-9]
|
wowee_[0-9][0-9][0-9][0-9]
|
||||||
|
|
||||||
# Extracted assets (run ./extract_assets.sh to generate)
|
# Extracted assets (run ./extract_assets.sh or .\extract_assets.ps1 to generate)
|
||||||
Data/db/
|
Data/db/
|
||||||
Data/character/
|
Data/character/
|
||||||
Data/creature/
|
Data/creature/
|
||||||
|
|
|
||||||
|
|
@ -49,15 +49,104 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build build -j"$(nproc)"
|
cmake --build build -j"$(nproc)"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Asset Extraction (Linux)
|
||||||
|
|
||||||
|
After building, extract assets from your WoW client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./extract_assets.sh /path/to/WoW/Data wotlk
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports `classic`, `tbc`, `wotlk` targets (auto-detected if omitted).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🍎 macOS
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn stormlib
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clone & Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --recurse-submodules https://github.com/Kelsidavis/WoWee.git
|
||||||
|
cd WoWee
|
||||||
|
|
||||||
|
BREW=$(brew --prefix)
|
||||||
|
export PKG_CONFIG_PATH="$BREW/lib/pkgconfig:$(brew --prefix ffmpeg)/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig"
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_PREFIX_PATH="$BREW" \
|
||||||
|
-DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)"
|
||||||
|
cmake --build build -j"$(sysctl -n hw.logicalcpu)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Extraction (macOS)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./extract_assets.sh /path/to/WoW/Data wotlk
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🪟 Windows (MSYS2 — Recommended)
|
||||||
|
|
||||||
|
MSYS2 provides all dependencies as pre-built packages.
|
||||||
|
|
||||||
|
### Install MSYS2
|
||||||
|
|
||||||
|
Download and install from <https://www.msys2.org/>, then open a **MINGW64** shell.
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pacman -S --needed \
|
||||||
|
mingw-w64-x86_64-cmake \
|
||||||
|
mingw-w64-x86_64-gcc \
|
||||||
|
mingw-w64-x86_64-ninja \
|
||||||
|
mingw-w64-x86_64-pkgconf \
|
||||||
|
mingw-w64-x86_64-SDL2 \
|
||||||
|
mingw-w64-x86_64-glew \
|
||||||
|
mingw-w64-x86_64-glm \
|
||||||
|
mingw-w64-x86_64-openssl \
|
||||||
|
mingw-w64-x86_64-zlib \
|
||||||
|
mingw-w64-x86_64-ffmpeg \
|
||||||
|
mingw-w64-x86_64-unicorn \
|
||||||
|
mingw-w64-x86_64-vulkan-loader \
|
||||||
|
mingw-w64-x86_64-vulkan-headers \
|
||||||
|
mingw-w64-x86_64-shaderc \
|
||||||
|
mingw-w64-x86_64-stormlib \
|
||||||
|
git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clone & Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --recurse-submodules https://github.com/Kelsidavis/WoWee.git
|
||||||
|
cd WoWee
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build --parallel $(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🪟 Windows (Visual Studio 2022)
|
## 🪟 Windows (Visual Studio 2022)
|
||||||
|
|
||||||
|
For users who prefer Visual Studio over MSYS2.
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
- Visual Studio 2022
|
- Visual Studio 2022 with **Desktop development with C++** workload
|
||||||
- Desktop development with C++
|
- CMake tools for Windows (included in VS workload)
|
||||||
- CMake tools for Windows
|
- [LunarG Vulkan SDK](https://vulkan.lunarg.com/) (provides Vulkan headers, loader, and glslc)
|
||||||
|
|
||||||
|
### vcpkg Dependencies
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
vcpkg install sdl2 glew glm openssl zlib ffmpeg stormlib --triplet x64-windows
|
||||||
|
```
|
||||||
|
|
||||||
### Clone
|
### Clone
|
||||||
|
|
||||||
|
|
@ -68,16 +157,29 @@ cd WoWee
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
Open the folder in Visual Studio (it will detect CMake automatically)
|
Open the folder in Visual Studio (it will detect CMake automatically)
|
||||||
or build from Developer PowerShell:
|
or build from Developer PowerShell:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="[vcpkg root]/scripts/buildsystems/vcpkg.cmake"
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🪟 Asset Extraction (Windows)
|
||||||
|
|
||||||
|
After building (via either MSYS2 or Visual Studio), extract assets from your WoW client:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\extract_assets.ps1 "C:\Games\WoW-3.3.5a\Data"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or double-click `extract_assets.bat` and provide the path when prompted.
|
||||||
|
You can also specify an expansion: `.\extract_assets.ps1 "C:\Games\WoW\Data" wotlk`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ⚠️ Notes
|
## ⚠️ Notes
|
||||||
|
|
||||||
- Case matters on Linux (`WoWee` not `wowee`).
|
- Case matters on Linux (`WoWee` not `wowee`).
|
||||||
|
|
|
||||||
|
|
@ -592,12 +592,18 @@ add_custom_command(TARGET wowee POST_BUILD
|
||||||
# which does NOT include System32. Copy vulkan-1.dll into the output directory so
|
# which does NOT include System32. Copy vulkan-1.dll into the output directory so
|
||||||
# SDL_Vulkan_LoadLibrary can locate it without needing a full system PATH search.
|
# SDL_Vulkan_LoadLibrary can locate it without needing a full system PATH search.
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_custom_command(TARGET wowee POST_BUILD
|
find_file(VULKAN_DLL vulkan-1.dll
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
PATHS "$ENV{SystemRoot}/System32" "$ENV{VULKAN_SDK}/Bin"
|
||||||
"$ENV{SystemRoot}/System32/vulkan-1.dll"
|
NO_DEFAULT_PATH)
|
||||||
"$<TARGET_FILE_DIR:wowee>/vulkan-1.dll"
|
if(VULKAN_DLL)
|
||||||
COMMENT "Copying vulkan-1.dll to output directory"
|
add_custom_command(TARGET wowee POST_BUILD
|
||||||
)
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${VULKAN_DLL}" "$<TARGET_FILE_DIR:wowee>/vulkan-1.dll"
|
||||||
|
COMMENT "Copying vulkan-1.dll to output directory"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(STATUS " vulkan-1.dll not found — skipping copy (MSYS2 provides it via PATH)")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Install targets
|
# Install targets
|
||||||
|
|
@ -659,7 +665,14 @@ if(STORMLIB_LIBRARY AND STORMLIB_INCLUDE_DIR)
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
)
|
)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(asset_extract PRIVATE wininet bz2)
|
find_library(WININET_LIB wininet)
|
||||||
|
find_library(BZ2_LIB bz2)
|
||||||
|
if(WININET_LIB)
|
||||||
|
target_link_libraries(asset_extract PRIVATE ${WININET_LIB})
|
||||||
|
endif()
|
||||||
|
if(BZ2_LIB)
|
||||||
|
target_link_libraries(asset_extract PRIVATE ${BZ2_LIB})
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
set_target_properties(asset_extract PROPERTIES
|
set_target_properties(asset_extract PROPERTIES
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -36,7 +36,7 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**.
|
||||||
### Asset Pipeline
|
### Asset Pipeline
|
||||||
- Extracted loose-file **`Data/`** tree indexed by **`manifest.json`** (fast lookup + caching)
|
- Extracted loose-file **`Data/`** tree indexed by **`manifest.json`** (fast lookup + caching)
|
||||||
- Optional **overlay layers** for multi-expansion asset deduplication
|
- Optional **overlay layers** for multi-expansion asset deduplication
|
||||||
- `asset_extract` + `extract_assets.sh` for MPQ extraction (StormLib tooling)
|
- `asset_extract` + `extract_assets.sh` (Linux/macOS) / `extract_assets.ps1` (Windows) for MPQ extraction (StormLib tooling)
|
||||||
- File formats: **BLP** (DXT1/3/5), **ADT**, **M2**, **WMO**, **DBC** (Spell/Item/Faction/etc.)
|
- File formats: **BLP** (DXT1/3/5), **ADT**, **M2**, **WMO**, **DBC** (Spell/Item/Faction/etc.)
|
||||||
|
|
||||||
### Gameplay Systems
|
### Gameplay Systems
|
||||||
|
|
@ -101,8 +101,12 @@ Wowee loads assets via an extracted loose-file tree indexed by `manifest.json` (
|
||||||
#### 1) Extract MPQs into `./Data/`
|
#### 1) Extract MPQs into `./Data/`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# WotLK 3.3.5a example
|
# Linux / macOS
|
||||||
./extract_assets.sh /path/to/WoW/Data wotlk
|
./extract_assets.sh /path/to/WoW/Data wotlk
|
||||||
|
|
||||||
|
# Windows (PowerShell)
|
||||||
|
.\extract_assets.ps1 "C:\Games\WoW-3.3.5a\Data" wotlk
|
||||||
|
# Or double-click extract_assets.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -117,7 +121,7 @@ Data/
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `StormLib` is required to build/run the extractor (`asset_extract`), but the main client does not require StormLib at runtime.
|
- `StormLib` is required to build/run the extractor (`asset_extract`), but the main client does not require StormLib at runtime.
|
||||||
- `extract_assets.sh` supports `classic`, `tbc`, `wotlk` targets.
|
- `extract_assets.sh` / `extract_assets.ps1` support `classic`, `tbc`, `wotlk` targets.
|
||||||
|
|
||||||
#### 2) Point wowee at the extracted data
|
#### 2) Point wowee at the extracted data
|
||||||
|
|
||||||
|
|
@ -206,7 +210,7 @@ make -j$(nproc)
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
- **Platform**: Linux (primary), C++20, CMake 3.15+
|
- **Platform**: Linux (primary), Windows (MSYS2/MSVC), macOS — C++20, CMake 3.15+
|
||||||
- **Dependencies**: SDL2, Vulkan, GLM, OpenSSL, ImGui, FFmpeg, Unicorn Engine (StormLib for asset extraction tooling)
|
- **Dependencies**: SDL2, Vulkan, GLM, OpenSSL, ImGui, FFmpeg, Unicorn Engine (StormLib for asset extraction tooling)
|
||||||
- **Architecture**: Modular design with clear separation (core, rendering, networking, game logic, asset pipeline, UI, audio)
|
- **Architecture**: Modular design with clear separation (core, rendering, networking, game logic, asset pipeline, UI, audio)
|
||||||
- **Networking**: Non-blocking TCP, SRP6a authentication, RC4 encryption, WoW 3.3.5a protocol
|
- **Networking**: Non-blocking TCP, SRP6a authentication, RC4 encryption, WoW 3.3.5a protocol
|
||||||
|
|
|
||||||
4
extract_assets.bat
Normal file
4
extract_assets.bat
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
@echo off
|
||||||
|
REM Convenience wrapper — launches the PowerShell asset extraction script.
|
||||||
|
REM Usage: extract_assets.bat C:\Games\WoW\Data [expansion]
|
||||||
|
powershell -ExecutionPolicy Bypass -File "%~dp0extract_assets.ps1" %*
|
||||||
120
extract_assets.ps1
Normal file
120
extract_assets.ps1
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Extracts WoW MPQ archives for use with wowee (Windows equivalent of extract_assets.sh).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Builds the asset_extract tool (if needed) and runs it against a WoW Data directory.
|
||||||
|
|
||||||
|
.PARAMETER MpqDir
|
||||||
|
Path to the WoW client's Data directory containing .MPQ files.
|
||||||
|
|
||||||
|
.PARAMETER Expansion
|
||||||
|
Expansion hint: classic, turtle, tbc, wotlk. Auto-detected if omitted.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\extract_assets.ps1 "C:\Games\WoW-3.3.5a\Data"
|
||||||
|
.\extract_assets.ps1 "C:\Games\WoW-1.12\Data" classic
|
||||||
|
.\extract_assets.ps1 "D:\TurtleWoW\Data" turtle
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true, Position=0)]
|
||||||
|
[string]$MpqDir,
|
||||||
|
|
||||||
|
[Parameter(Position=1)]
|
||||||
|
[string]$Expansion = "auto"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$BuildDir = Join-Path $ScriptDir "build"
|
||||||
|
$Binary = Join-Path $BuildDir "bin\asset_extract.exe"
|
||||||
|
$OutputDir = Join-Path $ScriptDir "Data"
|
||||||
|
|
||||||
|
# --- Validate arguments ---
|
||||||
|
if (-not (Test-Path $MpqDir -PathType Container)) {
|
||||||
|
Write-Error "Error: Directory not found: $MpqDir"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for .MPQ files
|
||||||
|
$mpqFiles = Get-ChildItem -Path $MpqDir -Filter "*.MPQ" -ErrorAction SilentlyContinue
|
||||||
|
$mpqFilesLower = Get-ChildItem -Path $MpqDir -Filter "*.mpq" -ErrorAction SilentlyContinue
|
||||||
|
if (-not $mpqFiles -and -not $mpqFilesLower) {
|
||||||
|
Write-Error "Error: No .MPQ files found in: $MpqDir`nMake sure this is the WoW Data/ directory (not the WoW root)."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note about existing CSV DBCs
|
||||||
|
if (Test-Path (Join-Path $OutputDir "expansions")) {
|
||||||
|
$csvPattern = $null
|
||||||
|
if ($Expansion -ne "auto") {
|
||||||
|
$csvPattern = Join-Path $OutputDir "expansions\$Expansion\db\*.csv"
|
||||||
|
} else {
|
||||||
|
$csvPattern = Join-Path $OutputDir "expansions\*\db\*.csv"
|
||||||
|
}
|
||||||
|
if ($csvPattern -and (Get-ChildItem -Path $csvPattern -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "Note: Found CSV DBCs. DBC extraction is optional; visual assets are still required.`n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Build asset_extract if needed ---
|
||||||
|
if (-not (Test-Path $Binary)) {
|
||||||
|
Write-Host "Building asset_extract..."
|
||||||
|
|
||||||
|
# Check for cmake
|
||||||
|
if (-not (Get-Command cmake -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Error "Error: cmake not found. Install CMake and ensure it is on your PATH."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $BuildDir)) {
|
||||||
|
& cmake -S $ScriptDir -B $BuildDir -DCMAKE_BUILD_TYPE=Release
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
}
|
||||||
|
|
||||||
|
$numProcs = $env:NUMBER_OF_PROCESSORS
|
||||||
|
if (-not $numProcs) { $numProcs = 4 }
|
||||||
|
& cmake --build $BuildDir --target asset_extract --parallel $numProcs
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $Binary)) {
|
||||||
|
Write-Error "Error: Failed to build asset_extract"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Run extraction ---
|
||||||
|
Write-Host "Extracting assets from: $MpqDir"
|
||||||
|
Write-Host "Output directory: $OutputDir"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
$extraArgs = @("--mpq-dir", $MpqDir, "--output", $OutputDir)
|
||||||
|
|
||||||
|
if ($Expansion -ne "auto") {
|
||||||
|
$extraArgs += "--expansion"
|
||||||
|
$extraArgs += $Expansion
|
||||||
|
}
|
||||||
|
|
||||||
|
# Skip DBC extraction if CSVs already present
|
||||||
|
$skipDbc = $false
|
||||||
|
if ($Expansion -ne "auto") {
|
||||||
|
$csvPath = Join-Path $OutputDir "expansions\$Expansion\db\*.csv"
|
||||||
|
if (Get-ChildItem -Path $csvPath -ErrorAction SilentlyContinue) { $skipDbc = $true }
|
||||||
|
} else {
|
||||||
|
$csvPath = Join-Path $OutputDir "expansions\*\db\*.csv"
|
||||||
|
if (Get-ChildItem -Path $csvPath -ErrorAction SilentlyContinue) { $skipDbc = $true }
|
||||||
|
}
|
||||||
|
if ($skipDbc) {
|
||||||
|
$extraArgs += "--skip-dbc"
|
||||||
|
}
|
||||||
|
|
||||||
|
& $Binary @extraArgs
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Done! Assets extracted to $OutputDir"
|
||||||
|
Write-Host "You can now run wowee."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue