Mercurial > wow > cyborg-mmo7
diff support/casc.lua @ 65:8b8b0bade520
Fixed support for mounts using the new MountJournal and mount IDs (no conversion of old profiles at the moment).
author | Jerome Vuarand <jerome.vuarand@gmail.com> |
---|---|
date | Thu, 23 Oct 2014 13:44:59 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,342 @@ +local M, plat, bin = {}, require("casc.platform"), require("casc.bin") +local jenkins96, blte = require("casc.jenkins96"), require("casc.blte") + +local uint32_le, uint32_be, uint40_be, uint16_le, to_be40, to_le32, to_bin, to_hex, ssub + = bin.uint32_le, bin.uint32_be, bin.uint40_be, bin.uint16_le, bin.to_be40, bin.to_le32, bin.to_bin, bin.to_hex, string.sub + +M.locale = {} do + for k,v in pairs({US=0x02, KR=0x04, FR=0x10, DE=0x20, CN=0x40, ES=0x80, TW=0x0100, GB=0x0200, MX=0x1000, RU=0x2000, BR=0x4000, IT=0x8000, PT=0x010000}) do + local isCN, m = k == "cn", v * 2 + M.locale[k] = function(loc, tf) + return loc % m >= v and ((tf % 16 > 7) == isCN and 2 or 1) or nil + end + end +end +local function defaultLocale(loc, tf, cdn) + return (loc % 0x400 < 0x200 and 0 or 2) + (tf % 16 < 8 and 1 or 0) - (cdn and 4 or 0) +end + +local function check_args(name, idx, v, t1, t2, ...) + if t1 then + local tt = type(v) + if tt ~= t1 and tt ~= t2 then + error('Invalid argument #' .. idx .. " to " .. name .. ": " .. t1 .. " expected; got " .. tt, 3) + end + return check_args(name, idx+1, ...) + end +end + +local function readFile(...) + local path = plat.path(...) + if path then + local h, err = io.open(path, "rb") + if h then + local c = h:read("*a") + h:close() + return c + end + return h, err + end +end +local function readURL(...) + local url = plat.url(...) + if url then + return plat.http(url) + end +end +local function prefixHash(h) + local a, b, c = h:match("((%x%x)(%x%x).+)") + return b, c, a +end +local function cachedRead3(cpath, lpath, url) + local ret = cpath and readFile(cpath) or lpath and readFile(lpath) + if not ret and url then + ret = readURL(url) + if ret and cpath then + local h = io.open(cpath, "wb") + if h then + h:write(ret) + h:close() + end + end + end + return ret +end +local function cachedRead(casc, ...) + local ckey = plat.url(...) + ckey = ckey and casc.cachebase and plat.path(casc.cachebase, (ckey:gsub("[/\\]", "-"))) + return cachedRead3(ckey, plat.path(casc.base, ...), plat.url(casc.cdnbase, ...)) +end + +local function parseInfoData(data) + local hname, htype, hn, ret, ln, last = {}, {}, 1, {}, 1 + local i, s, line = data:gmatch("[^\n]+") + line = i(s, line) + for e in line:gmatch("[^|]+") do + hn, hname[hn], htype[hn] = hn + 1, e:match("^([^!]+)!([^:]+)") + end + + for e in i,s,line do + local l, ln = {}, 1 + for f in e:gmatch("|?([^|]*)") do + if f ~= "" then + l[hname[ln]] = htype[ln] == "DEC" and tonumber(f) or f + end + ln = ln + 1 + end + if ln > 1 then + ret[#ret+1] = l + end + end + + return ret +end +local function parseConfigData(data, into) + local ret, ln, last = type(into) == "table" and into or {}, 1 + for l in data:gmatch("[^\r\n]+") do + if l:match("^%s*[^#].-%S") then + local name, args = l:match("^%s*(%S+)%s*=(.*)$") + if name then + last, ln = {}, 1 + ret[name] = last + end + for s in (args or l):gmatch("%S+") do + last[ln], ln = s, ln + 1 + end + end + end + return ret +end +local function parseLocalIndexData(data, into) + local pos, sub, len = 8 + uint32_le(data), data.sub + pos = pos + ((16 - pos % 16) % 16) + len, pos = uint32_le(data, pos), pos + 9 + assert(len % 18 == 0, "Index data block length parity check") + + for i=1, len/18 do + into[sub(data, pos, pos + 8)], pos = uint40_be(data, pos+8), pos + 18 + end + + return len/18 +end +local function parseCDNIndexData(name, data, into) + local dlen, p = #data-28, 0 + for i=1, math.floor(dlen/4100) - math.floor(dlen/844600) do + for pos=p, p+4072, 24 do + local len = uint32_be(data, pos+16) + if len > 0 then + local ofs = uint32_be(data, pos+20) + into[ssub(data, pos+1, pos+9)] = ofs .. "-" .. (ofs+len) .. ":" .. name + end + end + p = p + 4096 + end +end +local function parseRootData(data, ed) + local ret, pos, dl = {}, 1, #data + while pos < dl do + local n, tflag, loc = uint32_le(data, pos-1), uint32_le(data, pos+3), uint32_le(data, pos+7) + pos = pos + 12 + 4*n + for i=1,n do + local nhash = ssub(data, pos+16, pos+23) + local t, j = ret[nhash] or {} + ret[nhash], j = t, #t + t[j+1], t[j+2], t[j+3], pos = ssub(data, pos,pos+15), tflag, loc, pos + 24 + end + end + return ret +end +local function parseEncodingData(data) + local h, locale, n = {}, data:sub(1,2), uint32_be(data, 9) + local p2 = 22 + uint32_be(data, 18) + 32*n + for i=1, n do + local c = uint16_le(data, p2) + while c > 0 do + local chsh = ssub(data, p2+7, p2+22) + h[chsh] = {size=uint32_be(data, p2+2)} + p2 = p2 + 6+16 + for i=1, c do + h[chsh][i], p2 = ssub(data, p2+1, p2+16), p2 + 16 + end + c = uint16_le(data, p2) + end + p2 = data:match("()%z%Z", p2) + end + return h, locale +end + +local function getFileByLoc(casc, loc, lcache) + local cnt = lcache and readFile(lcache) + if cnt then + elseif type(loc) == "number" and casc.base then + cnt = blte.readArchive(plat.path(casc.base, "data", ("data.%03d"):format(loc / 2^30)), loc % 2^30) + elseif type(loc) == "string" and casc.cdnbase then + local range, name = loc:match("(%d+%-%d+):(.+)") + cnt = blte.readData(plat.http(plat.url(casc.cdnbase, "data", prefixHash(name or loc)), range and {Range="bytes=" .. range})) + if lcache and cnt then + local h = io.open(lcache, "wb") + if h then + h:write(cnt) + h:close() + end + end + end + return cnt +end +local function getLocByFileKey(casc, fileKey) + local key, i, i2 = fileKey:sub(1,9), casc.index, casc.indexCDN + return i and i[key] or i2 and i2[key] +end +local function getFileByKey(casc, fileKey, lcache) + return getFileByLoc(casc, getLocByFileKey(casc, #fileKey == 32 and to_bin(fileKey) or fileKey), lcache) +end +local function getLocFromRootEntry(casc, e, rateFunc) + local score, ret = -math.huge + for i=1,e and #e or 0, 3 do + local keys = casc.encoding[e[i] or e] + local loc = keys and keys[1] and getLocByFileKey(casc, keys[1]) + if loc then + local lscore = rateFunc(e[i+2], e[i+1], type(loc) == "string") + if lscore and lscore > score then + score, ret = lscore, loc + end + end + end + return ret +end +local function addCDNKey(casc, key) + if casc.indexCDN then + local hkey = #key == 32 and key or to_hex(key) + local rkey = #key == 16 and key or to_bin(key) + casc.indexCDN[rkey:sub(1,9)] = hkey + end + return key +end + +local handle = {} +local handle_mt = {__index=handle} +function handle:readFile(path, lang) + lang = M.locale[lang] or lang + check_args("cascHandle:readFile", 1, path, "string", "string", lang, "function", "nil") + + local h = jenkins96.hash_path(path) + return getFileByLoc(self, getLocFromRootEntry(self, self.root and self.root[h], lang or self.locale)) +end +function handle:readFileByEncodingHash(hash) + check_args("cascHandle:readFileByEncodingHash", 1, hash, "string", "string") + + return getFileByKey(self, #hash == 32 and to_bin(hash) or hash) +end +function handle:readFileByContentHash(hash) + check_args("cascHandle:readFileByContentHash", 1, hash, "string", "string") + + local k = self.encoding[#hash == 32 and to_bin(hash) or hash] + return k and getFileByKey(self, k[1]) +end +function handle:setLocale(locale) + check_args("cascHandle:setLocale", 1, M.locale[locale] or locale, "function", "nil") + + self.locale = M.locale[locale] or locale or defaultLocale +end + +function M.open(localBase, buildKey, cdnBase, cdnKey, cacheBase) + if buildKey == nil then error('Syntax: cac.open("localBase" or nil, "buildKey", "cdnBase" or nil, "cdnKey" or nil[, "cacheBase"])') end + check_args("casc.open", 1, localBase, "string", "nil", buildKey, "string", nil, cdnBase, "string", "nil", cdnKey, "string", "nil", cacheBase, "string", "nil") + assert(type(localBase) == "string" or type(cdnBase) == "string", 'invalid arguments: at least one of localBase, cdnBase must not be nil') + assert(type(cdnBase) ~= "string" or type(cdnKey) == "string", 'invalid arguments: if cdnBase is specified, cdnKey must be a string') + + local casc = setmetatable({base=localBase, buildKey=buildKey, cdnbase=cdnBase, cdnKey=cdnKey, cachebase=cacheBase, locale=defaultLocale}, handle_mt) + casc.conf = parseConfigData(cachedRead(casc, "config", prefixHash(buildKey))) + if cdnKey then + parseConfigData(cachedRead(casc, "config", prefixHash(cdnKey)), casc.conf) + casc.indexCDN = {} + for k,v in pairs(casc.conf.archives) do + parseCDNIndexData(v, cachedRead3( + plat.path(cacheBase, "index." .. v), + plat.path(localBase, "indices", v .. ".index"), + plat.url(cdnBase, "data", prefixHash(v .. ".index")) + ), casc.indexCDN) + end + end + if localBase then + casc.index = {} + for _, f in plat.files(plat.path(localBase, "data"), "*.idx") do + parseLocalIndexData(assert(readFile(f)), casc.index) + end + end + + local ekey = addCDNKey(casc, casc.conf.encoding[#casc.conf.encoding]) + casc.encoding = parseEncodingData(getFileByKey(casc, ekey, plat.path(cacheBase, "encoding." .. ekey))) + local rkey = addCDNKey(casc, casc.encoding[to_bin(casc.conf.root[#casc.conf.root])][1]) + casc.root = parseRootData(getFileByKey(casc, rkey, plat.path(cacheBase, "root." .. to_hex(rkey)))) + + return casc +end + +function M.cdnbuild(patchBase, region) + check_args("casc.cdnbuild", 1, patchBase, "string", "string", region, "string", "nil") + + local versions = parseInfoData(plat.http(plat.url(patchBase, "versions"))) + local cdns = parseInfoData(plat.http(plat.url(patchBase, "cdns"))) + local reginfo = {} + for i=1,#versions do + local v = versions[i] + reginfo[v.Region] = {cdnKey = v.CDNConfig, buildKey=v.BuildConfig, build=v.BuildId, version=v.VersionsName} + end + for i=1,#cdns do + local c = cdns[i] + if reginfo[c.Name] then + reginfo[c.Name].cdnBase = c.Hosts and c.Path and plat.url("http://", c.Hosts:match("%S+"), c.Path) + end + end + if reginfo[region] and reginfo[region].cdnBase then + local r = reginfo[region] + return r.buildKey, r.cdnBase, r.cdnKey, r.version, r.build + elseif region == nil then + return reginfo, versions, cdns + end +end +function M.localbuild(buildInfoPath, selectBuild) + check_args("casc.localbuild", 1, buildInfoPath, "string", "string", selectBuild, "function", "nil") + + local info = parseInfoData(assert(readFile(buildInfoPath))) + if type(selectBuild) == "function" and info then + local ii = info[selectBuild(info)] + if ii then + return ii["Build Key"], plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]), ii["CDN Key"], ii["Version"] + end + elseif info then + local branches = {} + for i=1,#info do + local ii = info[i] + local curl = plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]) + branches[ii.Branch] = {cdnKey=ii["CDN Key"], buildKey=ii["Build Key"], version=ii["Version"], cdnBase=curl} + end + return branches, info + end +end + +function M.selectActiveBuild(buildInfo) + check_args("casc.selectActiveBuild", 1, buildInfo, "table", "table") + + for i=1,#buildInfo do + if buildInfo[i].Active == 1 then + return i + end + end +end +function M.selectUserBuild(buildInfo) + check_args("casc.selectUserBuild", 1, buildInfo, "table", "table") + + print("Available builds:") + for i=1,#buildInfo do + print(("%d. [%s] %s (%s)%s"):format(i, buildInfo[i]["Build Key"], buildInfo[i].Version, buildInfo[i].Branch, buildInfo[i].Active == 1 and " (active)" or "")) + end + io.stdout:write("Enter index: ") + io.stdout:flush() + + local c = tonumber(io.stdin:read("*l")) + return buildInfo[c] and c +end + +return M \ No newline at end of file