Mercurial > wow > cyborg-mmo7
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 64:49ae7191821f | 65:8b8b0bade520 |
|---|---|
| 1 local M, plat, bin = {}, require("casc.platform"), require("casc.bin") | |
| 2 local jenkins96, blte = require("casc.jenkins96"), require("casc.blte") | |
| 3 | |
| 4 local uint32_le, uint32_be, uint40_be, uint16_le, to_be40, to_le32, to_bin, to_hex, ssub | |
| 5 = 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 | |
| 6 | |
| 7 M.locale = {} do | |
| 8 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 | |
| 9 local isCN, m = k == "cn", v * 2 | |
| 10 M.locale[k] = function(loc, tf) | |
| 11 return loc % m >= v and ((tf % 16 > 7) == isCN and 2 or 1) or nil | |
| 12 end | |
| 13 end | |
| 14 end | |
| 15 local function defaultLocale(loc, tf, cdn) | |
| 16 return (loc % 0x400 < 0x200 and 0 or 2) + (tf % 16 < 8 and 1 or 0) - (cdn and 4 or 0) | |
| 17 end | |
| 18 | |
| 19 local function check_args(name, idx, v, t1, t2, ...) | |
| 20 if t1 then | |
| 21 local tt = type(v) | |
| 22 if tt ~= t1 and tt ~= t2 then | |
| 23 error('Invalid argument #' .. idx .. " to " .. name .. ": " .. t1 .. " expected; got " .. tt, 3) | |
| 24 end | |
| 25 return check_args(name, idx+1, ...) | |
| 26 end | |
| 27 end | |
| 28 | |
| 29 local function readFile(...) | |
| 30 local path = plat.path(...) | |
| 31 if path then | |
| 32 local h, err = io.open(path, "rb") | |
| 33 if h then | |
| 34 local c = h:read("*a") | |
| 35 h:close() | |
| 36 return c | |
| 37 end | |
| 38 return h, err | |
| 39 end | |
| 40 end | |
| 41 local function readURL(...) | |
| 42 local url = plat.url(...) | |
| 43 if url then | |
| 44 return plat.http(url) | |
| 45 end | |
| 46 end | |
| 47 local function prefixHash(h) | |
| 48 local a, b, c = h:match("((%x%x)(%x%x).+)") | |
| 49 return b, c, a | |
| 50 end | |
| 51 local function cachedRead3(cpath, lpath, url) | |
| 52 local ret = cpath and readFile(cpath) or lpath and readFile(lpath) | |
| 53 if not ret and url then | |
| 54 ret = readURL(url) | |
| 55 if ret and cpath then | |
| 56 local h = io.open(cpath, "wb") | |
| 57 if h then | |
| 58 h:write(ret) | |
| 59 h:close() | |
| 60 end | |
| 61 end | |
| 62 end | |
| 63 return ret | |
| 64 end | |
| 65 local function cachedRead(casc, ...) | |
| 66 local ckey = plat.url(...) | |
| 67 ckey = ckey and casc.cachebase and plat.path(casc.cachebase, (ckey:gsub("[/\\]", "-"))) | |
| 68 return cachedRead3(ckey, plat.path(casc.base, ...), plat.url(casc.cdnbase, ...)) | |
| 69 end | |
| 70 | |
| 71 local function parseInfoData(data) | |
| 72 local hname, htype, hn, ret, ln, last = {}, {}, 1, {}, 1 | |
| 73 local i, s, line = data:gmatch("[^\n]+") | |
| 74 line = i(s, line) | |
| 75 for e in line:gmatch("[^|]+") do | |
| 76 hn, hname[hn], htype[hn] = hn + 1, e:match("^([^!]+)!([^:]+)") | |
| 77 end | |
| 78 | |
| 79 for e in i,s,line do | |
| 80 local l, ln = {}, 1 | |
| 81 for f in e:gmatch("|?([^|]*)") do | |
| 82 if f ~= "" then | |
| 83 l[hname[ln]] = htype[ln] == "DEC" and tonumber(f) or f | |
| 84 end | |
| 85 ln = ln + 1 | |
| 86 end | |
| 87 if ln > 1 then | |
| 88 ret[#ret+1] = l | |
| 89 end | |
| 90 end | |
| 91 | |
| 92 return ret | |
| 93 end | |
| 94 local function parseConfigData(data, into) | |
| 95 local ret, ln, last = type(into) == "table" and into or {}, 1 | |
| 96 for l in data:gmatch("[^\r\n]+") do | |
| 97 if l:match("^%s*[^#].-%S") then | |
| 98 local name, args = l:match("^%s*(%S+)%s*=(.*)$") | |
| 99 if name then | |
| 100 last, ln = {}, 1 | |
| 101 ret[name] = last | |
| 102 end | |
| 103 for s in (args or l):gmatch("%S+") do | |
| 104 last[ln], ln = s, ln + 1 | |
| 105 end | |
| 106 end | |
| 107 end | |
| 108 return ret | |
| 109 end | |
| 110 local function parseLocalIndexData(data, into) | |
| 111 local pos, sub, len = 8 + uint32_le(data), data.sub | |
| 112 pos = pos + ((16 - pos % 16) % 16) | |
| 113 len, pos = uint32_le(data, pos), pos + 9 | |
| 114 assert(len % 18 == 0, "Index data block length parity check") | |
| 115 | |
| 116 for i=1, len/18 do | |
| 117 into[sub(data, pos, pos + 8)], pos = uint40_be(data, pos+8), pos + 18 | |
| 118 end | |
| 119 | |
| 120 return len/18 | |
| 121 end | |
| 122 local function parseCDNIndexData(name, data, into) | |
| 123 local dlen, p = #data-28, 0 | |
| 124 for i=1, math.floor(dlen/4100) - math.floor(dlen/844600) do | |
| 125 for pos=p, p+4072, 24 do | |
| 126 local len = uint32_be(data, pos+16) | |
| 127 if len > 0 then | |
| 128 local ofs = uint32_be(data, pos+20) | |
| 129 into[ssub(data, pos+1, pos+9)] = ofs .. "-" .. (ofs+len) .. ":" .. name | |
| 130 end | |
| 131 end | |
| 132 p = p + 4096 | |
| 133 end | |
| 134 end | |
| 135 local function parseRootData(data, ed) | |
| 136 local ret, pos, dl = {}, 1, #data | |
| 137 while pos < dl do | |
| 138 local n, tflag, loc = uint32_le(data, pos-1), uint32_le(data, pos+3), uint32_le(data, pos+7) | |
| 139 pos = pos + 12 + 4*n | |
| 140 for i=1,n do | |
| 141 local nhash = ssub(data, pos+16, pos+23) | |
| 142 local t, j = ret[nhash] or {} | |
| 143 ret[nhash], j = t, #t | |
| 144 t[j+1], t[j+2], t[j+3], pos = ssub(data, pos,pos+15), tflag, loc, pos + 24 | |
| 145 end | |
| 146 end | |
| 147 return ret | |
| 148 end | |
| 149 local function parseEncodingData(data) | |
| 150 local h, locale, n = {}, data:sub(1,2), uint32_be(data, 9) | |
| 151 local p2 = 22 + uint32_be(data, 18) + 32*n | |
| 152 for i=1, n do | |
| 153 local c = uint16_le(data, p2) | |
| 154 while c > 0 do | |
| 155 local chsh = ssub(data, p2+7, p2+22) | |
| 156 h[chsh] = {size=uint32_be(data, p2+2)} | |
| 157 p2 = p2 + 6+16 | |
| 158 for i=1, c do | |
| 159 h[chsh][i], p2 = ssub(data, p2+1, p2+16), p2 + 16 | |
| 160 end | |
| 161 c = uint16_le(data, p2) | |
| 162 end | |
| 163 p2 = data:match("()%z%Z", p2) | |
| 164 end | |
| 165 return h, locale | |
| 166 end | |
| 167 | |
| 168 local function getFileByLoc(casc, loc, lcache) | |
| 169 local cnt = lcache and readFile(lcache) | |
| 170 if cnt then | |
| 171 elseif type(loc) == "number" and casc.base then | |
| 172 cnt = blte.readArchive(plat.path(casc.base, "data", ("data.%03d"):format(loc / 2^30)), loc % 2^30) | |
| 173 elseif type(loc) == "string" and casc.cdnbase then | |
| 174 local range, name = loc:match("(%d+%-%d+):(.+)") | |
| 175 cnt = blte.readData(plat.http(plat.url(casc.cdnbase, "data", prefixHash(name or loc)), range and {Range="bytes=" .. range})) | |
| 176 if lcache and cnt then | |
| 177 local h = io.open(lcache, "wb") | |
| 178 if h then | |
| 179 h:write(cnt) | |
| 180 h:close() | |
| 181 end | |
| 182 end | |
| 183 end | |
| 184 return cnt | |
| 185 end | |
| 186 local function getLocByFileKey(casc, fileKey) | |
| 187 local key, i, i2 = fileKey:sub(1,9), casc.index, casc.indexCDN | |
| 188 return i and i[key] or i2 and i2[key] | |
| 189 end | |
| 190 local function getFileByKey(casc, fileKey, lcache) | |
| 191 return getFileByLoc(casc, getLocByFileKey(casc, #fileKey == 32 and to_bin(fileKey) or fileKey), lcache) | |
| 192 end | |
| 193 local function getLocFromRootEntry(casc, e, rateFunc) | |
| 194 local score, ret = -math.huge | |
| 195 for i=1,e and #e or 0, 3 do | |
| 196 local keys = casc.encoding[e[i] or e] | |
| 197 local loc = keys and keys[1] and getLocByFileKey(casc, keys[1]) | |
| 198 if loc then | |
| 199 local lscore = rateFunc(e[i+2], e[i+1], type(loc) == "string") | |
| 200 if lscore and lscore > score then | |
| 201 score, ret = lscore, loc | |
| 202 end | |
| 203 end | |
| 204 end | |
| 205 return ret | |
| 206 end | |
| 207 local function addCDNKey(casc, key) | |
| 208 if casc.indexCDN then | |
| 209 local hkey = #key == 32 and key or to_hex(key) | |
| 210 local rkey = #key == 16 and key or to_bin(key) | |
| 211 casc.indexCDN[rkey:sub(1,9)] = hkey | |
| 212 end | |
| 213 return key | |
| 214 end | |
| 215 | |
| 216 local handle = {} | |
| 217 local handle_mt = {__index=handle} | |
| 218 function handle:readFile(path, lang) | |
| 219 lang = M.locale[lang] or lang | |
| 220 check_args("cascHandle:readFile", 1, path, "string", "string", lang, "function", "nil") | |
| 221 | |
| 222 local h = jenkins96.hash_path(path) | |
| 223 return getFileByLoc(self, getLocFromRootEntry(self, self.root and self.root[h], lang or self.locale)) | |
| 224 end | |
| 225 function handle:readFileByEncodingHash(hash) | |
| 226 check_args("cascHandle:readFileByEncodingHash", 1, hash, "string", "string") | |
| 227 | |
| 228 return getFileByKey(self, #hash == 32 and to_bin(hash) or hash) | |
| 229 end | |
| 230 function handle:readFileByContentHash(hash) | |
| 231 check_args("cascHandle:readFileByContentHash", 1, hash, "string", "string") | |
| 232 | |
| 233 local k = self.encoding[#hash == 32 and to_bin(hash) or hash] | |
| 234 return k and getFileByKey(self, k[1]) | |
| 235 end | |
| 236 function handle:setLocale(locale) | |
| 237 check_args("cascHandle:setLocale", 1, M.locale[locale] or locale, "function", "nil") | |
| 238 | |
| 239 self.locale = M.locale[locale] or locale or defaultLocale | |
| 240 end | |
| 241 | |
| 242 function M.open(localBase, buildKey, cdnBase, cdnKey, cacheBase) | |
| 243 if buildKey == nil then error('Syntax: cac.open("localBase" or nil, "buildKey", "cdnBase" or nil, "cdnKey" or nil[, "cacheBase"])') end | |
| 244 check_args("casc.open", 1, localBase, "string", "nil", buildKey, "string", nil, cdnBase, "string", "nil", cdnKey, "string", "nil", cacheBase, "string", "nil") | |
| 245 assert(type(localBase) == "string" or type(cdnBase) == "string", 'invalid arguments: at least one of localBase, cdnBase must not be nil') | |
| 246 assert(type(cdnBase) ~= "string" or type(cdnKey) == "string", 'invalid arguments: if cdnBase is specified, cdnKey must be a string') | |
| 247 | |
| 248 local casc = setmetatable({base=localBase, buildKey=buildKey, cdnbase=cdnBase, cdnKey=cdnKey, cachebase=cacheBase, locale=defaultLocale}, handle_mt) | |
| 249 casc.conf = parseConfigData(cachedRead(casc, "config", prefixHash(buildKey))) | |
| 250 if cdnKey then | |
| 251 parseConfigData(cachedRead(casc, "config", prefixHash(cdnKey)), casc.conf) | |
| 252 casc.indexCDN = {} | |
| 253 for k,v in pairs(casc.conf.archives) do | |
| 254 parseCDNIndexData(v, cachedRead3( | |
| 255 plat.path(cacheBase, "index." .. v), | |
| 256 plat.path(localBase, "indices", v .. ".index"), | |
| 257 plat.url(cdnBase, "data", prefixHash(v .. ".index")) | |
| 258 ), casc.indexCDN) | |
| 259 end | |
| 260 end | |
| 261 if localBase then | |
| 262 casc.index = {} | |
| 263 for _, f in plat.files(plat.path(localBase, "data"), "*.idx") do | |
| 264 parseLocalIndexData(assert(readFile(f)), casc.index) | |
| 265 end | |
| 266 end | |
| 267 | |
| 268 local ekey = addCDNKey(casc, casc.conf.encoding[#casc.conf.encoding]) | |
| 269 casc.encoding = parseEncodingData(getFileByKey(casc, ekey, plat.path(cacheBase, "encoding." .. ekey))) | |
| 270 local rkey = addCDNKey(casc, casc.encoding[to_bin(casc.conf.root[#casc.conf.root])][1]) | |
| 271 casc.root = parseRootData(getFileByKey(casc, rkey, plat.path(cacheBase, "root." .. to_hex(rkey)))) | |
| 272 | |
| 273 return casc | |
| 274 end | |
| 275 | |
| 276 function M.cdnbuild(patchBase, region) | |
| 277 check_args("casc.cdnbuild", 1, patchBase, "string", "string", region, "string", "nil") | |
| 278 | |
| 279 local versions = parseInfoData(plat.http(plat.url(patchBase, "versions"))) | |
| 280 local cdns = parseInfoData(plat.http(plat.url(patchBase, "cdns"))) | |
| 281 local reginfo = {} | |
| 282 for i=1,#versions do | |
| 283 local v = versions[i] | |
| 284 reginfo[v.Region] = {cdnKey = v.CDNConfig, buildKey=v.BuildConfig, build=v.BuildId, version=v.VersionsName} | |
| 285 end | |
| 286 for i=1,#cdns do | |
| 287 local c = cdns[i] | |
| 288 if reginfo[c.Name] then | |
| 289 reginfo[c.Name].cdnBase = c.Hosts and c.Path and plat.url("http://", c.Hosts:match("%S+"), c.Path) | |
| 290 end | |
| 291 end | |
| 292 if reginfo[region] and reginfo[region].cdnBase then | |
| 293 local r = reginfo[region] | |
| 294 return r.buildKey, r.cdnBase, r.cdnKey, r.version, r.build | |
| 295 elseif region == nil then | |
| 296 return reginfo, versions, cdns | |
| 297 end | |
| 298 end | |
| 299 function M.localbuild(buildInfoPath, selectBuild) | |
| 300 check_args("casc.localbuild", 1, buildInfoPath, "string", "string", selectBuild, "function", "nil") | |
| 301 | |
| 302 local info = parseInfoData(assert(readFile(buildInfoPath))) | |
| 303 if type(selectBuild) == "function" and info then | |
| 304 local ii = info[selectBuild(info)] | |
| 305 if ii then | |
| 306 return ii["Build Key"], plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]), ii["CDN Key"], ii["Version"] | |
| 307 end | |
| 308 elseif info then | |
| 309 local branches = {} | |
| 310 for i=1,#info do | |
| 311 local ii = info[i] | |
| 312 local curl = plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]) | |
| 313 branches[ii.Branch] = {cdnKey=ii["CDN Key"], buildKey=ii["Build Key"], version=ii["Version"], cdnBase=curl} | |
| 314 end | |
| 315 return branches, info | |
| 316 end | |
| 317 end | |
| 318 | |
| 319 function M.selectActiveBuild(buildInfo) | |
| 320 check_args("casc.selectActiveBuild", 1, buildInfo, "table", "table") | |
| 321 | |
| 322 for i=1,#buildInfo do | |
| 323 if buildInfo[i].Active == 1 then | |
| 324 return i | |
| 325 end | |
| 326 end | |
| 327 end | |
| 328 function M.selectUserBuild(buildInfo) | |
| 329 check_args("casc.selectUserBuild", 1, buildInfo, "table", "table") | |
| 330 | |
| 331 print("Available builds:") | |
| 332 for i=1,#buildInfo do | |
| 333 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 "")) | |
| 334 end | |
| 335 io.stdout:write("Enter index: ") | |
| 336 io.stdout:flush() | |
| 337 | |
| 338 local c = tonumber(io.stdin:read("*l")) | |
| 339 return buildInfo[c] and c | |
| 340 end | |
| 341 | |
| 342 return M |
