mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
Add automatic client-asset installation for modern Tibia versions in OTC, with the UI and runtime support needed for a safe download-and-install flow. Main changes: - Add modules/client_assets to orchestrate descriptor and release resolution, downloads, retries, cancellation, archive extraction, optional LZMA handling, and SHA-256 integrity checks. - Add richer install logs in the console, including download phases, install phases, and final install paths. - Add support for packaged runtime files, including large DLLs distributed through archives. - Make client_entergame depend on client_assets. - Check required assets during login for modern clients version 1281 and newer. - Prompt the user to download missing modern assets instead of requiring manual setup. - Allow the client list and version selection to work with installed modern asset sets. Install layout: - Install things assets under data/things/<version>. - Install sound assets under data/sounds/<version>. - Keep extra runtime files in the existing client root/bin flow. - Preserve the existing OTC asset layout as the source of truth instead of introducing a separate permanent path. UI improvements: - Improve missing-assets and progress dialog sizing and text wrapping. - Improve progress behavior for downloads without Content-Length. - Add indeterminate progress behavior for unknown-length phases. - Add an hourglass busy indicator for indeterminate phases. - Reduce the perception that the installer is stuck during long extraction steps. Runtime and build support: - Add libarchive usage and linking for archive extraction. - Add SHA-256 helpers and Lua bindings used by client_assets. - Add safer extraction and path normalization in ResourceManager. - Adjust the Emscripten HTTP login path to follow the same HTTP/HTTPS fallback behavior used by native login. Security and integrity: - Enable strictManifestSha256 by default. - Disable raw fallback on hash mismatch by default. - Key releasesCache per source URL and repository to avoid stale cross-source cache reuse. - Add an LZMA decompression output cap to reduce memory-exhaustion risk. Compatibility: - Keep existing classic asset behavior unchanged. - Use the new auto-install flow only for modern versions when required assets are missing. - Keep the scope limited to client-side asset automation and required runtime support. - No server-side Canary behavior is changed by this PR. This makes modern client setup safer and more automatic by downloading, validating, extracting, and installing missing assets directly from OTC while preserving the existing classic asset flow.
110 lines
3.4 KiB
Lua
110 lines
3.4 KiB
Lua
ThingsLoaderController = Controller:new()
|
|
|
|
local filename = nil
|
|
local loaded = false
|
|
|
|
function setFileName(name)
|
|
filename = name
|
|
end
|
|
|
|
function isLoaded()
|
|
return loaded
|
|
end
|
|
|
|
local function tryLoadDatWithFallbacks(datPath)
|
|
if g_things.loadDat(datPath) then
|
|
return true
|
|
end
|
|
|
|
local featureFlags = {
|
|
GameSpritesU32,
|
|
GameEnhancedAnimations,
|
|
GameIdleAnimations
|
|
}
|
|
|
|
local combinations = {
|
|
{ 1 }, { 2 }, { 3 },
|
|
{ 1, 2 }, { 1, 3 }, { 2, 3 },
|
|
{ 1, 2, 3 }
|
|
}
|
|
|
|
for _, combo in ipairs(combinations) do
|
|
for _, idx in ipairs(combo) do
|
|
g_game.enableFeature(featureFlags[idx])
|
|
end
|
|
|
|
if g_things.loadDat(datPath) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function load(version)
|
|
local errorList = {}
|
|
|
|
if version >= 1281 and not g_game.getFeature(GameLoadSprInsteadProtobuf) then
|
|
local filePath = resolvepath(string.format('/data/things/%d/', version))
|
|
if not g_things.loadAppearances(filePath) then
|
|
errorList[#errorList + 1] = "Couldn't load assets"
|
|
end
|
|
if not g_things.loadStaticData(filePath) then
|
|
errorList[#errorList + 1] = "Couldn't load staticdata"
|
|
end
|
|
if g_game.getFeature(GameProficiency) and not g_things.resolveProficienciesFile(filePath) then
|
|
g_logger.warning(string.format(
|
|
"[game_things.load()] Couldn't load /data/things/%d/proficiencies-<hash>.json", version))
|
|
end
|
|
else
|
|
local datPath, sprPath
|
|
if filename then
|
|
datPath = resolvepath('/data/things/' .. filename)
|
|
sprPath = resolvepath('/data/things/' .. filename)
|
|
else
|
|
datPath = resolvepath('/data/things/' .. version .. '/Tibia')
|
|
sprPath = resolvepath('/data/things/' .. version .. '/Tibia')
|
|
end
|
|
|
|
g_logger.setLevel(5)
|
|
if not tryLoadDatWithFallbacks(datPath) then
|
|
errorList[#errorList + 1] = tr('Unable to load dat file, please place a valid dat in \'%s.dat\'', datPath)
|
|
end
|
|
g_logger.setLevel(1)
|
|
|
|
if not g_sprites.loadSpr(sprPath) then
|
|
errorList[#errorList + 1] = tr('Unable to load spr file, please place a valid spr in \'%s.spr\'', sprPath)
|
|
end
|
|
if g_game.getFeature(GameLoadSprInsteadProtobuf) and version >= 1281 then
|
|
local staticPath = resolvepath(string.format('/data/things/%d/appearances', version))
|
|
if not g_things.loadAppearances(staticPath) then
|
|
g_logger.warning(string.format(
|
|
"[game_things.load()] Couldn't load /data/things/%d/appearances.dat, possible packets error.", version))
|
|
end
|
|
end
|
|
end
|
|
|
|
loaded = #errorList == 0
|
|
if loaded then
|
|
-- loading client files was successful, try to load sounds now
|
|
-- sound files are optional, this means that failing to load them
|
|
-- will not block logging into game
|
|
g_sounds.loadClientFiles(resolvepath(string.format('/data/sounds/%d/', version)))
|
|
return
|
|
end
|
|
|
|
local messageBox = displayErrorBox(tr('Error'), table.concat(errorList, "\n"))
|
|
addEvent(function()
|
|
messageBox:raise()
|
|
messageBox:focus()
|
|
end)
|
|
|
|
g_game.setClientVersion(0)
|
|
g_game.setProtocolVersion(0)
|
|
end
|
|
|
|
function ThingsLoaderController:onInit()
|
|
self:registerEvents(g_game, {
|
|
onClientVersionChange = load
|
|
})
|
|
end
|