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 |