Mercurial > wow > cyborg-mmo7
changeset 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 | 49ae7191821f |
children | 06167f9ed2ac |
files | .pkgmeta CyborgMMO7.lua CyborgMMO7.toc MountMap.lua RatPageController.lua WowObjects.lua support/casc.lua support/casc/bin.lua support/casc/blte.lua support/casc/dbc.lua support/casc/jenkins96.lua support/casc/platform.lua support/gen-mount-db.lua |
diffstat | 13 files changed, 1374 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.pkgmeta Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,4 @@ +ignore: + - support + +# vi: ft=yaml ts=4 sts=4 sw=4 et
--- a/CyborgMMO7.lua Thu Oct 23 12:17:02 2014 +0100 +++ b/CyborgMMO7.lua Thu Oct 23 13:44:59 2014 +0100 @@ -331,6 +331,10 @@ if not CyborgMMO7SaveData then CyborgMMO7SaveData = {} end + -- cleanup the local mount cache + for mount in pairs(CyborgMMO_MountMap) do + CyborgMMO_LocalMountMap[mount] = nil + end PreLoad(CyborgMMO7SaveData) elseif event == "CYBORGMMO_ASYNC_DATA_LOADED" then AsyncDataLoaded = true
--- a/CyborgMMO7.toc Thu Oct 23 12:17:02 2014 +0100 +++ b/CyborgMMO7.toc Thu Oct 23 13:44:59 2014 +0100 @@ -4,8 +4,9 @@ ## Version: @project-version@ ## LoadOnDemand: 0 ## SavedVariablesPerCharacter: CyborgMMO7SaveData -## SavedVariables: CyborgMMO_ProfileKeyBindings +## SavedVariables: CyborgMMO_ProfileKeyBindings CyborgMMO_LocalMountMap +MountMap.lua Localisation.lua OptionView.lua CallbackFactory.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MountMap.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,501 @@ +-- this is a generated file, do not edit, see support/gen-mount-db.lua +CyborgMMO_MountMap = { + [6] = 458, + [7] = 459, + [8] = 468, + [9] = 470, + [11] = 472, + [12] = 578, + [13] = 579, + [14] = 580, + [15] = 581, + [17] = 5784, + [18] = 6648, + [19] = 6653, + [20] = 6654, + [21] = 6777, + [22] = 6896, + [24] = 6898, + [25] = 6899, + [26] = 8394, + [27] = 8395, + [28] = 8980, + [31] = 10789, + [34] = 10793, + [35] = 10795, + [36] = 10796, + [38] = 10799, + [39] = 10873, + [40] = 10969, + [41] = 13819, + [42] = 15779, + [43] = 15780, + [45] = 16055, + [46] = 16056, + [50] = 16080, + [51] = 16081, + [52] = 16082, + [53] = 16083, + [54] = 16084, + [55] = 17229, + [56] = 17450, + [57] = 17453, + [58] = 17454, + [62] = 17459, + [63] = 17460, + [64] = 17461, + [65] = 17462, + [66] = 17463, + [67] = 17464, + [68] = 17465, + [69] = 17481, + [70] = 18363, + [71] = 18989, + [72] = 18990, + [73] = 18991, + [74] = 18992, + [75] = 22717, + [76] = 22718, + [77] = 22719, + [78] = 22720, + [79] = 22721, + [80] = 22722, + [81] = 22723, + [82] = 22724, + [83] = 23161, + [84] = 23214, + [85] = 23219, + [87] = 23221, + [88] = 23222, + [89] = 23223, + [90] = 23225, + [91] = 23227, + [92] = 23228, + [93] = 23229, + [94] = 23238, + [95] = 23239, + [96] = 23240, + [97] = 23241, + [98] = 23242, + [99] = 23243, + [100] = 23246, + [101] = 23247, + [102] = 23248, + [103] = 23249, + [104] = 23250, + [105] = 23251, + [106] = 23252, + [107] = 23338, + [108] = 23509, + [109] = 23510, + [110] = 24242, + [111] = 24252, + [116] = 25863, + [117] = 25953, + [118] = 26054, + [119] = 26055, + [120] = 26056, + [121] = 26655, + [122] = 26656, + [123] = 28828, + [125] = 30174, + [129] = 32235, + [130] = 32239, + [131] = 32240, + [132] = 32242, + [133] = 32243, + [134] = 32244, + [135] = 32245, + [136] = 32246, + [137] = 32289, + [138] = 32290, + [139] = 32292, + [140] = 32295, + [141] = 32296, + [142] = 32297, + [145] = 33630, + [146] = 33660, + [147] = 34406, + [149] = 34767, + [150] = 34769, + [151] = 34790, + [152] = 34795, + [153] = 34896, + [154] = 34897, + [155] = 34898, + [156] = 34899, + [157] = 35018, + [158] = 35020, + [159] = 35022, + [160] = 35025, + [161] = 35027, + [162] = 35028, + [163] = 35710, + [164] = 35711, + [165] = 35712, + [166] = 35713, + [167] = 35714, + [168] = 36702, + [169] = 37015, + [170] = 39315, + [171] = 39316, + [172] = 39317, + [173] = 39318, + [174] = 39319, + [176] = 39798, + [177] = 39800, + [178] = 39801, + [179] = 39802, + [180] = 39803, + [183] = 40192, + [185] = 41252, + [186] = 41513, + [187] = 41514, + [188] = 41515, + [189] = 41516, + [190] = 41517, + [191] = 41518, + [196] = 42776, + [197] = 42777, + [199] = 43688, + [201] = 43899, + [202] = 43900, + [203] = 43927, + [204] = 44151, + [205] = 44153, + [206] = 44317, + [207] = 44744, + [211] = 46197, + [212] = 46199, + [213] = 46628, + [219] = 48025, + [220] = 48027, + [221] = 48778, + [222] = 48954, + [223] = 49193, + [224] = 49322, + [225] = 49378, + [226] = 49379, + [230] = 51412, + [236] = 54729, + [237] = 54753, + [238] = 55164, + [240] = 55531, + [241] = 58615, + [242] = 58819, + [243] = 58983, + [246] = 59567, + [247] = 59568, + [248] = 59569, + [249] = 59570, + [250] = 59571, + [251] = 59572, + [253] = 59650, + [254] = 59785, + [255] = 59788, + [256] = 59791, + [257] = 59793, + [258] = 59797, + [259] = 59799, + [262] = 59961, + [263] = 59976, + [264] = 59996, + [265] = 60002, + [266] = 60021, + [267] = 60024, + [268] = 60025, + [269] = 60114, + [270] = 60116, + [271] = 60118, + [272] = 60119, + [273] = 60136, + [274] = 60140, + [275] = 60424, + [276] = 61229, + [277] = 61230, + [278] = 61294, + [279] = 61309, + [280] = 61425, + [284] = 61447, + [285] = 61451, + [286] = 61465, + [287] = 61467, + [288] = 61469, + [289] = 61470, + [291] = 61996, + [292] = 61997, + [293] = 62048, + [294] = 63232, + [295] = 63635, + [296] = 63636, + [297] = 63637, + [298] = 63638, + [299] = 63639, + [300] = 63640, + [301] = 63641, + [302] = 63642, + [303] = 63643, + [304] = 63796, + [305] = 63844, + [306] = 63956, + [307] = 63963, + [308] = 64656, + [309] = 64657, + [310] = 64658, + [311] = 64659, + [312] = 64731, + [313] = 64927, + [314] = 64977, + [317] = 65439, + [318] = 65637, + [319] = 65638, + [320] = 65639, + [321] = 65640, + [322] = 65641, + [323] = 65642, + [324] = 65643, + [325] = 65644, + [326] = 65645, + [327] = 65646, + [328] = 65917, + [329] = 66087, + [330] = 66088, + [331] = 66090, + [332] = 66091, + [333] = 66122, + [334] = 66123, + [335] = 66124, + [336] = 66846, + [337] = 66847, + [338] = 66906, + [339] = 66907, + [340] = 67336, + [341] = 67466, + [342] = 68056, + [343] = 68057, + [344] = 68187, + [345] = 68188, + [349] = 69395, + [350] = 69820, + [351] = 69826, + [352] = 71342, + [358] = 71810, + [363] = 72286, + [364] = 72807, + [365] = 72808, + [366] = 73313, + [367] = 73629, + [368] = 73630, + [371] = 74856, + [372] = 74918, + [373] = 75207, + [375] = 75596, + [376] = 75614, + [382] = 75973, + [386] = 84751, + [388] = 87090, + [389] = 87091, + [391] = 88331, + [392] = 88335, + [393] = 88718, + [394] = 88741, + [395] = 88742, + [396] = 88744, + [397] = 88746, + [398] = 88748, + [399] = 88749, + [400] = 88750, + [401] = 88990, + [403] = 90621, + [404] = 92155, + [405] = 92231, + [406] = 92232, + [407] = 93326, + [408] = 93623, + [409] = 93644, + [410] = 96491, + [411] = 96499, + [412] = 96503, + [413] = 97359, + [415] = 97493, + [416] = 97501, + [417] = 97560, + [418] = 97581, + [419] = 98204, + [420] = 98718, + [421] = 98727, + [422] = 100332, + [423] = 100333, + [424] = 101282, + [425] = 101542, + [426] = 101573, + [428] = 101821, + [429] = 102346, + [430] = 102349, + [431] = 102350, + [432] = 102488, + [433] = 102514, + [434] = 103081, + [435] = 103195, + [436] = 103196, + [439] = 107203, + [440] = 107516, + [441] = 107517, + [442] = 107842, + [443] = 107844, + [444] = 107845, + [445] = 110039, + [446] = 110051, + [447] = 113120, + [448] = 113199, + [449] = 118089, + [450] = 118737, + [451] = 120043, + [452] = 120395, + [453] = 120822, + [455] = 121820, + [456] = 121836, + [457] = 121837, + [458] = 121838, + [459] = 121839, + [460] = 122708, + [462] = 123182, + [463] = 123886, + [464] = 123992, + [465] = 123993, + [466] = 124408, + [467] = 124550, + [468] = 124659, + [469] = 126507, + [470] = 126508, + [471] = 127154, + [472] = 127156, + [473] = 127158, + [474] = 127161, + [475] = 127164, + [476] = 127165, + [477] = 127169, + [478] = 127170, + [479] = 127174, + [480] = 127176, + [481] = 127177, + [484] = 127209, + [485] = 127213, + [486] = 127216, + [487] = 127220, + [488] = 127271, + [492] = 127286, + [493] = 127287, + [494] = 127288, + [495] = 127289, + [496] = 127290, + [497] = 127293, + [498] = 127295, + [499] = 127302, + [500] = 127308, + [501] = 127310, + [503] = 129552, + [504] = 129918, + [505] = 129932, + [506] = 129934, + [507] = 129935, + [508] = 130086, + [509] = 130092, + [510] = 130137, + [511] = 130138, + [515] = 130965, + [516] = 130985, + [517] = 132036, + [518] = 132117, + [519] = 132118, + [520] = 132119, + [521] = 133023, + [522] = 134359, + [523] = 134573, + [526] = 135416, + [527] = 135418, + [528] = 136163, + [529] = 136164, + [530] = 136400, + [531] = 136471, + [532] = 136505, + [533] = 138423, + [534] = 138424, + [535] = 138425, + [536] = 138426, + [537] = 138640, + [538] = 138641, + [539] = 138642, + [540] = 138643, + [541] = 139407, + [542] = 139442, + [543] = 139448, + [544] = 139595, + [545] = 140249, + [546] = 140250, + [547] = 142073, + [548] = 142266, + [549] = 142478, + [550] = 142641, + [551] = 142878, + [554] = 146615, + [555] = 146622, + [557] = 148392, + [558] = 148396, + [559] = 148417, + [560] = 148428, + [561] = 148476, + [562] = 148618, + [563] = 148619, + [564] = 148620, + [565] = 148626, + [566] = 148970, + [567] = 148972, + [568] = 149801, + [571] = 153489, + [593] = 163024, + [594] = 163025, + [600] = 155741, + [603] = 169952, + [606] = 170347, + [607] = 171436, + [609] = 171617, + [612] = 171620, + [613] = 171621, + [614] = 171622, + [615] = 171623, + [616] = 171624, + [617] = 171625, + [618] = 171626, + [619] = 171627, + [621] = 171629, + [623] = 171632, + [625] = 171634, + [626] = 171635, + [627] = 171636, + [628] = 171637, + [629] = 171638, + [630] = 171824, + [632] = 171825, + [634] = 171828, + [635] = 171829, + [636] = 171830, + [637] = 171831, + [638] = 171832, + [639] = 171833, + [640] = 171834, + [641] = 171835, + [642] = 171836, + [644] = 171838, + [645] = 171839, + [647] = 171841, + [648] = 171842, + [649] = 171843, + [650] = 171844, + [651] = 171845, + [654] = 171848, + [655] = 171849, + [657] = 171851, + [664] = 175700, +} +CyborgMMO_LocalMountMap = {}
--- a/RatPageController.lua Thu Oct 23 12:17:02 2014 +0100 +++ b/RatPageController.lua Thu Oct 23 13:44:59 2014 +0100 @@ -48,6 +48,36 @@ function RatPageController_methods:GetCursorObject() local type,a,b,c = GetCursorInfo() ClearCursor() + + -- special case for unknown mounts (do it here since we're sure the cursor is free) + if type=='mount' then + local mountID = a + -- if the mount is unknown + if mountID~=0xFFFFFFF and not CyborgMMO_MountMap[mountID] and not CyborgMMO_LocalMountMap[mountID] then + -- build a reverse index of known mount spells + local reverse = {} + for mount,spell in pairs(CyborgMMO_MountMap) do reverse[spell] = mount end + for mount,spell in pairs(CyborgMMO_LocalMountMap) do reverse[spell] = mount end + -- iterate over mount journal + for i=1,C_MountJournal.GetNumMounts() do + local _,spell = C_MountJournal.GetMountInfo(i) + -- if the mount has no known mount ID + if not reverse[spell] then + -- pickup the mount + C_MountJournal.Pickup(i) + -- get the mount id from the cursor + local _,mount = GetCursorInfo() + ClearCursor() + -- save that to avoid spamming the cursor too often + if mount then + CyborgMMO_LocalMountMap[mount] = spell + reverse[spell] = mount + end + end + end + end + end + if type=='item' then local id,link = a,b return CyborgMMO_CreateWowObject('item', id) @@ -65,6 +95,9 @@ elseif type=='battlepet' then local petID = a return CyborgMMO_CreateWowObject('battlepet', petID) + elseif type=='mount' then + local mountID = a + return CyborgMMO_CreateWowObject('mount', mountID) elseif type=='equipmentset' then local name = a return CyborgMMO_CreateWowObject('equipmentset', name)
--- a/WowObjects.lua Thu Oct 23 12:17:02 2014 +0100 +++ b/WowObjects.lua Thu Oct 23 13:44:59 2014 +0100 @@ -357,6 +357,68 @@ ------------------------------------------------------------------------------ +local function GetMountInfoEx(mountID) + -- special case for random mount + if mountID == 0xFFFFFFF then + return 0,"Interface/ICONS/ACHIEVEMENT_GUILDPERK_MOUNTUP" + end + + local spellID = CyborgMMO_MountMap[mountID] or CyborgMMO_LocalMountMap[mountID] + if not spellID then return nil,"not in database" end + + local mountIndex + for i=1,C_MountJournal.GetNumMounts() do + local _,spell,texture = C_MountJournal.GetMountInfo(i) -- :FIXME: this may fail too early in the session (like when loading saved data) + if spell==spellID then + return i,texture + end + end + + return nil,"not in journal" +end + +local WowMount_methods = setmetatable({}, {__index=WowObject_methods}) +local WowMount_mt = {__index=WowMount_methods} + +local function WowMount(mountID) + local mountIndex,texture = GetMountInfoEx(mountID) + if not mountIndex then + -- the mount might have been removed from the game + return nil + end + + local self = WowObject("mount", mountID) + CyborgMMO_DPrint("creating mount binding:", mountID, texture) + + self.mountID = mountID + self.texture = texture + + setmetatable(self, WowMount_mt) + + return self +end + +function WowMount_methods:DoAction() + local mountIndex = GetMountInfoEx(self.mountID) + if not mountIndex then return end + + C_MountJournal.Summon(mountIndex) +end + +function WowMount_methods:Pickup() + local mountIndex = GetMountInfoEx(self.mountID) + if not mountIndex then return end + + return C_MountJournal.Pickup(mountIndex) +end + +function WowMount_methods:SetBinding(key) + local buttonFrame,parentFrame,name = CyborgMMO_CallbackFactory:AddCallback(function() self:DoAction() end) + SetOverrideBindingClick(parentFrame, true, key, name, "LeftButton") +end + +------------------------------------------------------------------------------ + -- this class is used by pre-defined icons in the corner of the Rat page CyborgMMO_CallbackIcons = { new = function(self) @@ -416,6 +478,8 @@ object = WowEquipmentSet(...) elseif type == "battlepet" then object = WowBattlePet(...) + elseif type == "mount" then + object = WowMount(...) elseif type == "callback" then object = WowCallback(...) else
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,342 @@ +local M, plat, bin = {}, require("casc.platform"), require("casc.bin") +local jenkins96, blte = require("casc.jenkins96"), require("casc.blte") + +local uint32_le, uint32_be, uint40_be, uint16_le, to_be40, to_le32, to_bin, to_hex, ssub + = 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 + +M.locale = {} do + 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 + local isCN, m = k == "cn", v * 2 + M.locale[k] = function(loc, tf) + return loc % m >= v and ((tf % 16 > 7) == isCN and 2 or 1) or nil + end + end +end +local function defaultLocale(loc, tf, cdn) + return (loc % 0x400 < 0x200 and 0 or 2) + (tf % 16 < 8 and 1 or 0) - (cdn and 4 or 0) +end + +local function check_args(name, idx, v, t1, t2, ...) + if t1 then + local tt = type(v) + if tt ~= t1 and tt ~= t2 then + error('Invalid argument #' .. idx .. " to " .. name .. ": " .. t1 .. " expected; got " .. tt, 3) + end + return check_args(name, idx+1, ...) + end +end + +local function readFile(...) + local path = plat.path(...) + if path then + local h, err = io.open(path, "rb") + if h then + local c = h:read("*a") + h:close() + return c + end + return h, err + end +end +local function readURL(...) + local url = plat.url(...) + if url then + return plat.http(url) + end +end +local function prefixHash(h) + local a, b, c = h:match("((%x%x)(%x%x).+)") + return b, c, a +end +local function cachedRead3(cpath, lpath, url) + local ret = cpath and readFile(cpath) or lpath and readFile(lpath) + if not ret and url then + ret = readURL(url) + if ret and cpath then + local h = io.open(cpath, "wb") + if h then + h:write(ret) + h:close() + end + end + end + return ret +end +local function cachedRead(casc, ...) + local ckey = plat.url(...) + ckey = ckey and casc.cachebase and plat.path(casc.cachebase, (ckey:gsub("[/\\]", "-"))) + return cachedRead3(ckey, plat.path(casc.base, ...), plat.url(casc.cdnbase, ...)) +end + +local function parseInfoData(data) + local hname, htype, hn, ret, ln, last = {}, {}, 1, {}, 1 + local i, s, line = data:gmatch("[^\n]+") + line = i(s, line) + for e in line:gmatch("[^|]+") do + hn, hname[hn], htype[hn] = hn + 1, e:match("^([^!]+)!([^:]+)") + end + + for e in i,s,line do + local l, ln = {}, 1 + for f in e:gmatch("|?([^|]*)") do + if f ~= "" then + l[hname[ln]] = htype[ln] == "DEC" and tonumber(f) or f + end + ln = ln + 1 + end + if ln > 1 then + ret[#ret+1] = l + end + end + + return ret +end +local function parseConfigData(data, into) + local ret, ln, last = type(into) == "table" and into or {}, 1 + for l in data:gmatch("[^\r\n]+") do + if l:match("^%s*[^#].-%S") then + local name, args = l:match("^%s*(%S+)%s*=(.*)$") + if name then + last, ln = {}, 1 + ret[name] = last + end + for s in (args or l):gmatch("%S+") do + last[ln], ln = s, ln + 1 + end + end + end + return ret +end +local function parseLocalIndexData(data, into) + local pos, sub, len = 8 + uint32_le(data), data.sub + pos = pos + ((16 - pos % 16) % 16) + len, pos = uint32_le(data, pos), pos + 9 + assert(len % 18 == 0, "Index data block length parity check") + + for i=1, len/18 do + into[sub(data, pos, pos + 8)], pos = uint40_be(data, pos+8), pos + 18 + end + + return len/18 +end +local function parseCDNIndexData(name, data, into) + local dlen, p = #data-28, 0 + for i=1, math.floor(dlen/4100) - math.floor(dlen/844600) do + for pos=p, p+4072, 24 do + local len = uint32_be(data, pos+16) + if len > 0 then + local ofs = uint32_be(data, pos+20) + into[ssub(data, pos+1, pos+9)] = ofs .. "-" .. (ofs+len) .. ":" .. name + end + end + p = p + 4096 + end +end +local function parseRootData(data, ed) + local ret, pos, dl = {}, 1, #data + while pos < dl do + local n, tflag, loc = uint32_le(data, pos-1), uint32_le(data, pos+3), uint32_le(data, pos+7) + pos = pos + 12 + 4*n + for i=1,n do + local nhash = ssub(data, pos+16, pos+23) + local t, j = ret[nhash] or {} + ret[nhash], j = t, #t + t[j+1], t[j+2], t[j+3], pos = ssub(data, pos,pos+15), tflag, loc, pos + 24 + end + end + return ret +end +local function parseEncodingData(data) + local h, locale, n = {}, data:sub(1,2), uint32_be(data, 9) + local p2 = 22 + uint32_be(data, 18) + 32*n + for i=1, n do + local c = uint16_le(data, p2) + while c > 0 do + local chsh = ssub(data, p2+7, p2+22) + h[chsh] = {size=uint32_be(data, p2+2)} + p2 = p2 + 6+16 + for i=1, c do + h[chsh][i], p2 = ssub(data, p2+1, p2+16), p2 + 16 + end + c = uint16_le(data, p2) + end + p2 = data:match("()%z%Z", p2) + end + return h, locale +end + +local function getFileByLoc(casc, loc, lcache) + local cnt = lcache and readFile(lcache) + if cnt then + elseif type(loc) == "number" and casc.base then + cnt = blte.readArchive(plat.path(casc.base, "data", ("data.%03d"):format(loc / 2^30)), loc % 2^30) + elseif type(loc) == "string" and casc.cdnbase then + local range, name = loc:match("(%d+%-%d+):(.+)") + cnt = blte.readData(plat.http(plat.url(casc.cdnbase, "data", prefixHash(name or loc)), range and {Range="bytes=" .. range})) + if lcache and cnt then + local h = io.open(lcache, "wb") + if h then + h:write(cnt) + h:close() + end + end + end + return cnt +end +local function getLocByFileKey(casc, fileKey) + local key, i, i2 = fileKey:sub(1,9), casc.index, casc.indexCDN + return i and i[key] or i2 and i2[key] +end +local function getFileByKey(casc, fileKey, lcache) + return getFileByLoc(casc, getLocByFileKey(casc, #fileKey == 32 and to_bin(fileKey) or fileKey), lcache) +end +local function getLocFromRootEntry(casc, e, rateFunc) + local score, ret = -math.huge + for i=1,e and #e or 0, 3 do + local keys = casc.encoding[e[i] or e] + local loc = keys and keys[1] and getLocByFileKey(casc, keys[1]) + if loc then + local lscore = rateFunc(e[i+2], e[i+1], type(loc) == "string") + if lscore and lscore > score then + score, ret = lscore, loc + end + end + end + return ret +end +local function addCDNKey(casc, key) + if casc.indexCDN then + local hkey = #key == 32 and key or to_hex(key) + local rkey = #key == 16 and key or to_bin(key) + casc.indexCDN[rkey:sub(1,9)] = hkey + end + return key +end + +local handle = {} +local handle_mt = {__index=handle} +function handle:readFile(path, lang) + lang = M.locale[lang] or lang + check_args("cascHandle:readFile", 1, path, "string", "string", lang, "function", "nil") + + local h = jenkins96.hash_path(path) + return getFileByLoc(self, getLocFromRootEntry(self, self.root and self.root[h], lang or self.locale)) +end +function handle:readFileByEncodingHash(hash) + check_args("cascHandle:readFileByEncodingHash", 1, hash, "string", "string") + + return getFileByKey(self, #hash == 32 and to_bin(hash) or hash) +end +function handle:readFileByContentHash(hash) + check_args("cascHandle:readFileByContentHash", 1, hash, "string", "string") + + local k = self.encoding[#hash == 32 and to_bin(hash) or hash] + return k and getFileByKey(self, k[1]) +end +function handle:setLocale(locale) + check_args("cascHandle:setLocale", 1, M.locale[locale] or locale, "function", "nil") + + self.locale = M.locale[locale] or locale or defaultLocale +end + +function M.open(localBase, buildKey, cdnBase, cdnKey, cacheBase) + if buildKey == nil then error('Syntax: cac.open("localBase" or nil, "buildKey", "cdnBase" or nil, "cdnKey" or nil[, "cacheBase"])') end + check_args("casc.open", 1, localBase, "string", "nil", buildKey, "string", nil, cdnBase, "string", "nil", cdnKey, "string", "nil", cacheBase, "string", "nil") + assert(type(localBase) == "string" or type(cdnBase) == "string", 'invalid arguments: at least one of localBase, cdnBase must not be nil') + assert(type(cdnBase) ~= "string" or type(cdnKey) == "string", 'invalid arguments: if cdnBase is specified, cdnKey must be a string') + + local casc = setmetatable({base=localBase, buildKey=buildKey, cdnbase=cdnBase, cdnKey=cdnKey, cachebase=cacheBase, locale=defaultLocale}, handle_mt) + casc.conf = parseConfigData(cachedRead(casc, "config", prefixHash(buildKey))) + if cdnKey then + parseConfigData(cachedRead(casc, "config", prefixHash(cdnKey)), casc.conf) + casc.indexCDN = {} + for k,v in pairs(casc.conf.archives) do + parseCDNIndexData(v, cachedRead3( + plat.path(cacheBase, "index." .. v), + plat.path(localBase, "indices", v .. ".index"), + plat.url(cdnBase, "data", prefixHash(v .. ".index")) + ), casc.indexCDN) + end + end + if localBase then + casc.index = {} + for _, f in plat.files(plat.path(localBase, "data"), "*.idx") do + parseLocalIndexData(assert(readFile(f)), casc.index) + end + end + + local ekey = addCDNKey(casc, casc.conf.encoding[#casc.conf.encoding]) + casc.encoding = parseEncodingData(getFileByKey(casc, ekey, plat.path(cacheBase, "encoding." .. ekey))) + local rkey = addCDNKey(casc, casc.encoding[to_bin(casc.conf.root[#casc.conf.root])][1]) + casc.root = parseRootData(getFileByKey(casc, rkey, plat.path(cacheBase, "root." .. to_hex(rkey)))) + + return casc +end + +function M.cdnbuild(patchBase, region) + check_args("casc.cdnbuild", 1, patchBase, "string", "string", region, "string", "nil") + + local versions = parseInfoData(plat.http(plat.url(patchBase, "versions"))) + local cdns = parseInfoData(plat.http(plat.url(patchBase, "cdns"))) + local reginfo = {} + for i=1,#versions do + local v = versions[i] + reginfo[v.Region] = {cdnKey = v.CDNConfig, buildKey=v.BuildConfig, build=v.BuildId, version=v.VersionsName} + end + for i=1,#cdns do + local c = cdns[i] + if reginfo[c.Name] then + reginfo[c.Name].cdnBase = c.Hosts and c.Path and plat.url("http://", c.Hosts:match("%S+"), c.Path) + end + end + if reginfo[region] and reginfo[region].cdnBase then + local r = reginfo[region] + return r.buildKey, r.cdnBase, r.cdnKey, r.version, r.build + elseif region == nil then + return reginfo, versions, cdns + end +end +function M.localbuild(buildInfoPath, selectBuild) + check_args("casc.localbuild", 1, buildInfoPath, "string", "string", selectBuild, "function", "nil") + + local info = parseInfoData(assert(readFile(buildInfoPath))) + if type(selectBuild) == "function" and info then + local ii = info[selectBuild(info)] + if ii then + return ii["Build Key"], plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]), ii["CDN Key"], ii["Version"] + end + elseif info then + local branches = {} + for i=1,#info do + local ii = info[i] + local curl = plat.url("http://", ii["CDN Hosts"]:match("%S+"), ii["CDN Path"]) + branches[ii.Branch] = {cdnKey=ii["CDN Key"], buildKey=ii["Build Key"], version=ii["Version"], cdnBase=curl} + end + return branches, info + end +end + +function M.selectActiveBuild(buildInfo) + check_args("casc.selectActiveBuild", 1, buildInfo, "table", "table") + + for i=1,#buildInfo do + if buildInfo[i].Active == 1 then + return i + end + end +end +function M.selectUserBuild(buildInfo) + check_args("casc.selectUserBuild", 1, buildInfo, "table", "table") + + print("Available builds:") + for i=1,#buildInfo do + 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 "")) + end + io.stdout:write("Enter index: ") + io.stdout:flush() + + local c = tonumber(io.stdin:read("*l")) + return buildInfo[c] and c +end + +return M \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc/bin.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,55 @@ +local M, sbyte, schar, sgsub, sformat = {}, string.byte, string.char, string.gsub, string.format +local inf, nan = math.huge, math.huge-math.huge + +local hexbin = {} for i=0,255 do + local h, b = sformat("%02x", i), schar(i) + hexbin[sformat("%02X", i)], hexbin[h], hexbin[b] = b, b, h +end + +function M.uint32_le(s, pos) + local d, c, b, a = sbyte(s, (pos or 0)+1, (pos or 0) + 4) + return a*256^3 + b*256^2 + c*256 + d +end +function M.uint16_le(s, pos) + local b,a = sbyte(s, (pos or 0)+1, (pos or 0) + 2) + return a*256 + b +end +function M.uint32_be(s, pos) + local a,b,c,d = sbyte(s, (pos or 0)+1, (pos or 0) + 4) + return a*256^3 + b*256^2 + c*256 + d +end +function M.uint40_be(s, pos) + local a, b, c, d, e = sbyte(s, (pos or 0)+1, (pos or 0) + 5) + return a*256^4 + b*256^3 + c*256^2 + d*256 + e +end +function M.float32_le(s, pos) + local a, b, c, d = sbyte(s, (pos or 0) + 1, (pos or 0) + 4) + local s, e, f = d > 127 and -1 or 1, (d % 128)*2 + (c > 127 and 1 or 0), a + b*256 + (c % 128)*256^2 + if e > 0 and e < 255 then + return s * (1+f/2^23) * 2^(e-127) + else + return e == 0 and (s * f/2^23 * 2^-126) or f == 0 and (s * inf) or nan + end +end +function M.int32_le(s, pos) + local d, c, b, a = sbyte(s, (pos or 0)+1, (pos or 0) + 4) + return a*256^3 + b*256^2 + c*256 + d - (a < 128 and 0 or 2^32) +end + +function M.to_le32(n) + local n = n % 2^32 + return schar(n % 256, (n / 256) % 256, (n / 256^2) % 256, (n / 256^3) % 256) +end +function M.to_be40(n) + local n = n % 2^40 + return schar((n / 256^4) % 256, (n / 256^3) % 256, (n / 256^2) % 256, (n / 256) % 256, n % 256) +end + +function M.to_bin(hs) + return hs and sgsub(hs, "%x%x", hexbin) +end +function M.to_hex(bs) + return bs and sgsub(bs, ".", hexbin) +end + +return M \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc/blte.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,64 @@ +local M, plat, bin = {}, require("casc.platform"), require("casc.bin") +local open, tconcat, assert, error = io.open, table.concat, assert, error +local uint32_le, uint32_be = bin.uint32_le, bin.uint32_be + +local string_cursor do + local function read(self, n) + local p = self.pos + self.pos = p + n + return self.str:sub(p, p+n-1) + end + function string_cursor(s) + return {str=s, read=read, pos=1} + end +end + +local function decodeChunk(chunk) + local format = chunk:sub(1,1) + if format == 'N' then + return chunk:sub(2) + elseif format == 'Z' then + return plat.decompress(chunk:sub(2)) + else + error('Unknown chunk format: ' .. tostring(format)) + end +end +local function parseBLTE(h, dataSize) + local header = h:read(8) + assert(header:sub(1,4) == 'BLTE', 'BLTE file magic signature') + local ofs = uint32_be(header, 4) + + local chunks, ret = ofs > 0 and {} + if ofs > 0 then + local n = uint32_be(h:read(4)) % 2^16 + local buf, p = h:read(n*24), 0 + for i=1, n do + chunks[i], p = uint32_be(buf, p), p + 24 + end + for i=1, #chunks do + chunks[i] = decodeChunk(h:read(chunks[i])) + end + ret = tconcat(chunks, "") + else + ret = decodeChunk(h:read(dataSize)) + end + return ret +end + +function M.readArchive(path, offset) + assert(type(path) == "string" and type(offset) == "number", 'Syntax: "content" = casc.blte.readArchive("path", offset)') + + local h = open(path, "rb") + h:seek("set", offset) + local blockHead = h:read(30) + local ret = parseBLTE(h, uint32_le(blockHead, 16)-30) + h:close() + + return ret +end +function M.readData(str) + assert(type(str) == "string", 'Syntax: "content" = casc.blte.readContent("str")') + return parseBLTE(string_cursor(str), #str) +end + +return M \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc/dbc.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,85 @@ +local M, bin = {}, require("casc.bin") +local assert, loadstring, smatch = assert, loadstring or load, string.match + +local uint32_le, int32_le, float32_le = bin.uint32_le, bin.int32_le, bin.float32_le + +local function unpacker(data, format, rows, stride, hsize, sbase, tfunc) + tfunc = type(tfunc) == "function" and tfunc or nil + + local skip, p, pe = 0, [=[-- casc.dbc:iterator + local smatch, uint32_le, int32_le, float32_le, tfunc, data, rows, stride, sbase, rpos, i = ... + return function() + if i < rows then + rpos, i = rpos + stride, i + 1 + return ]=] .. (tfunc and "tfunc(i" or "i"), (tfunc and ")" or "") .. '\nend\nend' + + for r, t in format:gmatch("(%d*)(.)") do + r = tonumber(r) or 1 + for i=1,r do + if t == '.' then + skip = skip + 4 * r + break + elseif t == 'u' then + p, skip = p .. ', uint32_le(data, rpos+' .. skip .. ')', skip + 4 + elseif t == 'i' then + p, skip = p .. ', int32_le(data, rpos+' .. skip .. ')', skip + 4 + elseif t == 'f' then + p, skip = p .. ', float32_le(data, rpos+' .. skip .. ')', skip + 4 + elseif t == 's' then + assert(sbase, "invalid signature: 's' requires a string block") + p, skip = p .. ', smatch(data, "%Z*", sbase + uint32_le(data,rpos+' .. skip .. '))', skip + 4 + else + error('Unknown signature field type "' .. t .. '"') + end + end + end + + return loadstring(p .. pe)(smatch, uint32_le, int32_le, float32_le, + tfunc, data, rows, stride, sbase, hsize - stride, 0), skip +end + +local header do + local function dbc(data) + assert(data:sub(1,4) == "WDBC", "DBC magic signature") + local rows, fields, stride, stringSize = uint32_le(data, 4), uint32_le(data, 8), uint32_le(data, 12), uint32_le(data, 16) + assert(20 + rows*stride + stringSize <= #data, "Data too short") + + return rows, fields, stride, 20, 21 + rows * stride + end + local function db2(data) + local hsize = 48 + local rows, fields, stride, stringSize = uint32_le(data, 4), uint32_le(data, 8), uint32_le(data, 12), uint32_le(data, 16) + local build, minId, maxId, locale, rid = uint32_le(data, 24), uint32_le(data, 32), uint32_le(data, 36), uint32_le(data, 40) + + if maxId > 0 then + local n, p = maxId-minId + 1, hsize + rid, hsize = {}, hsize + 6 * n + for i=1,n do + rid[i], p = uint32_le(data, p), p + 6 + end + end + assert(hsize + rows*stride + stringSize <= #data, "Data too short") + + return rows, fields, stride, hsize, hsize + 1 + rows * stride, rid, minId, maxId, build, locale + end + header = {WDBC=dbc, WDB2=db2, WCH2=db2} +end + +function M.header(data) + assert(type(data) == "string", 'Syntax: casc.dbc.header("data")') + local fourCC = data:sub(1,4) + return assert(header[fourCC], "Unsupported format")(data) +end + +function M.rows(data, sig, loose) + assert(type(data) == "string" and type(sig) == "string", 'Syntax: casc.dbc.rows("data", "rowSignature"[, loose])') + + local rows, _, stride, hsize, sbase, rid = M.header(data) + local iter, skip = unpacker(data, sig, rows, stride, hsize, sbase, rid and function(i, ...) return rid[i], ... end) + assert(skip <= stride, 'signature exceeds stride') + assert(loose or skip == stride, 'signature/stride mismatch') + + return iter +end + +return M
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc/jenkins96.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,38 @@ +local M, plat, bin = {}, require("casc.platform"), require("casc.bin") + +local rot, xor, uint32_le, to_le32 = plat.rol, plat.bxor, bin.uint32_le, bin.to_le32 + +function M.hash(k) + assert(type(k) == "string", 'Syntax: casc.jenkins96.hash("key")') + + if #k == 0 then return 0xdeadbeef, 0xdeadbeef end + local a = 0xdeadbeef + #k + local b, c, k = a, a, k .. (k ~= "" and ("\0"):rep((12 - #k % 12) % 12) or "") + for i=0, #k-13, 12 do + a, b, c = a + uint32_le(k, i), b + uint32_le(k, i+4), c + uint32_le(k, i+8) + a = xor(a-c, rot(c, 4)); c = c + b + b = xor(b-a, rot(a, 6)); a = a + c + c = xor(c-b, rot(b, 8)); b = b + a + a = xor(a-c, rot(c,16)); c = c + b + b = xor(b-a, rot(a,19)); a = a + c + c = xor(c-b, rot(b, 4)); b = b + a + end + local i = #k - 12 + a, b, c = a + uint32_le(k, i), b + uint32_le(k, i+4), c + uint32_le(k, i+8) + c = xor(c, b) - rot(b,14) + a = xor(a, c) - rot(c,11) + b = xor(b, a) - rot(a,25) + c = xor(c, b) - rot(b,16) + a = xor(a, c) - rot(c,04) + b = xor(b, a) - rot(a,14) + c = xor(c, b) - rot(b,24) + return c, b +end + +function M.hash_path(path) + assert(type(path) == "string", 'Syntax: casc.jenkins96.hash_path("path")') + local c, b = M.hash((path:upper():gsub('/', '\\'))) + return to_le32(b) .. to_le32(c) +end + +return M \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/casc/platform.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,148 @@ +local M, assert, getenv = {}, assert, os.getenv + +local function maybe(m) + local ok, v = pcall(require, m) + return ok and v +end +local lfs = maybe("lfs") -- LuaFileSystem; http://keplerproject.github.io/luafilesystem/ +local zlib = maybe("zlib") -- lzlib; https://github.com/LuaDist/lzlib +local bit = maybe("bit") -- Lua BitOp; http://bitop.luajit.org +local socket = maybe("socket.http") -- LuaSocket; http://w3.impa.br/~diego/software/luasocket/home.html + +local function shellEscape(s) + return '"' .. s:gsub('"', '"\\\\""') .. '"' +end +local function readAndDeleteFile(path) + local h, err = io.open(path, "rb") + if h then + local c = h:read("*a") + h:close() + h, err = c, nil + end + os.remove(path) + return h, err +end + +local dir_sep = package and package.config and package.config:sub(1,1) or "/" +do -- M.path(a, b, ...) + M.path = function(a, b, ...) + if a and b then + return M.path(a .. (a:sub(-1) ~= dir_sep and dir_sep or "") .. b, ...) + end + return a + end +end +M.url = function(a, b, ...) + if a and b then + return M.url(a .. ((a:sub(-1) == "/" or b:sub(1,1) == "/") and "" or "/") .. b, ...) + end + return a +end + +M.commands = + dir_sep == '/' and {toDevNull=' 2>/dev/null', ls='ls %s', mkdir='mkdir -p %s', gzip='gzip -dcq %s'} or + dir_sep == '\\' and {toDevNull=' 2>NUL', ls='(for %%a in (%s) do @echo %%~fa)', mkdir='mkdir %s', gzip='gzip -dcq %s', TMP=os.getenv('TMP') or os.getenv('TEMP')} + +M.tmpname = function() + local tn = os.tmpname() + return (M.commands and M.commands.TMP or "") .. tn +end + +M.decompress = zlib and zlib.decompress or function(compressed) + assert(type(compressed) == "string", 'Syntax: casc.platform.decompress("compressed")') + assert(M.commands and M.commands.gzip and M.commands.toDevNull, 'unsupported platform') + + local f, f2 = M.tmpname(), M.tmpname() + local h = io.open(f, "wb") + h:write('\31\139\8\0\0\0\0\0') + h:write(compressed) + h:close() + + os.execute(M.commands.gzip:format(shellEscape(f)) .. " 1>" .. f2 .. " " .. M.commands.toDevNull) + os.remove(f) + + return readAndDeleteFile(f2) +end + +M.mkdir = lfs and lfs.mkdir or function(path) + assert(type(path) == 'string', 'Syntax: casc.platform.mkdir("path")') + assert(M.commands and M.commands.mkdir, 'unsupported platform') + + return os.execute(M.commands.mkdir:format(shellEscape(path))) +end + +M.files = lfs and function(dir, glob) + assert(type(dir) == "string" and type(glob) == 'string', 'Syntax: casc.platform.files("dir", "glob")') + local pat = "^" .. glob:gsub("%.%-%+", "%%%0"):gsub("%*", ".*") .. "$" + local t, ni = {}, 1 + for f in lfs.dir(dir) do + if f ~= "." and f ~= ".." and f:match(pat) then + t[ni], ni = M.path(dir, f), ni + 1 + end + end + return pairs(t) +end or function(dir, glob) + assert(type(dir) == "string" and type(glob) == 'string', 'Syntax: casc.platform.files("dir", "glob")') + assert(M.commands and M.commands.ls, 'unsupported platform') + + local dir, files = glob:match("^(.-)([^" .. dir_sep .. "]+)$") + local t, ni, h = {}, 1, io.popen(M.commands.ls:format(shellEscape(dir) .. files), "r") + for l in h:lines() do + t[ni], ni = l, ni + 1 + end + h:close() + return pairs(t) +end + +local floor = math.floor +M.rol = bit and bit.rol or function(n, b) + local n, e2 = n % 2^32, 2^(32-b) + return ((n % e2) * 2^b + floor(n/e2)) % 2^32 +end + +M.bxor = bit and bit.bxor or function(a, b) + local out, m, lm = 0, 1 + for i=1,32 do + m, lm = m+m, m + local am, bm = a % m, b % m + out, a, b = out + (am == bm and 0 or lm), a-am, b-bm + if a == 0 or b == 0 then + return (out + b + a) % 2^32 + end + end + return out +end + +if socket then + socket.USERAGENT, socket.TIMEOUT = "lcasc/1.1", 5 + local ltn12, RETRIES = require("ltn12"), 3 + M.http = function(url, h) + for i=1,RETRIES do + local sink = {} + local ok, status, head = socket.request({url=url, sink=ltn12.sink.table(sink), headers=h}) + if ok then + local cnt = table.concat(sink, "") + return status >= 200 and status < 300 and cnt, status, head, cnt + elseif i == RETRIES then + error("HTTP request failed: " .. tostring(status) .. "\nURL: " .. tostring(url)) + end + end + end +else + M.http = function(url, h) + local c, of = "curl -s -S -A 'luacasc/1.1+curl'", M.tmpname() + if type(h) == "table" then + for k,v in pairs(h) do + c = c .. ' -H ' .. shellEscape(k .. ": " .. v) + end + end + c = c .. ' -o ' .. shellEscape(of) + c = c .. ' ' .. shellEscape(url) + if os.execute(c) == 0 then + return readAndDeleteFile(of) + end + os.remove(of) + end +end + +return M \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/support/gen-mount-db.lua Thu Oct 23 13:44:59 2014 +0100 @@ -0,0 +1,34 @@ +local dump = require 'dump' +local path = require 'path' +local registry = require 'registry' +local casc = require 'casc' +local dbc = require 'casc.dbc' + + +local root = + registry.HKLM.SOFTWARE['Blizzard Entertainment']['World of Warcraft'] 'InstallPath' or + registry.HKLM.SOFTWARE.Wow6432Node['Blizzard Entertainment']['World of Warcraft'] 'InstallPath' +root = path.split(root) + +-- load local build config +local buildKey,cdnBase,cdnKey = assert(casc.localbuild(tostring(root / '.build.info'), casc.selectActiveBuild)) + +-- open the CASC +local handle = casc.open(tostring(root / 'Data'), buildKey, cdnBase, cdnKey) + +-- read the mount database +local Mount = handle:readFile("DBFilesClient/Mount.db2") + +-- create a mapping from mount ID to spell ID +local map = {} +for _,mount,spell in dbc.rows(Mount, 'u7.u.') do + map[mount] = spell +end + +-- save +local file = assert(io.open("../MountMap.lua", 'wb')) +assert(file:write("-- this is a generated file, do not edit, see support/gen-mount-db.lua\n")) +assert(file:write("CyborgMMO_MountMap = "..dump.tostring(map).."\n")) +assert(file:write("CyborgMMO_LocalMountMap = {}\n")) +assert(file:close()) +