jerome@65: local M, plat, bin = {}, require("casc.platform"), require("casc.bin") jerome@65: local jenkins96, blte = require("casc.jenkins96"), require("casc.blte") jerome@65: jerome@65: local uint32_le, uint32_be, uint40_be, uint16_le, to_be40, to_le32, to_bin, to_hex, ssub jerome@65: = 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 jerome@65: jerome@65: M.locale = {} do jerome@65: 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 jerome@65: local isCN, m = k == "cn", v * 2 jerome@65: M.locale[k] = function(loc, tf) jerome@65: return loc % m >= v and ((tf % 16 > 7) == isCN and 2 or 1) or nil jerome@65: end jerome@65: end jerome@65: end jerome@65: local function defaultLocale(loc, tf, cdn) jerome@65: return (loc % 0x400 < 0x200 and 0 or 2) + (tf % 16 < 8 and 1 or 0) - (cdn and 4 or 0) jerome@65: end jerome@65: jerome@65: local function check_args(name, idx, v, t1, t2, ...) jerome@65: if t1 then jerome@65: local tt = type(v) jerome@65: if tt ~= t1 and tt ~= t2 then jerome@65: error('Invalid argument #' .. idx .. " to " .. name .. ": " .. t1 .. " expected; got " .. tt, 3) jerome@65: end jerome@65: return check_args(name, idx+1, ...) jerome@65: end jerome@65: end jerome@65: jerome@65: local function readFile(...) jerome@65: local path = plat.path(...) jerome@65: if path then jerome@65: local h, err = io.open(path, "rb") jerome@65: if h then jerome@65: local c = h:read("*a") jerome@65: h:close() jerome@65: return c jerome@65: end jerome@65: return h, err jerome@65: end jerome@65: end jerome@65: local function readURL(...) jerome@65: local url = plat.url(...) jerome@65: if url then jerome@65: return plat.http(url) jerome@65: end jerome@65: end jerome@65: local function prefixHash(h) jerome@65: local a, b, c = h:match("((%x%x)(%x%x).+)") jerome@65: return b, c, a jerome@65: end jerome@65: local function cachedRead3(cpath, lpath, url) jerome@65: local ret = cpath and readFile(cpath) or lpath and readFile(lpath) jerome@65: if not ret and url then jerome@65: ret = readURL(url) jerome@65: if ret and cpath then jerome@65: local h = io.open(cpath, "wb") jerome@65: if h then jerome@65: h:write(ret) jerome@65: h:close() jerome@65: end jerome@65: end jerome@65: end jerome@65: return ret jerome@65: end jerome@65: local function cachedRead(casc, ...) jerome@65: local ckey = plat.url(...) jerome@65: ckey = ckey and casc.cachebase and plat.path(casc.cachebase, (ckey:gsub("[/\\]", "-"))) jerome@65: return cachedRead3(ckey, plat.path(casc.base, ...), plat.url(casc.cdnbase, ...)) jerome@65: end jerome@65: jerome@65: local function parseInfoData(data) jerome@65: local hname, htype, hn, ret, ln, last = {}, {}, 1, {}, 1 jerome@65: local i, s, line = data:gmatch("[^\n]+") jerome@65: line = i(s, line) jerome@65: for e in line:gmatch("[^|]+") do jerome@65: hn, hname[hn], htype[hn] = hn + 1, e:match("^([^!]+)!([^:]+)") jerome@65: end jerome@65: jerome@65: for e in i,s,line do jerome@65: local l, ln = {}, 1 jerome@65: for f in e:gmatch("|?([^|]*)") do jerome@65: if f ~= "" then jerome@65: l[hname[ln]] = htype[ln] == "DEC" and tonumber(f) or f jerome@65: end jerome@65: ln = ln + 1 jerome@65: end jerome@65: if ln > 1 then jerome@65: ret[#ret+1] = l jerome@65: end jerome@65: end jerome@65: jerome@65: return ret jerome@65: end jerome@65: local function parseConfigData(data, into) jerome@65: local ret, ln, last = type(into) == "table" and into or {}, 1 jerome@65: for l in data:gmatch("[^\r\n]+") do jerome@65: if l:match("^%s*[^#].-%S") then jerome@65: local name, args = l:match("^%s*(%S+)%s*=(.*)$") jerome@65: if name then jerome@65: last, ln = {}, 1 jerome@65: ret[name] = last jerome@65: end jerome@65: for s in (args or l):gmatch("%S+") do jerome@65: last[ln], ln = s, ln + 1 jerome@65: end jerome@65: end jerome@65: end jerome@65: return ret jerome@65: end jerome@65: local function parseLocalIndexData(data, into) jerome@65: local pos, sub, len = 8 + uint32_le(data), data.sub jerome@65: pos = pos + ((16 - pos % 16) % 16) jerome@65: len, pos = uint32_le(data, pos), pos + 9 jerome@65: assert(len % 18 == 0, "Index data block length parity check") jerome@65: jerome@65: for i=1, len/18 do jerome@65: into[sub(data, pos, pos + 8)], pos = uint40_be(data, pos+8), pos + 18 jerome@65: end jerome@65: jerome@65: return len/18 jerome@65: end jerome@65: local function parseCDNIndexData(name, data, into) jerome@65: local dlen, p = #data-28, 0 jerome@65: for i=1, math.floor(dlen/4100) - math.floor(dlen/844600) do jerome@65: for pos=p, p+4072, 24 do jerome@65: local len = uint32_be(data, pos+16) jerome@65: if len > 0 then jerome@65: local ofs = uint32_be(data, pos+20) jerome@65: into[ssub(data, pos+1, pos+9)] = ofs .. "-" .. (ofs+len) .. ":" .. name jerome@65: end jerome@65: end jerome@65: p = p + 4096 jerome@65: end jerome@65: end jerome@65: local function parseRootData(data, ed) jerome@65: local ret, pos, dl = {}, 1, #data jerome@65: while pos < dl do jerome@65: local n, tflag, loc = uint32_le(data, pos-1), uint32_le(data, pos+3), uint32_le(data, pos+7) jerome@65: pos = pos + 12 + 4*n jerome@65: for i=1,n do jerome@65: local nhash = ssub(data, pos+16, pos+23) jerome@65: local t, j = ret[nhash] or {} jerome@65: ret[nhash], j = t, #t jerome@65: t[j+1], t[j+2], t[j+3], pos = ssub(data, pos,pos+15), tflag, loc, pos + 24 jerome@65: end jerome@65: end jerome@65: return ret jerome@65: end jerome@65: local function parseEncodingData(data) jerome@65: local h, locale, n = {}, data:sub(1,2), uint32_be(data, 9) jerome@65: local p2 = 22 + uint32_be(data, 18) + 32*n jerome@65: for i=1, n do jerome@65: local c = uint16_le(data, p2) jerome@65: while c > 0 do jerome@65: local chsh = ssub(data, p2+7, p2+22) jerome@65: h[chsh] = {size=uint32_be(data, p2+2)} jerome@65: p2 = p2 + 6+16 jerome@65: for i=1, c do jerome@65: h[chsh][i], p2 = ssub(data, p2+1, p2+16), p2 + 16 jerome@65: end jerome@65: c = uint16_le(data, p2) jerome@65: end jerome@65: p2 = data:match("()%z%Z", p2) jerome@65: end jerome@65: return h, locale jerome@65: end jerome@65: jerome@65: local function getFileByLoc(casc, loc, lcache) jerome@65: local cnt = lcache and readFile(lcache) jerome@65: if cnt then jerome@65: elseif type(loc) == "number" and casc.base then jerome@65: cnt = blte.readArchive(plat.path(casc.base, "data", ("data.%03d"):format(loc / 2^30)), loc % 2^30) jerome@65: elseif type(loc) == "string" and casc.cdnbase then jerome@65: local range, name = loc:match("(%d+%-%d+):(.+)") jerome@65: cnt = blte.readData(plat.http(plat.url(casc.cdnbase, "data", prefixHash(name or loc)), range and {Range="bytes=" .. range})) jerome@65: if lcache and cnt then jerome@65: local h = io.open(lcache, "wb") jerome@65: if h then jerome@65: h:write(cnt) jerome@65: h:close() jerome@65: end jerome@65: end jerome@65: end jerome@65: return cnt jerome@65: end jerome@65: local function getLocByFileKey(casc, fileKey) jerome@65: local key, i, i2 = fileKey:sub(1,9), casc.index, casc.indexCDN jerome@65: return i and i[key] or i2 and i2[key] jerome@65: end jerome@65: local function getFileByKey(casc, fileKey, lcache) jerome@65: return getFileByLoc(casc, getLocByFileKey(casc, #fileKey == 32 and to_bin(fileKey) or fileKey), lcache) jerome@65: end jerome@65: local function getLocFromRootEntry(casc, e, rateFunc) jerome@65: local score, ret = -math.huge jerome@65: for i=1,e and #e or 0, 3 do jerome@65: local keys = casc.encoding[e[i] or e] jerome@65: local loc = keys and keys[1] and getLocByFileKey(casc, keys[1]) jerome@65: if loc then jerome@65: local lscore = rateFunc(e[i+2], e[i+1], type(loc) == "string") jerome@65: if lscore and lscore > score then jerome@65: score, ret = lscore, loc jerome@65: end jerome@65: end jerome@65: end jerome@65: return ret jerome@65: end jerome@65: local function addCDNKey(casc, key) jerome@65: if casc.indexCDN then jerome@65: local hkey = #key == 32 and key or to_hex(key) jerome@65: local rkey = #key == 16 and key or to_bin(key) jerome@65: casc.indexCDN[rkey:sub(1,9)] = hkey jerome@65: end jerome@65: return key jerome@65: end jerome@65: jerome@65: local handle = {} jerome@65: local handle_mt = {__index=handle} jerome@65: function handle:readFile(path, lang) jerome@65: lang = M.locale[lang] or lang jerome@65: check_args("cascHandle:readFile", 1, path, "string", "string", lang, "function", "nil") jerome@65: jerome@65: local h = jenkins96.hash_path(path) jerome@65: return getFileByLoc(self, getLocFromRootEntry(self, self.root and self.root[h], lang or self.locale)) jerome@65: end jerome@65: function handle:readFileByEncodingHash(hash) jerome@65: check_args("cascHandle:readFileByEncodingHash", 1, hash, "string", "string") jerome@65: jerome@65: return getFileByKey(self, #hash == 32 and to_bin(hash) or hash) jerome@65: end jerome@65: function handle:readFileByContentHash(hash) jerome@65: check_args("cascHandle:readFileByContentHash", 1, hash, "string", "string") jerome@65: jerome@65: local k = self.encoding[#hash == 32 and to_bin(hash) or hash] jerome@65: return k and getFileByKey(self, k[1]) jerome@65: end jerome@65: function handle:setLocale(locale) jerome@65: check_args("cascHandle:setLocale", 1, M.locale[locale] or locale, "function", "nil") jerome@65: jerome@65: self.locale = M.locale[locale] or locale or defaultLocale jerome@65: end jerome@65: jerome@65: function M.open(localBase, buildKey, cdnBase, cdnKey, cacheBase) jerome@65: if buildKey == nil then error('Syntax: cac.open("localBase" or nil, "buildKey", "cdnBase" or nil, "cdnKey" or nil[, "cacheBase"])') end jerome@65: check_args("casc.open", 1, localBase, "string", "nil", buildKey, "string", nil, cdnBase, "string", "nil", cdnKey, "string", "nil", cacheBase, "string", "nil") jerome@65: assert(type(localBase) == "string" or type(cdnBase) == "string", 'invalid arguments: at least one of localBase, cdnBase must not be nil') jerome@65: assert(type(cdnBase) ~= "string" or type(cdnKey) == "string", 'invalid arguments: if cdnBase is specified, cdnKey must be a string') jerome@65: jerome@65: local casc = setmetatable({base=localBase, buildKey=buildKey, cdnbase=cdnBase, cdnKey=cdnKey, cachebase=cacheBase, locale=defaultLocale}, handle_mt) jerome@65: casc.conf = parseConfigData(cachedRead(casc, "config", prefixHash(buildKey))) jerome@65: if cdnKey then jerome@65: parseConfigData(cachedRead(casc, "config", prefixHash(cdnKey)), casc.conf) jerome@65: casc.indexCDN = {} jerome@65: for k,v in pairs(casc.conf.archives) do jerome@65: parseCDNIndexData(v, cachedRead3( jerome@65: plat.path(cacheBase, "index." .. v), jerome@65: plat.path(localBase, "indices", v .. ".index"), jerome@65: plat.url(cdnBase, "data", prefixHash(v .. ".index")) jerome@65: ), casc.indexCDN) jerome@65: end jerome@65: end jerome@65: if localBase then jerome@65: casc.index = {} jerome@65: for _, f in plat.files(plat.path(localBase, "data"), "*.idx") do jerome@65: parseLocalIndexData(assert(readFile(f)), casc.index) jerome@65: end jerome@65: end jerome@65: jerome@65: local ekey = addCDNKey(casc, casc.conf.encoding[#casc.conf.encoding]) jerome@65: casc.encoding = parseEncodingData(getFileByKey(casc, ekey, plat.path(cacheBase, "encoding." .. ekey))) jerome@65: local rkey = addCDNKey(casc, casc.encoding[to_bin(casc.conf.root[#casc.conf.root])][1]) jerome@65: casc.root = parseRootData(getFileByKey(casc, rkey, plat.path(cacheBase, "root." .. to_hex(rkey)))) jerome@65: jerome@65: return casc jerome@65: end jerome@65: jerome@65: function M.cdnbuild(patchBase, region) jerome@65: check_args("casc.cdnbuild", 1, patchBase, "string", "string", region, "string", "nil") jerome@65: jerome@65: local versions = parseInfoData(plat.http(plat.url(patchBase, "versions"))) jerome@65: local cdns = parseInfoData(plat.http(plat.url(patchBase, "cdns"))) jerome@65: local reginfo = {} jerome@65: for i=1,#versions do jerome@65: local v = versions[i] jerome@65: reginfo[v.Region] = {cdnKey = v.CDNConfig, buildKey=v.BuildConfig, build=v.BuildId, version=v.VersionsName} jerome@65: end jerome@65: for i=1,#cdns do jerome@65: local c = cdns[i] jerome@65: if reginfo[c.Name] then jerome@65: reginfo[c.Name].cdnBase = c.Hosts and c.Path and plat.url("http://", c.Hosts:match("%S+"), c.Path) jerome@65: end jerome@65: end jerome@65: if reginfo[region] and reginfo[region].cdnBase then jerome@65: local r = reginfo[region] jerome@65: return r.buildKey, r.cdnBase, r.cdnKey, r.version, r.build jerome@65: elseif region == nil then jerome@65: return reginfo, versions, cdns jerome@65: end jerome@65: end jerome@65: function M.localbuild(buildInfoPath, selectBuild) jerome@65: check_args("casc.localbuild", 1, buildInfoPath, "string", "string", selectBuild, "function", "nil") jerome@65: jerome@65: local info = parseInfoData(assert(readFile(buildInfoPath))) jerome@65: if type(selectBuild) == "function" and info then jerome@65: local ii = info[selectBuild(info)] jerome@65: if ii then jerome@65: return ii["Build Key"], plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]), ii["CDN Key"], ii["Version"] jerome@65: end jerome@65: elseif info then jerome@65: local branches = {} jerome@65: for i=1,#info do jerome@65: local ii = info[i] jerome@65: local curl = plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]) jerome@65: branches[ii.Branch] = {cdnKey=ii["CDN Key"], buildKey=ii["Build Key"], version=ii["Version"], cdnBase=curl} jerome@65: end jerome@65: return branches, info jerome@65: end jerome@65: end jerome@65: jerome@65: function M.selectActiveBuild(buildInfo) jerome@65: check_args("casc.selectActiveBuild", 1, buildInfo, "table", "table") jerome@65: jerome@65: for i=1,#buildInfo do jerome@65: if buildInfo[i].Active == 1 then jerome@65: return i jerome@65: end jerome@65: end jerome@65: end jerome@65: function M.selectUserBuild(buildInfo) jerome@65: check_args("casc.selectUserBuild", 1, buildInfo, "table", "table") jerome@65: jerome@65: print("Available builds:") jerome@65: for i=1,#buildInfo do jerome@65: 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 "")) jerome@65: end jerome@65: io.stdout:write("Enter index: ") jerome@65: io.stdout:flush() jerome@65: jerome@65: local c = tonumber(io.stdin:read("*l")) jerome@65: return buildInfo[c] and c jerome@65: end jerome@65: jerome@65: return M