-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathwowcig.lua
279 lines (263 loc) · 7.85 KB
/
wowcig.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
local locales = (function()
local t = {}
for k in require('pl.tablex').sort(require('casc').locale) do
table.insert(t, k)
end
return t
end)()
local args = (function()
local parser = require('argparse')()
parser:option('-c --cache', 'cache directory', 'cache')
parser:option('-d --db2', 'db2 to extract'):count('*')
parser:option('-e --extracts', 'extracts directory', 'extracts')
parser:option('-l --local', 'Use local WoW Directory instead of CDN.')
parser:option('-a --locale', 'locale to use', 'US'):count(1):choices(locales)
parser:option('-p --product', 'WoW product'):count(1):choices({
'wow',
'wowt',
'wowz',
'wow_beta',
'wow_classic',
'wow_classic_beta',
'wow_classic_era',
'wow_classic_era_ptr',
'wow_classic_ptr',
})
parser:flag('-b --blp', 'extract blp files')
parser:flag('-r --resolvetocdn', 'wowcig will use the CDN for data not available locally.')
parser:flag('-v --verbose', 'verbose printing')
parser:flag('-x --skip-framexml', 'skip framexml extraction')
parser:flag('-z --zip', 'write zip files instead of directory trees')
return parser:parse()
end)()
local dbds = require('luadbd').dbds
local path = require('path')
local function normalizePath(p)
-- path.normalize does not normalize x/../y to y.
-- Unfortunately, we need exactly that behavior for Interface_Vanilla etc.
-- links in per-product TOCs. We hack around it here by adding an extra dir.
return path.normalize('a/' .. p):sub(3)
end
path.mkdir(args.cache)
local function log(...)
if args.verbose then
print(...)
end
end
local encryptionKeys = (function()
local url = 'https://raw.githubusercontent.com/wowdev/TACTKeys/master/WoW.txt'
local wowtxt = require('ssl.https').request(url)
local ret = {}
for line in wowtxt:gmatch('[^\r\n]+') do
local k, v = line:match('^([0-9A-F]+) ([0-9A-F]+)')
ret[k:lower()] = v:lower()
end
return ret
end)()
local load, save, onexit, version = (function()
local casc = require('casc')
local handle, err, bkey, cdn, ckey, version
if args['local'] then
local bldInfoFile = path.join(args['local'], '.build.info')
local _, buildInfo = casc.localbuild(bldInfoFile)
for _, build in pairs(buildInfo) do
if build.Product == args.product then
version = build.Version
break
end
end
if not version then
if not args.resolvetocdn then
print('No local data for ' .. args.product .. ' in ' .. args['local'])
os.exit()
end
log('No local data for ' .. args.product .. ' in ' .. args['local'] .. '. Will attempt to use CDN.')
else
log('loading', version, args.product, args['local'])
local localConf = casc.conf(args['local'])
localConf.keys = encryptionKeys
handle, err = casc.open(localConf)
end
end
if not handle then
local url = 'http://us.patch.battle.net:1119/' .. args.product
bkey, cdn, ckey, version = casc.cdnbuild(url, 'us')
assert(bkey)
log('loading', version, url)
handle, err = casc.open({
bkey = bkey,
cdn = cdn,
ckey = ckey,
cache = args.cache,
cacheFiles = true,
keys = encryptionKeys,
locale = casc.locale[args.locale],
log = log,
zerofillEncryptedChunks = true,
})
end
if not handle then
print('unable to open ' .. args.product .. ': ' .. err)
os.exit()
end
local zipfile
if args.zip then
local z = require('brimworks.zip')
local filename = path.join(args.extracts, version .. '.zip')
path.remove(filename)
zipfile = assert(z.open(filename, z.OR(z.CREATE, z.EXCL)))
end
local fdids = {}
do
local dbd = dbds.manifestinterfacedata
local filedb = assert(handle:readFile(dbd.fdid))
local build = assert(dbd:build(version))
for row in build:rows(filedb) do
fdids[normalizePath(row.FilePath .. row.FileName)] = row.ID
end
end
local function load(f)
return handle:readFile(fdids[f] or f)
end
local function save(f, c)
if not c then
log('skipping', f)
else
log('writing ', f)
if zipfile then
local t = {}
if type(c) == 'function' then
c(function(s)
table.insert(t, s)
end)
else
table.insert(t, c)
end
local content = table.concat(t, '')
local fn = path.join(version, f)
local idx = zipfile:name_locate(fn)
if idx then
zipfile:replace(idx, 'string', content)
else
zipfile:add(fn, 'string', content)
end
else
local fn = path.join(args.extracts, version, f)
path.mkdir(path.dirname(fn))
local fd = assert(io.open(fn, 'w'))
if type(c) == 'function' then
c(function(s)
fd:write(s)
end)
else
fd:write(c)
end
fd:close()
end
end
end
local function onexit()
if zipfile then
zipfile:close()
require('pl.file').write(path.join(args.extracts, args.product .. '.txt'), version .. '\n')
else
require('lfs').link(version, path.join(args.extracts, args.product), true)
end
end
return load, save, onexit, version
end)()
local function joinRelative(relativeTo, suffix)
return normalizePath(path.join(path.dirname(relativeTo), suffix))
end
local processFile = (function()
local lxp = require('lxp')
local function doProcessFile(fn)
local content = load(fn)
save(fn, content)
if fn:sub(-4) == '.xml' then
local parser = lxp.new({
StartElement = function(_, name, attrs)
local lname = string.lower(name)
if (lname == 'include' or lname == 'script') and attrs.file then
doProcessFile(joinRelative(fn, attrs.file))
end
end,
})
parser:parse(content)
parser:close()
end
end
return doProcessFile
end)()
local function processToc(tocName)
local toc = load(tocName)
save(tocName, toc)
if toc then
for line in toc:gmatch('[^\r\n]+') do
if line:sub(1, 1) ~= '#' then
processFile(joinRelative(tocName, line:gsub('%s*$', '')))
end
end
end
end
local productSuffixes = {
'',
'_Vanilla',
'_TBC',
'_Wrath',
'_Mainline',
}
if not args.skip_framexml then
local function processAllProductFiles(addonDir)
assert(addonDir:sub(1, 10) == 'Interface/', addonDir)
local addonName = path.basename(addonDir)
for _, suffix in ipairs(productSuffixes) do
processToc(path.join(addonDir, addonName .. suffix .. '.toc'))
processFile(path.join('Interface' .. suffix, addonDir:sub(11), 'Bindings.xml'))
end
end
processAllProductFiles('Interface/FrameXML')
do
local dbd = dbds.manifestinterfacetocdata
local tocdb = assert(load(dbd.fdid))
local build = assert(dbd:build(version))
for dir in build:rows(tocdb) do
processAllProductFiles(normalizePath(dir.FilePath))
end
end
-- Generated API docs are not in beta manifests, unfortunately.
processAllProductFiles('Interface/AddOns/Blizzard_APIDocumentationGenerated')
end
local alldb2s = false
for _, db2 in ipairs(args.db2) do
alldb2s = alldb2s or string.lower(db2) == 'all'
end
if alldb2s then
for name, dbd in pairs(dbds) do
if dbd:build(version) then
save(('db2/%s.db2'):format(name), function(write)
write(assert(load(dbd.fdid)))
end)
end
end
else
for _, db2 in ipairs(args.db2) do
local name = string.lower(db2)
save(('db2/%s.db2'):format(name), function(write)
local dbd = assert(dbds[name], 'invalid dbd ' .. name)
local data = assert(load(dbd.fdid), 'error loading dbd ' .. name)
write(data)
end)
end
end
if args.blp then
local dbd = dbds.manifestinterfacedata
local tocdb = assert(load(dbd.fdid))
local build = assert(dbd:build(version))
for row in build:rows(tocdb) do
if row.FileName:sub(-4) == '.blp' then
save(joinRelative(row.FilePath, row.FileName), load(row.ID))
end
end
end
onexit()