fix: make client asset installation archive-first (#1739)

Restore archive first modern client asset installation

This commit reworks the modern client asset auto installation flow so the normal path is based on downloading and extracting the GitHub release or source ZIP instead of relying on the manifest flow as the primary installer.

The installer now keeps the final runtime files in the same locations already expected by OTClient. Asset files are installed under data things version, sound files are installed under data sounds version, and configured runtime extras are installed into their expected runtime locations such as bin.

The manifest based path is still kept as a compatibility fallback, but it is no longer the preferred installation path for modern client assets. This restores the archive first behavior and makes the install result match the standard runtime loader layout more directly.

For client versions 1281 and newer, OTClient now checks the existing files under data things version before attempting installation. When auto install is enabled and the required files are missing, the client prompts the user, downloads the configured release or source ZIP, extracts the assets folder into data things version, extracts the sounds folder into data sounds version, and installs configured runtime extras into their target runtime paths.

If Services clientAssets enabled is false, OTClient now skips the asset ensure download flow during login. This avoids recursive login attempts caused by the installer prompt and keeps manual or custom asset folders usable. In that mode, OTClient proceeds with the provided local files and lets the normal runtime loader report missing or invalid assets when appropriate.

The archive extraction support was also expanded. ZIP extraction now works through the vendored minizip fallback when libarchive is not available. Visual Studio project configuration was updated to enable libarchive so Windows builds can handle ZIP and RAR archive extraction. Android builds remain safe because unsupported libarchive linkage is not required there.

Main changes included in this commit

• Restores archive first installation for modern client assets

• Keeps the manifest path only as a compatibility fallback

• Downloads the GitHub release or source ZIP as the normal install path

• Extracts assets into data things version

• Extracts sounds into data sounds version

• Installs runtime extras into their expected runtime locations such as bin

• Keeps final installed files in the same OTClient runtime paths already used by the loader

• Adds ZIP extraction support through the vendored minizip fallback

• Enables libarchive in the Visual Studio project for Windows archive extraction

• Allows Windows builds to handle ZIP and RAR extraction through libarchive

• Preserves Android build safety by not requiring unsupported libarchive linkage

• Skips asset ensure and download prompts when Services clientAssets enabled is false

• Avoids recursive login attempts when asset auto install is disabled

• Keeps manual and custom asset folders usable

• Lets the normal runtime loader report actual missing or invalid files when auto install is disabled

• Documents the archive first flow fallback behavior install paths and troubleshooting notes

Runtime behavior

For modern client versions 1281 and newer, OTClient first checks the existing files under data things version. If the files are already present, login continues without triggering the installer.

If files are missing and auto install is enabled, OTClient prompts the user and installs assets from the configured GitHub release or source archive. The archive folders are mapped into the runtime locations expected by the client.

The assets folder is installed into data things version. The sounds folder is installed into data sounds version. Runtime extras such as bin files are installed into their configured runtime destinations.

If auto install is disabled, the installer is not called during login. The client continues with the manually provided files and the regular loader remains responsible for reporting any actual asset problems.

Verification performed

• luac p modules client assets client assets lua init lua

• luac p modules client entergame entergame lua

• git diff check

• Local Windows build using vc18 otclient sln OpenGL x64 Build

• GitHub Actions fast checks passed on the PR branch

• GitHub Actions Lua syntax checks passed on the PR branch

Overall this commit restores the expected archive based installation flow for modern client assets while preserving compatibility fallback behavior. It keeps installed files aligned with the existing OTClient runtime layout improves archive extraction support across build targets and avoids unwanted installer prompts when automatic asset installation is disabled.
This commit is contained in:
Eduardo Dantas 2026-06-12 09:11:21 -03:00 committed by GitHub
parent 6cf47c2012
commit 8d98d5a6ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 284 additions and 29 deletions

2
.gitattributes vendored
View file

@ -1,5 +1,7 @@
# Normalize source code before commits
.gitattributes text eol=lf
*.lua eol=lf
*.md text eol=lf
*.txt eol=lf
*.ot* eol=lf
*.cpp eol=lf

View file

@ -31,8 +31,9 @@ Do not introduce an alternative permanent assets root for runtime loading.
The flow supports:
- archive-first installation from release/tag package
- manifest-driven installation
- archive installation from the release/tag source ZIP as the default path
- manifest-driven installation as a fallback path when the archive cannot be installed
- manifest hash identifier installation into `data/things/<version>/assets.json.sha256`
- packaged files list (including large binaries distributed as `.zip`/`.rar`)
- extraction of `.zip` and `.rar`
- optional `.lzma` decompression
@ -43,13 +44,18 @@ Defaults are hardened:
- `strictManifestSha256 = true`
- `allowRawFallbackHashMismatch = false`
- `allowMissingPackedRawFallback = true`
`allowMissingPackedRawFallback` is a narrow compatibility fallback for repository releases that reference official `.lzma`/archive package files not stored in the assets repository. It is only used after the packed file is missing and the client falls back to the raw file from the same manifest/release source. It does not enable arbitrary hash mismatches for normal raw downloads.
Release cache is scoped per source (`releasesUrl` / repository key), avoiding stale cross-source reuse.
## Runtime/Platform Notes
- Desktop targets use `libarchive` for archive extraction.
- Android build excludes `libarchive` dependency (CI compatibility). In this target, archive extraction through `ResourceManager` is unavailable and returns failure explicitly.
- Desktop targets use `libarchive` for archive extraction when it is available.
- Builds without `libarchive` still extract `.zip` archives through the vendored minizip fallback. This keeps the GitHub source ZIP flow functional on clean desktop builds.
- `.rar` extraction requires `libarchive`. If a packaged `.rar` is optional and the build cannot extract it, installation should fail clearly or skip it according to the package configuration.
- The default flow is archive-first because the release source ZIP is the canonical package for this repository. The manifest path remains a compatibility fallback, not the primary installation path.
- Emscripten login fallback was aligned with native `httpLogin` semantics.
## UX Behavior
@ -69,11 +75,15 @@ Check:
- `data/things/<version>/assets.json.sha256`
- `data/sounds/<version>/catalog-sound.json` (when sounds are enabled)
### 2) SHA-256 mismatch
### 2) Missing `.lzma` package file
If the console shows a 404 for `*.lzma`, the client is using the manifest fallback instead of the release source ZIP. First check why archive installation failed. The manifest fallback can install raw files through `allowMissingPackedRawFallback`, but this path is slower and should not be the normal flow for clean installs.
### 3) SHA-256 mismatch
By default, mismatches fail installation. Verify upstream files and hashes first before changing integrity flags.
### 3) Slow progress / “stuck”
### 4) Slow progress / “stuck”
If Content-Length is missing, UI may run in indeterminate mode during download and extraction. Use console logs to confirm active phase.
@ -90,4 +100,3 @@ When changing this system, validate:
3. Runtime loads modern assets from those paths.
4. Hash verification behavior matches configuration.
5. Windows/Linux CI remains green; Android does not attempt to resolve unsupported libarchive linkage.

View file

@ -14,7 +14,9 @@ Services = {
installSounds = true,
strictManifestSha256 = true,
allowRawFallbackHashMismatch = false,
allowMissingPackedRawFallback = true,
preferArchive = true,
fallbackToArchiveOnManifestFailure = false,
installArchiveExtras = true,
archiveExtraPrefixes = { "bin" },
installPackagedFiles = true

View file

@ -10,7 +10,9 @@ local DEFAULT_CONFIG = {
preferPackedManifestUrls = true,
strictManifestSha256 = true,
allowRawFallbackHashMismatch = false,
allowMissingPackedRawFallback = true,
preferArchive = true,
fallbackToArchiveOnManifestFailure = false,
installArchiveExtras = true,
archiveExtraPrefixes = { 'bin' },
archiveExtrasDestination = '',
@ -67,6 +69,29 @@ local function isLzmaPath(path)
return endsWith(tostring(path or ''):lower(), '.lzma')
end
local function isNotFoundError(message)
message = tostring(message or ''):lower()
return message:find('404', 1, true) or message:find('not found', 1, true)
end
local function isMissingPackedHashError(message)
return tostring(message or ''):find(HTTP_NOT_FOUND_SHA256, 1, true) ~= nil
end
local function isPackedToRawFallback(downloadInfo, fallbackDownload)
if not downloadInfo or not fallbackDownload then
return false
end
local sourcePath = tostring(downloadInfo.sourcePath or '')
local fallbackPath = tostring(fallbackDownload.sourcePath or '')
if fallbackPath == '' or isLzmaPath(fallbackPath) or isArchivePath(fallbackPath) then
return false
end
return isLzmaPath(sourcePath) or isArchivePath(sourcePath)
end
local function parentPath(path)
path = tostring(path or ''):gsub('\\', '/')
return path:match('^(.*)/[^/]+$') or ''
@ -318,6 +343,18 @@ local function shouldInstallInWorkDir(config)
return config and config.installInWorkDir ~= false
end
local function writeInstallFile(config, path, contents)
if shouldInstallInWorkDir(config) and g_resources.writeFileContentsToWorkDir then
return g_resources.writeFileContentsToWorkDir(path, contents)
end
local directory = parentPath(path)
if directory ~= '' then
g_resources.makeDir('/' .. directory)
end
return g_resources.writeFileContents('/' .. path, contents)
end
local function hasCatalogEntryFile(basePath, entry)
if type(entry) ~= 'table' or type(entry.file) ~= 'string' then
return false
@ -751,20 +788,34 @@ local function extractDownloadedArchive(config, downloadPath, destinationPath, e
return g_resources.extractDownloadedArchive(downloadPath, destinationPath, entryPrefix or '', stripPrefix == true)
end
local function installDownloadedFile(config, downloadPath, destinationPath, decompressLzma, expectedFileSha256, allowHashMismatch)
local function installDownloadedFile(config, downloadPath, destinationPath, decompressLzma, expectedFileSha256, allowHashMismatch, allowHashMismatchReason)
if not writeDownloadedFile(config, downloadPath, destinationPath, decompressLzma) then
return false, 'Unable to write downloaded asset: ' .. destinationPath
end
local ok, hashError = verifyInstalledSha256(destinationPath, expectedFileSha256, not allowHashMismatch)
if not ok and allowHashMismatch and not hashError:find(HTTP_NOT_FOUND_SHA256, 1, true) then
logWarning(hashError .. ' Continuing with raw fallback because allowRawFallbackHashMismatch is enabled.')
logWarning(hashError .. ' Continuing because ' .. (allowHashMismatchReason or 'allowRawFallbackHashMismatch is enabled') .. '.')
return true
end
return ok, hashError
end
local function writeManifestHashIdentifier(config, version, sha256)
if not sha256 or sha256 == '' then
return true
end
local destinationPath = string.format('data/things/%d/assets.json.sha256', version)
if not writeInstallFile(config, destinationPath, sha256 .. '\n') then
return false, 'Unable to write asset hash identifier: ' .. destinationPath
end
logInfo(string.format('Asset hash identifier install path: %s.', physicalInstallPath(destinationPath)))
return true
end
local function installDownloadedArchive(config, downloadPath, destinationPath, entryPrefix, stripPrefix, expectedPath, expectedSha256)
if not extractDownloadedArchive(config, downloadPath, destinationPath, entryPrefix, stripPrefix) then
return false, 'Unable to extract downloaded archive: ' .. destinationPath
@ -892,11 +943,15 @@ local function installManifestEntries(config, descriptor, files, index, installe
return installManifestEntries(config, descriptor, files, index + 1, installed, total, callback)
end
local preferPacked = descriptor.preferPackedManifestUrls or config.preferPackedManifestUrls
local preferPacked = descriptor.preferPackedManifestUrls
if preferPacked == nil then
preferPacked = config.preferPackedManifestUrls
end
local directDownload = buildManifestDownload(entry, descriptor, selectedEntry, preferPacked)
local fallbackDownload
if config.allowRawFallbackHashMismatch ~= false and not directDownload.extractArchive and not isLzmaPath(directDownload.sourcePath) then
directDownload.allowHashMismatch = true
directDownload.allowHashMismatchReason = 'allowRawFallbackHashMismatch is enabled'
end
if entry.url then
fallbackDownload = buildManifestDownload(entry, descriptor, selectedEntry, not preferPacked)
@ -904,6 +959,7 @@ local function installManifestEntries(config, descriptor, files, index, installe
fallbackDownload = nil
elseif config.allowRawFallbackHashMismatch ~= false and not isArchivePath(fallbackDownload.sourcePath) and not isLzmaPath(fallbackDownload.sourcePath) then
fallbackDownload.allowHashMismatch = true
fallbackDownload.allowHashMismatchReason = 'allowRawFallbackHashMismatch is enabled'
end
end
if directDownload.expectedFileSha256 and g_resources.fileSha256('/' .. directDownload.destinationPath) == directDownload.expectedFileSha256 then
@ -930,6 +986,10 @@ local function installManifestEntries(config, descriptor, files, index, installe
return downloadEntry(downloadInfo, nextFallback, retriesLeft - 1)
end
if nextFallback then
if config.allowMissingPackedRawFallback ~= false and isNotFoundError(err) and isPackedToRawFallback(downloadInfo, nextFallback) then
nextFallback.allowHashMismatch = true
nextFallback.allowHashMismatchReason = 'the packed asset is missing and allowMissingPackedRawFallback is enabled'
end
logWarning(string.format('Download failed for %s: %s. Trying %s.', downloadInfo.sourcePath, err, nextFallback.sourcePath))
return downloadEntry(nextFallback, nil, config.retries or 0)
end
@ -939,11 +999,15 @@ local function installManifestEntries(config, descriptor, files, index, installe
local ok, hashError = verifyDownloadedSha256(path, downloadInfo.expectedDownloadSha256)
if not ok then
if nextFallback then
if config.allowMissingPackedRawFallback ~= false and isMissingPackedHashError(hashError) and isPackedToRawFallback(downloadInfo, nextFallback) then
nextFallback.allowHashMismatch = true
nextFallback.allowHashMismatchReason = 'the packed asset is missing and allowMissingPackedRawFallback is enabled'
end
logWarning(hashError .. ' Trying ' .. nextFallback.sourcePath .. '.')
return downloadEntry(nextFallback, nil, config.retries or 0)
end
if downloadInfo.allowHashMismatch and not hashError:find(HTTP_NOT_FOUND_SHA256, 1, true) then
logWarning(hashError .. ' Continuing with raw fallback because allowRawFallbackHashMismatch is enabled.')
logWarning(hashError .. ' Continuing because ' .. (downloadInfo.allowHashMismatchReason or 'allowRawFallbackHashMismatch is enabled') .. '.')
ok = true
else
return callback(false, hashError)
@ -965,7 +1029,7 @@ local function installManifestEntries(config, descriptor, files, index, installe
downloadInfo.expectedFileSha256
)
else
ok, hashError = installDownloadedFile(config, path, downloadInfo.destinationPath, downloadInfo.decompressLzma, downloadInfo.expectedFileSha256, downloadInfo.allowHashMismatch)
ok, hashError = installDownloadedFile(config, path, downloadInfo.destinationPath, downloadInfo.decompressLzma, downloadInfo.expectedFileSha256, downloadInfo.allowHashMismatch, downloadInfo.allowHashMismatchReason)
end
if not ok then
if nextFallback then
@ -1018,8 +1082,8 @@ local function installFromManifest(config, descriptor, callback)
end
fetchManifestSha256(config, descriptor, function(expectedSha256)
local actualSha256 = g_crypt.sha256(data)
if expectedSha256 then
local actualSha256 = g_crypt.sha256(data)
if actualSha256 ~= expectedSha256 then
local hashError = string.format('Invalid assets manifest SHA-256 for %s. Expected %s, got %s.', descriptor.manifestUrl, expectedSha256, actualSha256)
if descriptor.strictManifestSha256 or config.strictManifestSha256 then
@ -1036,6 +1100,11 @@ local function installFromManifest(config, descriptor, callback)
return callback(false, 'Invalid assets manifest.')
end
local wroteHashIdentifier, writeHashError = writeManifestHashIdentifier(config, descriptor.version, actualSha256)
if not wroteHashIdentifier then
return callback(false, writeHashError)
end
installManifestEntries(config, descriptor, manifest.files, 1, 0, #manifest.files, callback)
end)
end)
@ -1202,7 +1271,18 @@ local function installPackagedFileList(config, descriptor, files, index, callbac
scheduleDownloadStep(function()
local ok, extractError = installDownloadedArchive(config, downloadPath, destinationPath, '', false)
if not ok then
return callback(false, extractError)
if descriptor.packagedFilesRequired then
return callback(false, extractError)
end
logWarning(string.format(
'Skipping optional packaged file %d/%d for client %s: %s (%s).',
index,
#files,
versionLabel(descriptor.version),
path,
extractError or 'unable to extract archive'
))
return installPackagedFileList(config, descriptor, files, index + 1, callback)
end
logInfo(string.format('Finished packaged file %d/%d for client %s: %s.', index, #files, versionLabel(descriptor.version), path))
@ -1244,6 +1324,8 @@ local function installPackagedFiles(config, descriptor, callback)
end
local function installDescriptor(config, descriptor, callback)
local archiveAttempted = false
local function finishWithPackagedFiles(ok, message)
if not ok then
return callback(false, message)
@ -1254,25 +1336,49 @@ local function installDescriptor(config, descriptor, callback)
end)
end
local function archiveFallback(nextCallback)
archiveAttempted = true
installFromArchive(config, descriptor, nextCallback)
end
local function manifestFallback()
installFromManifest(config, descriptor, function(ok, message)
if ok or not descriptor.archiveUrl then
local allowArchiveFallback = descriptor.fallbackToArchiveOnManifestFailure
if allowArchiveFallback == nil then
allowArchiveFallback = config.fallbackToArchiveOnManifestFailure
end
if ok or not descriptor.archiveUrl or archiveAttempted or not allowArchiveFallback then
return finishWithPackagedFiles(ok, message)
end
installFromArchive(config, descriptor, finishWithPackagedFiles)
logWarning((message or 'Asset manifest install failed.') .. ' Trying archive fallback.')
archiveFallback(finishWithPackagedFiles)
end)
end
if descriptor.preferArchive or config.preferArchive then
return installFromArchive(config, descriptor, function(ok, message)
local preferArchive = descriptor.preferArchive
if preferArchive == nil then
preferArchive = config.preferArchive
end
if preferArchive and descriptor.archiveUrl then
return archiveFallback(function(ok, message)
if ok or not descriptor.manifestUrl then
return finishWithPackagedFiles(ok, message)
end
logWarning((message or 'Archive asset install failed.') .. ' Falling back to asset manifest.')
manifestFallback()
end)
end
manifestFallback()
if descriptor.manifestUrl then
return manifestFallback()
end
if descriptor.archiveUrl then
return archiveFallback(finishWithPackagedFiles)
end
finishWithPackagedFiles(false, 'No assets source found.')
end
local function resolveFromCustomManifest(config, version, callback)

View file

@ -801,6 +801,7 @@ function EnterGame.doLogin()
g_settings.set('client-version', clientVersion)
if clientVersion >= 1281 and modules.client_assets and modules.client_assets.ensureClientVersion and
(not modules.client_assets.isEnabled or modules.client_assets.isEnabled()) and
not modules.client_assets.isClientVersionInstalled(clientVersion) then
modules.client_assets.ensureClientVersion(clientVersion, function(success, message)
if success then

View file

@ -521,8 +521,8 @@ if (WASM)
)
endif()
if(ANDROID)
# Vendored minizip sources for APK asset extraction (must be after SOURCE_FILES definition)
if(NOT WASM)
# Vendored minizip sources for ZIP extraction fallback (must be after SOURCE_FILES definition)
set(SOURCE_FILES ${SOURCE_FILES}
framework/core/minizip/ioapi.c
framework/core/minizip/ioapi_mem.c
@ -567,8 +567,8 @@ else()
message(STATUS "Use precompiled header: OFF")
endif(TOGGLE_PRE_COMPILED_HEADER)
# Exclude vendored minizip C files from PCH and unity build (Android only, C files cannot use C++ PCH)
if(ANDROID)
# Exclude vendored minizip C files from PCH and unity build (C files cannot use C++ PCH)
if(NOT WASM)
set_source_files_properties(
framework/core/minizip/ioapi.c
framework/core/minizip/ioapi_mem.c

View file

@ -41,6 +41,10 @@
#ifdef FRAMEWORK_HAVE_LIBARCHIVE
#include <archive.h>
#include <archive_entry.h>
#else
#include "minizip/ioapi.h"
#include "minizip/ioapi_mem.h"
#include "minizip/unzip.h"
#endif
ResourceManager g_resources;
@ -192,6 +196,109 @@ HttpResult_ptr getDownloadedFile(std::string path)
return g_http.getFile(path);
}
#ifndef FRAMEWORK_HAVE_LIBARCHIVE
bool extractDownloadedZipArchive(ResourceManager& resourceManager, const std::string& path, const std::string& archive, std::string destinationPath, const std::string& entryPrefix, const bool stripPrefix)
{
if (archive.size() > UINT32_MAX) {
g_logger.error("Downloaded archive '{}' is too large for zip extraction", path);
return false;
}
zlib_filefunc_def fileFunctions = {};
ourmemory_t archiveMemory = {};
archiveMemory.base = const_cast<char*>(archive.data());
archiveMemory.size = static_cast<uint32_t>(archive.size());
archiveMemory.grow = 0;
fill_memory_filefunc(&fileFunctions, &archiveMemory);
unzFile zipFile = unzOpen2(nullptr, &fileFunctions);
if (!zipFile) {
g_logger.error("Unable to open downloaded zip archive '{}'. Non-zip archives require libarchive support.", path);
return false;
}
unz_global_info globalInfo = {};
if (unzGetGlobalInfo(zipFile, &globalInfo) != UNZ_OK) {
g_logger.error("Unable to read zip archive info for '{}'", path);
unzClose(zipFile);
return false;
}
destinationPath = normalizeVirtualPath(std::move(destinationPath));
if (!destinationPath.empty() && !destinationPath.ends_with('/'))
destinationPath.push_back('/');
bool wroteFile = false;
constexpr int maxFilenameSize = 1024;
constexpr int readSize = 8192;
std::array<char, maxFilenameSize> fileName = {};
std::array<char, readSize> readBuffer = {};
for (uint32_t i = 0; i < globalInfo.number_entry; ++i) {
unz_file_info fileInfo = {};
fileName.fill('\0');
if (unzGetCurrentFileInfo(zipFile, &fileInfo, fileName.data(), static_cast<uint16_t>(fileName.size()), nullptr, 0, nullptr, 0) != UNZ_OK) {
g_logger.error("Unable to read zip entry info from '{}'", path);
unzClose(zipFile);
return false;
}
std::string entryName = fileName.data();
const bool isDirectory = entryName.ends_with('/') || entryName.ends_with('\\');
const auto relativePath = selectArchiveEntryPath(entryName, entryPrefix, stripPrefix);
if (!isDirectory && !relativePath.empty()) {
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
g_logger.error("Unable to open zip entry '{}' from '{}'", entryName, path);
unzClose(zipFile);
return false;
}
std::string contents;
int readBytes = 0;
do {
readBytes = unzReadCurrentFile(zipFile, readBuffer.data(), readBuffer.size());
if (readBytes < 0) {
g_logger.error("Unable to read zip entry '{}' from '{}': {}", entryName, path, readBytes);
unzCloseCurrentFile(zipFile);
unzClose(zipFile);
return false;
}
if (readBytes > 0)
contents.append(readBuffer.data(), static_cast<size_t>(readBytes));
} while (readBytes > 0);
if (unzCloseCurrentFile(zipFile) != UNZ_OK) {
g_logger.error("Unable to close zip entry '{}' from '{}'", entryName, path);
unzClose(zipFile);
return false;
}
const auto destinationFile = destinationPath + relativePath;
if (!resourceManager.writeFileBuffer(
destinationFile,
reinterpret_cast<const uint8_t*>(contents.data()),
static_cast<uint32_t>(contents.size()),
true
)) {
unzClose(zipFile);
return false;
}
wroteFile = true;
}
if (i + 1 < globalInfo.number_entry && unzGoToNextFile(zipFile) != UNZ_OK) {
g_logger.error("Unable to advance zip archive '{}' to next entry", path);
unzClose(zipFile);
return false;
}
}
unzClose(zipFile);
return wroteFile;
}
#endif
} // namespace
void ResourceManager::init(const char* argv0)
@ -833,12 +940,19 @@ bool ResourceManager::writeDownloadedFileToWorkDir(const std::string& path, std:
bool ResourceManager::extractDownloadedArchive(const std::string& path, std::string destinationPath, const std::string& entryPrefix, const bool stripPrefix)
{
#ifndef FRAMEWORK_HAVE_LIBARCHIVE
(void)path;
(void)destinationPath;
(void)entryPrefix;
(void)stripPrefix;
g_logger.error("Archive extraction is unavailable on this platform.");
return false;
const auto downloadedFile = getDownloadedFile(path);
if (!downloadedFile) {
g_logger.error("Cannot find downloaded archive '{}'", path);
return false;
}
const auto& archive = downloadedFile->response;
if (archive.empty()) {
g_logger.error("Downloaded archive '{}' is empty", path);
return false;
}
return extractDownloadedZipArchive(*this, path, archive, std::move(destinationPath), entryPrefix, stripPrefix);
#else
const auto downloadedFile = getDownloadedFile(path);
if (!downloadedFile) {

View file

@ -411,6 +411,26 @@
<ClCompile Include="..\src\framework\core\logger.cpp" />
<ClCompile Include="..\src\framework\core\module.cpp" />
<ClCompile Include="..\src\framework\core\modulemanager.cpp" />
<ClCompile Include="..\src\framework\core\minizip\crypt.c">
<IncludeInUnityFile>false</IncludeInUnityFile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<ForcedIncludeFiles></ForcedIncludeFiles>
</ClCompile>
<ClCompile Include="..\src\framework\core\minizip\ioapi.c">
<IncludeInUnityFile>false</IncludeInUnityFile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<ForcedIncludeFiles></ForcedIncludeFiles>
</ClCompile>
<ClCompile Include="..\src\framework\core\minizip\ioapi_mem.c">
<IncludeInUnityFile>false</IncludeInUnityFile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<ForcedIncludeFiles></ForcedIncludeFiles>
</ClCompile>
<ClCompile Include="..\src\framework\core\minizip\unzip.c">
<IncludeInUnityFile>false</IncludeInUnityFile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<ForcedIncludeFiles></ForcedIncludeFiles>
</ClCompile>
<ClCompile Include="..\src\framework\core\resourcemanager.cpp" />
<ClCompile Include="..\src\framework\core\scheduledevent.cpp" />
<ClCompile Include="..\src\framework\core\timer.cpp" />

View file

@ -13,6 +13,7 @@
FRAMEWORK_NET;
FRAMEWORK_SOUND;
FRAMEWORK_PROTOBUF;
FRAMEWORK_HAVE_LIBARCHIVE;
USE_PRECOMPILED_HEADERS;
OPENSSL_API_COMPAT=0x10100000L;
OPENSSL_SUPPRESS_DEPRECATED;