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