├── .gitignore ├── LICENSE ├── README.md ├── examples ├── AreaTable.lua ├── AtlasInfo.lua ├── GlobalStrings.lua ├── InterfaceIcons.lua ├── ItemAppearance.lua ├── ItemSet.lua ├── ItemVisuals.lua ├── ItemWotlk.lua ├── LibRecipes.lua ├── PrintDBC.lua ├── ProfessionNames.lua └── WeaponSkillNames.lua ├── out └── .gitignore └── wowtoolsparser.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .luacheckrc 3 | *.csv 4 | *.json 5 | *.txt 6 | cache 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ketho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WoW.tools Parser 2 | Lua parser for CSV or JSON files from [wow.tools](https://wow.tools/) by [Marlamin](https://github.com/Marlamin/wow.tools) 3 | 4 | ### Usage 5 | Prints [UiMap.db2](https://wow.tools/dbc/?dbc=uimap) 6 | ```lua 7 | local parser = require "wowtoolsparser" 8 | local iter = parser:ReadCSV("uimap") 9 | for line in iter:lines() do 10 | print(table.unpack(line)) 11 | end 12 | ``` 13 | Prints the most recent classic [ChrRaces.db2](https://wow.tools/dbc/?dbc=chrraces&build=1.13.7.37892) build 14 | ```lua 15 | local parser = require "wowtoolsparser" 16 | parser:ExplodeCSV(parser:ReadCSV("chrraces", {build="1.13"})) 17 | ``` 18 | Prints a specific [GlobalStrings.db2](https://wow.tools/dbc/?dbc=globalstrings&build=7.3.5.26972) build for the French locale, keyed by header name 19 | ```lua 20 | local parser = require "wowtoolsparser" 21 | local options = { 22 | header = true, -- index keys by header 23 | build = "7.3.5.26972", 24 | locale = "frFR", 25 | } 26 | 27 | local iter = parser:ReadCSV("globalstrings", options) 28 | for line in iter:lines() do 29 | print(line.ID, line.BaseTag, line.TagText_lang) 30 | end 31 | ``` 32 | 33 | ### Dependencies 34 | * luafilesystem: https://luarocks.org/modules/hisham/luafilesystem 35 | * luasocket: https://luarocks.org/modules/luasocket/luasocket 36 | * luasec: https://luarocks.org/modules/brunoos/luasec 37 | * lua-cjson: https://luarocks.org/modules/openresty/lua-cjson 38 | * csv: https://luarocks.org/modules/geoffleyland/csv 39 | 40 | -------------------------------------------------------------------------------- /examples/AreaTable.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/ketho-wow/UnexploredAreas/blob/master/UnexploredAreas.lua 2 | local parser = require "wowtoolsparser" 3 | local output = "out/AreaTable.lua" 4 | 5 | local AreaIDs = {} 6 | 7 | local function SortByKey(tbl) 8 | local t = {} 9 | for k, v in pairs(tbl) do 10 | table.insert(t, {k, v}) 11 | end 12 | table.sort(t, function(a, b) 13 | return a[1] < b[1] 14 | end) 15 | return t 16 | end 17 | 18 | local function SortKeys(tbl) 19 | local t = {} 20 | for k, v in pairs(tbl) do 21 | table.insert(t, k) 22 | end 23 | table.sort(t) 24 | return t 25 | end 26 | 27 | local function main(BUILD) 28 | print("writing "..output) 29 | local file = io.open(output, "w") 30 | 31 | local areatable_csv = parser:ReadCSV("areatable", {build=BUILD, header=true}) 32 | -- general lookup table for areaids 33 | local areatable_tbl = {} 34 | for line in areatable_csv:lines() do 35 | local ID = tonumber(line.ID) 36 | if ID then 37 | local ParentAreaID = tonumber(line.ParentAreaID) 38 | areatable_tbl[ID] = {ParentAreaID, line.AreaName_lang} 39 | end 40 | end 41 | 42 | -- areaids grouped by parent 43 | local areatable_parents = {} 44 | for ID, tbl in pairs(areatable_tbl) do 45 | local parent = tbl[1] 46 | local name = tbl[2] 47 | if parent > 0 then -- ignore the parent themselves 48 | areatable_parents[parent] = areatable_parents[parent] or {} 49 | areatable_parents[parent][ID] = true 50 | end 51 | end 52 | 53 | file:write("local AreaTable = {\n") 54 | -- oof this is confusing 55 | for _, v in pairs(SortByKey(areatable_parents)) do 56 | local parentID = v[1] 57 | local parentName = areatable_tbl[parentID][2] 58 | file:write(string.format('\t[%d] = { -- %s\n', parentID, parentName)) 59 | for _, ID in pairs(SortKeys(v[2])) do 60 | local AreaName_lang = areatable_tbl[ID][2] 61 | file:write(string.format('\t\t[%d] = "%s",\n', ID, AreaName_lang)) 62 | end 63 | file:write("\t},\n") 64 | end 65 | file:write("}\n\n") 66 | 67 | local uimapassignment_csv = parser:ReadCSV("uimapassignment", {build=BUILD, header=true}) 68 | local uimapassignment_tbl = {} 69 | for line in uimapassignment_csv:lines() do 70 | local AreaID = tonumber(line.AreaID) 71 | if AreaID and AreaID > 0 then 72 | -- an AreaID can have multiple of UiMapID 73 | local UiMapID = tonumber(line.UiMapID) 74 | uimapassignment_tbl[UiMapID] = AreaID 75 | end 76 | end 77 | 78 | file:write("local UiMapAssignment = {\n") 79 | for _, v in pairs(SortByKey(uimapassignment_tbl)) do 80 | local UiMapID = v[1] 81 | local AreaID = v[2] 82 | file:write(string.format('\t[%d] = %d,\n', UiMapID, AreaID)) 83 | end 84 | file:write("}\n") 85 | 86 | file:close() 87 | print("finished") 88 | end 89 | 90 | main() 91 | -------------------------------------------------------------------------------- /examples/AtlasInfo.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/Ketho/BlizzardInterfaceResources/blob/live/Resources/AtlasInfo.lua 2 | local parser = require "wowtoolsparser" 3 | --local OUT_PATH = "out/AtlasInfo.lua" 4 | local OUT_PATH = "../BlizzardInterfaceResources/Resources/AtlasInfo.lua" 5 | 6 | local function SortTable(tbl, key) 7 | table.sort(tbl, function(a, b) 8 | return a[key] < b[key] 9 | end) 10 | end 11 | 12 | local function AtlasInfo(options) 13 | options = options or {} 14 | options.header = true 15 | local filedata = parser:ReadListfile() 16 | local uitextureatlas = parser:ReadCSV("uitextureatlas", options) 17 | local atlasTable, atlasOrder, atlasSize = {}, {}, {} 18 | for line in uitextureatlas:lines() do 19 | local atlasID = tonumber(line.ID) 20 | local fdid = tonumber(line.FileDataID) 21 | if atlasID then -- last csv line is empty 22 | atlasTable[atlasID] = {} 23 | table.insert(atlasOrder, { 24 | atlasID = atlasID, 25 | fdid = fdid, 26 | fileName = filedata[fdid] or tostring(fdid), -- listfile might not yet be updated 27 | }) 28 | atlasSize[atlasID] = { 29 | width = tonumber(line.AtlasWidth), 30 | height = tonumber(line.AtlasHeight), 31 | } 32 | end 33 | end 34 | SortTable(atlasOrder, "fileName") 35 | 36 | local uitextureatlasmember = parser:ReadCSV("uitextureatlasmember", options) 37 | for line in uitextureatlasmember:lines() do 38 | local name = line.CommittedName 39 | if name and name ~= "" then -- 1130222 interface/store/shop has empty atlas members 40 | local atlasID = tonumber(line.UiTextureAtlasID) 41 | local size = atlasSize[atlasID] 42 | local left = tonumber(line.CommittedLeft) 43 | local right = tonumber(line.CommittedRight) 44 | local top = tonumber(line.CommittedTop) 45 | local bottom = tonumber(line.CommittedBottom) 46 | table.insert(atlasTable[atlasID], { 47 | memberID = tonumber(line.ID), 48 | name = name, 49 | width = right - left, 50 | height = bottom - top, 51 | left = left / size.width, 52 | right = right / size.width, 53 | top = top / size.height, 54 | bottom = bottom / size.height, 55 | tileshoriz = line.CommittedFlags&0x4 > 0, 56 | tilesvert = line.CommittedFlags&0x2 > 0, 57 | }) 58 | end 59 | end 60 | 61 | local fsAtlas = '\t["%s"] = { -- %d\n' 62 | local fsMember = '\t\t["%s"] = {%d, %d, %s, %s, %s, %s, %s, %s},\n' 63 | 64 | print("writing "..OUT_PATH) 65 | local file = io.open(OUT_PATH, "w") 66 | file:write("-- see also https://wow.gamepedia.com/API_C_Texture.GetAtlasInfo\n") 67 | file:write("-- atlas = width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, tilesHorizontally, tilesVertically\n") 68 | file:write("local AtlasInfo = {\n") 69 | for _, atlas in pairs(atlasOrder) do 70 | file:write(fsAtlas:format(atlas.fileName:match("(.+)%.blp") or atlas.fileName, atlas.fdid)) 71 | for _, member in pairs(atlasTable[atlas.atlasID]) do 72 | file:write(fsMember:format(member.name, member.width, member.height, 73 | member.left, member.right, member.top, member.bottom, 74 | member.tileshoriz, member.tilesvert)) 75 | end 76 | file:write("\t},\n") 77 | end 78 | file:write("}\n\nreturn AtlasInfo\n") 79 | file:close() 80 | print("finished") 81 | end 82 | 83 | AtlasInfo() 84 | -- AtlasInfo({build="9.1.5"}) 85 | -- AtlasInfo({build="1.14."}) 86 | -- AtlasInfo({build="2.5.2"}) 87 | -------------------------------------------------------------------------------- /examples/GlobalStrings.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/Ketho/BlizzardInterfaceResources/blob/live/Resources/GlobalStrings.lua 2 | local parser = require "wowtoolsparser" 3 | --local OUT_PATH = "out/GlobalStrings.lua" 4 | local OUT_PATH = "../BlizzardInterfaceResources/Resources/GlobalStrings.lua" 5 | 6 | local short = '%s = "%s";' 7 | local full = '_G["%s"] = "%s";' 8 | 9 | local slashStrings = { 10 | KEY_BACKSLASH = function(s) return s:sub(1, 2) == "9." end, -- broken in 9.1.0 11 | CHATLOGENABLED = function(s) return s:sub(1, 3) == "9.0" end, -- broken in 9.0.5 12 | --COMBATLOGENABLED = true, 13 | } 14 | 15 | local override = { 16 | -- https://wow.tools/dbc/?dbc=globalstrings#search=PARTY_PLAYER_CHROMIE_TIME_FMT 17 | PARTY_PLAYER_CHROMIE_TIME_FMT = [[%s\n\n\\%s]], -- 9.0.1 (35256) 18 | } 19 | 20 | local function IsValidTableKey(s) 21 | return not s:find("-") and not s:find("^%d") 22 | end 23 | 24 | local function GlobalStrings(options) 25 | options = options or {} 26 | options.header = true 27 | -- filter and sort globalstrings 28 | local globalstrings, usedBuild = parser:ReadCSV("globalstrings", options) 29 | local stringsTable = {} 30 | for line in globalstrings:lines() do 31 | local flags = tonumber(line.Flags) 32 | if flags and flags&0x1 > 0 then 33 | table.insert(stringsTable, { 34 | BaseTag = line.BaseTag, 35 | TagText = line.TagText_lang 36 | }) 37 | end 38 | end 39 | table.sort(stringsTable, function(a, b) 40 | return a.BaseTag < b.BaseTag 41 | end) 42 | 43 | print("writing "..OUT_PATH) 44 | local file = io.open(OUT_PATH, "w") 45 | for _, tbl in pairs(stringsTable) do 46 | local key, value = tbl.BaseTag, tbl.TagText 47 | value = value:gsub('\\32', ' ') -- space char 48 | -- unescape any quotes before escaping quotes 49 | value = value:gsub('\\\"', '"') 50 | value = value:gsub('"', '\\\"') 51 | if slashStrings[key] and slashStrings[key](usedBuild) then 52 | value = value:gsub("\\", "\\\\") 53 | end 54 | if override[key] then 55 | value = override[key] 56 | end 57 | 58 | -- check if the key is proper short table syntax 59 | local fs = IsValidTableKey(key) and short or full 60 | file:write(fs:format(key, value), "\n") 61 | end 62 | file:close() 63 | print("finished") 64 | end 65 | 66 | GlobalStrings() 67 | -- GlobalStrings({build="9.1.5"}) 68 | -- GlobalStrings({build="1.14."}) 69 | -- GlobalStrings({build="2.5.2"}) 70 | -- GlobalStrings({locale="deDE"}) 71 | -------------------------------------------------------------------------------- /examples/InterfaceIcons.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/ketho-wow/LargerMacroIconSelection/blob/master/Data/FileDataRetail.lua 2 | local parser = require "wowtoolsparser" 3 | local output = "out/InterfaceIcons.lua" 4 | 5 | local function main() 6 | local filedata = parser:ReadListfile() 7 | local sorted = {} 8 | for fdid, path in pairs(filedata) do 9 | local _, _, icon = path:find("interface/icons/(.+)%.blp$") 10 | if icon then 11 | table.insert(sorted, {fdid, icon}) 12 | end 13 | end 14 | table.sort(sorted, function(a, b) 15 | return a[1] < b[1] 16 | end) 17 | 18 | local fs = '[%d]="%s",\n' 19 | print("writing "..output) 20 | local file = io.open(output, "w") 21 | file:write("local InterfaceIcons = {\n") 22 | for _, tbl in pairs(sorted) do 23 | file:write(fs:format(table.unpack(tbl))) 24 | end 25 | file:write("}\n") 26 | file:close() 27 | print("finished") 28 | end 29 | 30 | main() 31 | -------------------------------------------------------------------------------- /examples/ItemAppearance.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/ketho-wow/WardrobeSort/blob/master/Data/FileData.lua 2 | -- https://github.com/ketho-wow/ClickMorph/blob/master/Data/Live/ItemAppearance.lua 3 | local parser = require "wowtoolsparser" 4 | local output = "out/ItemAppearance.lua" 5 | local data = {} 6 | local fd 7 | 8 | local suffix = { 9 | "_al_[ufm]", 10 | "_au_[ufm]", 11 | "_fo_[ufm]", 12 | "_ha_[ufm]", 13 | "_ll_[ufm]", 14 | "_lu_[ufm]", 15 | "_tl_[ufm]", 16 | "_tu_[ufm]", 17 | } 18 | 19 | -- get texturefiledata first to verify itemdisplayinfomaterialres filedata 20 | local order = { 21 | "texturefiledata", 22 | "itemdisplayinfomaterialres", 23 | "itemdisplayinfo", 24 | "itemappearance", 25 | } 26 | 27 | -- https://github.com/ketho-wow/WardrobeSort/wiki/Data-workflow 28 | local handler = { 29 | itemappearance = function(iter) 30 | local t = {} 31 | for l in iter:lines() do 32 | local ID = tonumber(l.ID) 33 | if ID then 34 | t[ID] = tonumber(l.ItemDisplayInfoID) 35 | end 36 | end 37 | return t 38 | end, 39 | itemdisplayinfo = function(iter) 40 | local t = {} 41 | for l in iter:lines() do 42 | local ID = tonumber(l.ID) 43 | if ID then 44 | t[ID] = tonumber(l["ModelMaterialResourcesID[0]"]) 45 | end 46 | end 47 | return t 48 | end, 49 | -- there are multiple textures ids for the same itemdisplay ids 50 | -- so only the last occurrence will be saved 51 | itemdisplayinfomaterialres = function(iter) 52 | local t = {} 53 | for l in iter:lines() do 54 | local ID = tonumber(l.ItemDisplayInfoID) 55 | local MRID = tonumber(l.MaterialResourcesID) 56 | if ID and fd[data.texturefiledata[MRID]] then 57 | t[ID] = MRID 58 | end 59 | end 60 | return t 61 | end, 62 | texturefiledata = function(iter) 63 | local t = {} 64 | for l in iter:lines() do 65 | local ID = tonumber(l.MaterialResourcesID) 66 | if ID then 67 | t[ID] = tonumber(l.FileDataID) 68 | end 69 | end 70 | return t 71 | end, 72 | } 73 | 74 | local function ParseDBC(dbc, BUILD) 75 | local iter = parser:ReadCSV(dbc, {build=BUILD, header=true}) 76 | return handler[dbc](iter) 77 | end 78 | 79 | local function ItemAppearance(BUILD) 80 | fd = parser:ReadListfile() 81 | for _, name in pairs(order) do 82 | print("reading "..name) 83 | data[name] = ParseDBC(name, BUILD) 84 | end 85 | local fs = '[%d]="%s",\n' 86 | 87 | print("writing "..output) 88 | local file = io.open(output, "w") 89 | file:write("local ItemAppearance = {\n") 90 | for id, val in pairs(data.itemappearance) do 91 | local idi = data.itemdisplayinfo[val] 92 | idi = idi and idi > 0 and idi 93 | local idimr = data.itemdisplayinfomaterialres[val] 94 | idimr = idimr and idimr > 0 and idimr 95 | local displayInfo = idi or idimr 96 | if displayInfo then 97 | local txfd = fd[data.texturefiledata[displayInfo]] 98 | if txfd then 99 | local visualName = txfd:match(".+/(.+)%.blp") 100 | for _, v in pairs(suffix) do 101 | visualName = visualName:gsub(v, "") 102 | end 103 | file:write(fs:format(id, visualName)) 104 | end 105 | end 106 | end 107 | file:write("}\n\nreturn ItemAppearance\n") 108 | file:close() 109 | print("finished") 110 | end 111 | 112 | ItemAppearance() 113 | -------------------------------------------------------------------------------- /examples/ItemSet.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/ketho-wow/ClickMorph/blob/master/Data/Classic/ItemSet.lua 2 | local parser = require "wowtoolsparser" 3 | local output = "out/ItemSet.lua" 4 | 5 | -- https://github.com/Gethe/wow-ui-source/blob/classic/FrameXML/Constants.lua#L311 6 | local INVSLOT_AMMO = 0 7 | local INVSLOT_HEAD = 1 8 | local INVSLOT_NECK = 2 9 | local INVSLOT_SHOULDER = 3 10 | local INVSLOT_BODY = 4 11 | local INVSLOT_CHEST = 5 12 | local INVSLOT_WAIST = 6 13 | local INVSLOT_LEGS = 7 14 | local INVSLOT_FEET = 8 15 | local INVSLOT_WRIST = 9 16 | local INVSLOT_HAND = 10 17 | local INVSLOT_FINGER1 = 11 18 | local INVSLOT_FINGER2 = 12 19 | local INVSLOT_TRINKET1 = 13 20 | local INVSLOT_TRINKET2 = 14 21 | local INVSLOT_BACK = 15 22 | local INVSLOT_MAINHAND = 16 23 | local INVSLOT_OFFHAND = 17 24 | local INVSLOT_RANGED = 18 25 | local INVSLOT_TABARD = 19 26 | 27 | -- https://wow.gamepedia.com/Enum_Item.InventoryType 28 | -- https://wow.gamepedia.com/InventorySlotName 29 | -- https://wow.gamepedia.com/API_C_Transmog.GetSlotForInventoryType 30 | local InventoryTypeToSlot = { 31 | --[0] = INVSLOT_AMMO, -- IndexNonEquipType 32 | [1] = INVSLOT_HEAD, -- IndexHeadType 33 | --[2] = INVSLOT_NECK, -- IndexNeckType 34 | [3] = INVSLOT_SHOULDER, -- IndexShoulderType 35 | [4] = INVSLOT_BODY, -- IndexBodyType 36 | [5] = INVSLOT_CHEST, -- IndexChestType 37 | [6] = INVSLOT_WAIST, -- IndexWaistType 38 | [7] = INVSLOT_LEGS, -- IndexLegsType 39 | [8] = INVSLOT_FEET, -- IndexFeetType 40 | [9] = INVSLOT_WRIST, -- IndexWristType 41 | [10] = INVSLOT_HAND, -- IndexHandType 42 | --[11] = INVSLOT_FINGER1, -- IndexFingerType 43 | --[12] = INVSLOT_TRINKET1, -- IndexTrinketType 44 | [13] = INVSLOT_MAINHAND, -- IndexWeaponType, 16 45 | [14] = INVSLOT_OFFHAND, -- IndexShieldType, 17 46 | [15] = INVSLOT_MAINHAND, -- IndexRangedType, 16 47 | [16] = INVSLOT_BACK, -- IndexCloakType, 15 48 | [17] = INVSLOT_MAINHAND, -- Index2HweaponType, 16 49 | --[18] = IndexBagType, -- IndexBagType 50 | [19] = INVSLOT_TABARD, -- IndexTabardType, 19 51 | [20] = INVSLOT_CHEST, -- IndexRobeType, 5 52 | [21] = INVSLOT_MAINHAND, -- IndexWeaponmainhandType, 16 53 | [22] = INVSLOT_OFFHAND, -- IndexWeaponoffhandType, 17 (fixed) 54 | [23] = INVSLOT_OFFHAND, -- IndexHoldableType, 17 55 | --[24] = IndexAmmoType, -- IndexAmmoType 56 | --[25] = IndexThrownType, -- IndexThrownType 57 | [26] = INVSLOT_MAINHAND, -- IndexRangedrightType, 16 58 | --[27] = IndexQuiverType, -- IndexQuiverType 59 | --[28] = IndexRelicType, -- IndexRelicType 60 | } 61 | 62 | local function SortTableKey(tbl) 63 | local sorted = {} 64 | for key in pairs(tbl) do 65 | table.insert(sorted, key) 66 | end 67 | table.sort(sorted) 68 | return sorted 69 | end 70 | 71 | local handler = { 72 | item = function(iter) 73 | local item = {} 74 | for l in iter:lines() do 75 | local ID = tonumber(l.ID) 76 | if ID then 77 | item[ID] = tonumber(l.InventoryType) 78 | end 79 | end 80 | return item 81 | end, 82 | itemset = function(iter) 83 | local names, itemIDs = {}, {} 84 | for l in iter:lines() do 85 | local setID = tonumber(l.ID) 86 | if setID then 87 | names[setID] = l.Name_lang 88 | itemIDs[setID] = {} 89 | for i = 0, 9 do 90 | local itemID = tonumber(l["ItemID["..i.."]"]) 91 | if itemID > 0 then 92 | table.insert(itemIDs[setID], itemID) 93 | end 94 | end 95 | end 96 | end 97 | return names, itemIDs 98 | end, 99 | } 100 | 101 | local function ParseDBC(dbc, BUILD) 102 | local iter = parser:ReadCSV(dbc, {build=BUILD, header=true}) 103 | return handler[dbc](iter) 104 | end 105 | 106 | local function ItemSet(BUILD) 107 | local item_inventoryType = ParseDBC("item", BUILD) 108 | local set_names, set_itemIDs = ParseDBC("itemset", BUILD) 109 | print("writing "..output) 110 | local file = io.open(output, "w") 111 | file:write("local ItemSet = {\n") 112 | 113 | for _, setID in pairs(SortTableKey(set_names)) do 114 | file:write(string.format("\t[%d] = {\n", setID)) 115 | file:write(string.format('\t\tname = "%s",\n', set_names[setID])) 116 | local sortedSet = {} 117 | for _, itemID in pairs(set_itemIDs[setID]) do 118 | table.insert(sortedSet, { 119 | itemID = itemID, 120 | inventoryType = item_inventoryType[itemID], 121 | slot = InventoryTypeToSlot[item_inventoryType[itemID]] or 0, 122 | }) 123 | end 124 | table.sort(sortedSet, function(a, b) 125 | return a.slot < b.slot 126 | end) 127 | for _, v in pairs(sortedSet) do 128 | if v.slot > 0 then -- neck, finger, trinket cant be morphed 129 | file:write(string.format("\t\t[%d] = %d, -- %d\n", v.slot, v.itemID, v.inventoryType)) 130 | end 131 | end 132 | file:write("\t},\n") 133 | end 134 | file:write("}\n\nreturn ItemSet\n") 135 | file:close() 136 | print("finished") 137 | end 138 | 139 | ItemSet("1.13.2") 140 | -------------------------------------------------------------------------------- /examples/ItemVisuals.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/ketho-wow/ClickMorph/blob/master/Data/Live/ItemVisuals.lua 2 | local parser = require "wowtoolsparser" 3 | local output = "out/ItemVisuals.lua" 4 | 5 | local function GetFirstID(t) 6 | for _, v in pairs(t) do 7 | if v ~= 0 then 8 | return v 9 | end 10 | end 11 | end 12 | 13 | local function ItemVisuals(BUILD) 14 | local fd = parser:ReadListfile() 15 | local iter = parser:ReadCSV("itemvisuals", {build=BUILD, header=true}) 16 | --local fsRaw = '\t[%d] = {%s},\n' 17 | local fsName = '\t[%d] = "%s",\n' 18 | local modelFileID = {} 19 | 20 | print("writing "..output) 21 | local file = io.open(output, "w") 22 | file:write("local ItemVisuals = {\n") 23 | for l in iter:lines() do 24 | local ID = tonumber(l.ID) 25 | if ID then 26 | modelFileID[ID] = {} 27 | for i = 0, 4 do 28 | table.insert(modelFileID[ID], tonumber(l["ModelFileID["..i.."]"])) 29 | end 30 | local firstID = GetFirstID(modelFileID[ID]) 31 | if firstID and fd[firstID] then -- ID 352 does not have a fd entry 32 | --file:write(fsRaw:format(ID, table.concat(modelFileID[ID], ", "))) 33 | local name = fd[firstID]:match(".+/(.+)%.m2") 34 | file:write(fsName:format(ID, name)) 35 | end 36 | end 37 | end 38 | file:write("}\n\nreturn ItemVisuals\n") 39 | file:close() 40 | print("finished") 41 | end 42 | 43 | ItemVisuals() 44 | -------------------------------------------------------------------------------- /examples/ItemWotlk.lua: -------------------------------------------------------------------------------- 1 | local parser = require "wowtoolsparser" 2 | local output = "out/ItemWotlk.lua" 3 | 4 | local function ItemWotlk(BUILD) 5 | print("reading itemdisplayinfo") 6 | local itemdisplayinfo = {} 7 | local dbc_itemdisplayinfo = parser:ReadCSV("itemdisplayinfo", {build=BUILD, header=true}) 8 | for line in dbc_itemdisplayinfo:lines() do 9 | local ID = tonumber(line.ID) 10 | if ID then 11 | itemdisplayinfo[ID] = line["InventoryIcon[0]"] 12 | end 13 | end 14 | local fs = '\t[%d] = {%d, "%s"},\n' 15 | 16 | print("reading itemsparse") 17 | local itemsparse = {} 18 | local dbc_itemsparse = parser:ReadCSV("itemsparse", {header=true}) 19 | for line in dbc_itemsparse:lines() do 20 | local ID = tonumber(line.ID) 21 | if ID then 22 | itemsparse[ID] = line["Display_lang"]:gsub('"', '\\"') 23 | end 24 | end 25 | 26 | local fs_noname = '\t[%d] = {%d, "%s"},\n' 27 | local fs_name = '\t[%d] = {%d, "%s", "%s"},\n' 28 | print("writing "..output) 29 | local file = io.open(output, "w") 30 | file:write("local ItemWotlk = {\n") 31 | file:write("\t-- ID = InventoryType, Icon, Name\n") 32 | local dbc_item = parser:ReadCSV("item", {build=BUILD, header=true}) 33 | for line in dbc_item:lines() do 34 | local ID = tonumber(line.ID) 35 | if ID then 36 | local InventoryType = tonumber(line.InventoryType) 37 | if InventoryType > 0 then 38 | local DisplayInfoID = tonumber(line.DisplayInfoID) 39 | if itemsparse[ID] then 40 | file:write(fs_name:format( 41 | ID, 42 | InventoryType, 43 | itemdisplayinfo[DisplayInfoID], 44 | itemsparse[ID] 45 | )) 46 | else 47 | file:write(fs_noname:format( 48 | ID, 49 | InventoryType, 50 | itemdisplayinfo[DisplayInfoID] 51 | )) 52 | end 53 | end 54 | end 55 | end 56 | file:write("}\n") 57 | file:close() 58 | print("finished") 59 | end 60 | 61 | ItemWotlk("3.3.5") 62 | -------------------------------------------------------------------------------- /examples/LibRecipes.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | https://www.wowace.com/projects/librecipes-2-0 3 | https://gist.github.com/Ketho/4776d54e49bd438b006642c0c4c049c9 4 | 5 | example: 6 | -- Alchemy 7 | lib:AddRecipe(2553, 3230, 2457) -- Elixir of Minor Agility 8 | https://www.wowhead.com/item=2553/recipe-elixir-of-minor-agility 9 | https://www.wowhead.com/spell=3230/elixir-of-minor-agility 10 | https://www.wowhead.com/item=2457/elixir-of-minor-agility 11 | 12 | DBCs: 13 | -- item.db2 14 | -- 1:ID, 2:ClassID, 3:SubclassID 15 | 2553,9,6,0,0,0,-1,134942,20,0 16 | 17 | -- itemeffect.db2 18 | -- 8:SpellID, 10:ParentItemID 19 | 105682,1,6,0,-1,-1,0,3230,0,2553 20 | 21 | -- spelleffect.db2 22 | -- 12:EffectItemType, 35:SpellID 23 | 991,0,0,0,24,0,0,0,0,1,0,2457,0,0,0,0,0,0,1,0,0.25,0,1,1,0,0,0,0,0,0,0,0,1,0,3230 24 | 25 | -- spellname.db2 26 | -- 1:ID, 2:Name_lang 27 | 3230,Elixir of Minor Agility 28 | 29 | -- itemsparse.db2 30 | -- 1:ID, 7:Display_lang 31 | 2457,-1,,,,,Elixir of Minor Agility,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0,0,15,60,1,1,0.9945,0,8192,0,16384,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,-1,0,0,0,0,0,0,0,0,3,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,2,0,1 32 | ]] 33 | 34 | local parser = require "wowtoolsparser" 35 | local output = "out/LibRecipes.lua" 36 | 37 | local LE_ITEM_CLASS_RECIPE = 9 38 | local tradeskills = { 39 | [0] = "Uncategorized", 40 | [1] = "Leatherworking", 41 | [2] = "Tailoring", 42 | [3] = "Engineering", 43 | [4] = "Blacksmithing", 44 | [5] = "Cooking", 45 | [6] = "Alchemy", 46 | [7] = "First Aid", 47 | [8] = "Enchanting", 48 | -- [9] = "Fishing", -- only has removed recipes 49 | [10] = "Jewelcrafting", 50 | [11] = "Inscription", 51 | -- there is no Mining category 52 | } 53 | local LibRecipesOrder = {6, 4, 5, 8, 3, 11, 10, 1, 7, 2, 0} -- alphabetically 54 | 55 | local handlers = { 56 | item = function(dbc) 57 | local t = {} 58 | for l in dbc:lines() do 59 | local ID = tonumber(l.ID) 60 | local ClassID = tonumber(l.ClassID) 61 | local SubclassID = tonumber(l.SubclassID) 62 | if ID then -- last csv line is empty 63 | if ClassID == LE_ITEM_CLASS_RECIPE then 64 | t[ID] = SubclassID 65 | end 66 | end 67 | end 68 | return t 69 | end, 70 | itemeffect = function(dbc) 71 | local t = {} 72 | for l in dbc:lines() do 73 | local ID = tonumber(l.ID) 74 | local SpellID = tonumber(l.SpellID) 75 | local ParentItemID = tonumber(l.ParentItemID) 76 | if ID then 77 | t[ParentItemID] = SpellID 78 | end 79 | end 80 | return t 81 | end, 82 | spelleffect = function(dbc) 83 | local t = {} 84 | for l in dbc:lines() do 85 | local ID = tonumber(l.ID) 86 | local EffectItemType = tonumber(l.EffectItemType) 87 | local SpellID = tonumber(l.SpellID) 88 | if ID then 89 | t[SpellID] = EffectItemType 90 | end 91 | end 92 | return t 93 | end, 94 | spellname = function(dbc) 95 | local t = {} 96 | for l in dbc:lines() do 97 | local ID = tonumber(l.ID) 98 | if ID then 99 | t[ID] = l.Name_lang 100 | end 101 | end 102 | return t 103 | end, 104 | itemsparse = function(dbc) 105 | local t = {} 106 | for l in dbc:lines() do 107 | local ID = tonumber(l.ID) 108 | if ID then 109 | t[ID] = l.Display_lang 110 | end 111 | end 112 | return t 113 | end, 114 | } 115 | 116 | local function ParseDBC(name, BUILD) 117 | local dbc = parser:ReadCSV(name, {build=BUILD, header=true}) 118 | return handlers[name](dbc) 119 | end 120 | 121 | local function LibRecipes(BUILD) 122 | local dbc_item = ParseDBC("item", BUILD) 123 | local dbc_itemeffect = ParseDBC("itemeffect", BUILD) 124 | local dbc_spelleffect = ParseDBC("spelleffect", BUILD) 125 | local dbc_spellname = ParseDBC("spellname", BUILD) 126 | local dbc_itemsparse = ParseDBC("itemsparse", BUILD) 127 | -- init tables 128 | local data = {} 129 | for i = 0, 11 do 130 | data[i] = {} 131 | end 132 | -- populate data 133 | for item_id, subclass_id in pairs(dbc_item) do 134 | local spell_id = dbc_itemeffect[item_id] 135 | local result_item = dbc_spelleffect[spell_id] 136 | -- for uncategorized recipes the item name is more descriptive 137 | local name = subclass_id>0 and dbc_spellname[spell_id] or dbc_itemsparse[item_id] 138 | if spell_id and name then -- sanity check for removed spells/items 139 | table.insert(data[subclass_id], {item_id, spell_id, result_item, name}) 140 | end 141 | end 142 | -- sort tables 143 | for _, tbl in pairs(data) do 144 | table.sort(tbl, function(a, b) 145 | return a[1] < b[1] 146 | end) 147 | end 148 | -- write data 149 | print("writing "..output) 150 | local file = io.open(output, "w") 151 | local fs = "lib:AddRecipe(%d, %s, %s) -- %s\n" 152 | for _, subclassID in pairs(LibRecipesOrder) do 153 | file:write("-- "..tradeskills[subclassID].."\n") 154 | for _, tbl in pairs(data[subclassID]) do 155 | file:write(fs:format(table.unpack(tbl))) 156 | end 157 | end 158 | file:close() 159 | print("finished") 160 | end 161 | 162 | LibRecipes("8.3.0") 163 | -- LibRecipes("1.13.5") 164 | -- LibRecipes() -- beta 165 | -------------------------------------------------------------------------------- /examples/PrintDBC.lua: -------------------------------------------------------------------------------- 1 | local parser = require "wowtoolsparser" 2 | 3 | parser:ExplodeCSV(parser:ReadCSV("uimap")) -- most recent retail build 4 | parser:ExplodeCSV(parser:ReadCSV("chrraces", {build="1.13.2"})) -- most recent classic build 5 | 6 | parser:ExplodeJSON(parser:ReadJSON("azeriteessence")) 7 | parser:ExplodeJSON(parser:ReadJSON("map", {build="7.3.5.26972"})) -- specific build 8 | parser:ExplodeJSON(parser:ReadJSON("uimap", {locale="frFR"})) -- french 9 | 10 | --ExplodeListfile(parser:ReadListfile()) -- takes very long when printing 11 | print("finished") 12 | -------------------------------------------------------------------------------- /examples/ProfessionNames.lua: -------------------------------------------------------------------------------- 1 | local parser = require "wowtoolsparser" 2 | 3 | local locales = { 4 | "enUS", 5 | "deDE", 6 | "frFR", 7 | "esMX", 8 | "ptBR", 9 | "ruRU", 10 | "zhCN", 11 | "zhTW", 12 | "koKR", 13 | } 14 | 15 | local output = "out/ProfessionNames.lua" 16 | local file = io.open(output, "w") 17 | file:write("local ProfessionNames = {\n") 18 | 19 | for _, locale in pairs(locales) do 20 | local iter = parser:ReadCSV("skillline", { 21 | header = true, 22 | locale = locale, 23 | }) 24 | 25 | file:write(string.format("\t%s = {\n", locale)) 26 | for l in iter:lines() do 27 | local ID = tonumber(l.ID) 28 | if ID then 29 | if tonumber(l.SpellBookSpellID) > 0 then 30 | file:write(string.format('\t\t[%d] = "%s",\n', ID, l.DisplayName_lang)) 31 | end 32 | end 33 | end 34 | file:write("\t},\n") 35 | end 36 | file:write("}\n") 37 | file:close() 38 | -------------------------------------------------------------------------------- /examples/WeaponSkillNames.lua: -------------------------------------------------------------------------------- 1 | -- https://wago.io/fjl8ot17v 2 | local parser = require "wowtoolsparser" 3 | 4 | local locales = { 5 | "enUS", 6 | "deDE", 7 | "frFR", 8 | "esMX", 9 | "ptBR", 10 | "ruRU", 11 | "zhCN", 12 | "zhTW", 13 | "koKR", 14 | } 15 | 16 | local output = "out/WeaponSkillNames.lua" 17 | local file = io.open(output, "w") 18 | file:write("local WeaponSkillNames = {\n") 19 | 20 | for _, locale in pairs(locales) do 21 | local iter = parser:ReadCSV("skillline", { 22 | header = true, 23 | build = "1.13", 24 | locale = locale, 25 | }) 26 | 27 | file:write(string.format("\t%s = {\n", locale)) 28 | for l in iter:lines() do 29 | local ID = tonumber(l.ID) 30 | if ID then 31 | if tonumber(l.CategoryID) == 6 then 32 | file:write(string.format('\t\t["%s"] = %d,\n', l.DisplayName_lang, ID)) 33 | end 34 | end 35 | end 36 | file:write("\t},\n") 37 | end 38 | file:write("}\n") 39 | file:close() 40 | -------------------------------------------------------------------------------- /out/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /wowtoolsparser.lua: -------------------------------------------------------------------------------- 1 | local lfs = require "lfs" 2 | local https = require "ssl.https" 3 | local cjson = require "cjson" 4 | local cjsonutil = require "cjson.util" 5 | local csv = require "csv" 6 | 7 | local parser = {} 8 | 9 | parser.CACHE_PATH = "cache/" 10 | parser.INVALIDATION_TIME = 60*60 11 | 12 | local listfile_cache = parser.CACHE_PATH.."listfile.csv" 13 | local versions_cache = parser.CACHE_PATH.."%s/%s_versions.json" 14 | local csv_cache = parser.CACHE_PATH.."%s/%s.csv" 15 | local json_cache = parser.CACHE_PATH.."%s/%s.json" 16 | 17 | local listfile_url = "https://wow.tools/casc/listfile/download/csv/unverified" 18 | --local databases_url = "https://api.wow.tools/databases" 19 | local versions_url = "https://api.wow.tools/databases/%s/versions" 20 | local csv_url = "https://wow.tools/api/export/?name=%s&build=%s&useHotfixes=true" 21 | local json_url = "https://wow.tools/api/data/%s/?build=%s&useHotfixes=true&length=%d" -- saves them a slice call 22 | 23 | local function GetBaseName(name, build, options) 24 | local base = name 25 | base = base.."_"..build 26 | if options.locale then 27 | base = base.."_"..options.locale 28 | end 29 | return base 30 | end 31 | 32 | local function CreateFolder(path) 33 | if not lfs.attributes(path) then 34 | lfs.mkdir(path) 35 | end 36 | end 37 | CreateFolder(parser.CACHE_PATH) 38 | 39 | local function ShouldDownload(path) 40 | local attr = lfs.attributes(path) 41 | if not attr then 42 | return true 43 | elseif path:find("versions%.json") or path:find("listfile%.csv") then 44 | local modified = attr.modification 45 | if os.time() > modified + parser.INVALIDATION_TIME then 46 | return true 47 | end 48 | end 49 | end 50 | 51 | local skip_codes = { 52 | [204] = true, -- No Content 53 | [400] = true, -- Bad Request 54 | } 55 | 56 | -- not really proud of this 57 | -- https://github.com/brunoos/luasec/wiki/LuaSec-1.0.x#httpsrequesturl---body 58 | local function DownloadFile(url, path, isRetry) 59 | local res, code, _, status = https.request(url) 60 | if code == 200 then 61 | if not path then 62 | return res 63 | else 64 | print("dl", path) 65 | local file = io.open(path, "w") 66 | file:write(res) 67 | file:close() 68 | return true 69 | end 70 | elseif code == 400 and url:find("useHotfixes") then -- some csvs are broken by hotfixes 71 | url = url:gsub("&useHotfixes=true", "") 72 | print("retry", path, status) 73 | DownloadFile(url, path) 74 | elseif code == "wantread" and not isRetry then 75 | print("wantread", path, status) 76 | DownloadFile(url.."&useHotfixes=true", path, true) 77 | elseif skip_codes[code] then 78 | print("skip", path, status) 79 | return false 80 | else 81 | error(string.format("%s, %s, %s", path, code, status)) 82 | end 83 | end 84 | 85 | --- Gets all build versions for a database 86 | -- @param name the DBC name 87 | -- @return table available build versions 88 | function parser:GetVersions(name) 89 | local path = versions_cache:format(name, name) 90 | if ShouldDownload(path) then 91 | DownloadFile(versions_url:format(name), path) 92 | end 93 | local json = cjsonutil.file_load(path) 94 | local tbl = cjson.decode(json) 95 | return tbl 96 | end 97 | 98 | --- Finds a wow.tools build 99 | -- @param name the DBC name 100 | -- @param build the build to search for 101 | -- @return string the build number (e.g. "8.2.0.30993") 102 | function parser:FindBuild(name, build) 103 | local versions = self:GetVersions(name) 104 | if build then 105 | for _, version in pairs(versions) do 106 | if version:find(build, 1, true) then 107 | return version 108 | end 109 | end 110 | error(string.format("build \"%s\" is not available for %s", build, name)) 111 | else 112 | return versions[1] -- the most recent build 113 | end 114 | end 115 | 116 | --- Parses the DBC (with header) from CSV 117 | -- @param name the DBC name 118 | -- @param options.build (optional) the build version, otherwise uses the most recent build 119 | -- @param options.header (optional) whether fields will be keyed by header name instead of column index 120 | -- @param options.locale (optional) the locale, otherwise uses english 121 | -- @return function the csv iterator 122 | -- @return string the used build 123 | function parser:ReadCSV(name, options) 124 | options = options or {} 125 | CreateFolder(self.CACHE_PATH..name) 126 | local build = self:FindBuild(name, options.build) 127 | local base = GetBaseName(name, build, options) 128 | local path = csv_cache:format(name, base) 129 | if not lfs.attributes(path) then 130 | local url = csv_url:format(name, build) 131 | if options.locale then 132 | url = url.."&locale="..options.locale 133 | end 134 | local success = DownloadFile(url, path) 135 | if not success then return false end 136 | end 137 | print("reading "..path) 138 | local iter = csv.open(path, {header = options.header, buffer_size = 1024*4}) 139 | return iter, build 140 | end 141 | 142 | --- Parses the DBC from JSON 143 | -- @param name the DBC name 144 | -- @param options.build (optional) the build version, otherwise uses the most recent build 145 | -- @param options.locale (optional) the locale, otherwise uses english 146 | -- @return table the converted json table 147 | -- @return string the used build 148 | function parser:ReadJSON(name, options) 149 | options = options or {} 150 | CreateFolder(self.CACHE_PATH..name) 151 | local build = self:FindBuild(name, options.build) 152 | local base = GetBaseName(name, build, options) 153 | local path = json_cache:format(name, base) 154 | if not lfs.attributes(path) then 155 | local initialRequest = DownloadFile(json_url:format(name, build, 0), false) 156 | local recordsTotal = cjson.decode(initialRequest).recordsTotal 157 | local url = json_url:format(name, build, recordsTotal) 158 | if options.locale then 159 | url = url.."&locale="..options.locale 160 | end 161 | local success = DownloadFile(url, path) 162 | if not success then return false end 163 | end 164 | print("reading "..path) 165 | local json = cjsonutil.file_load(path) 166 | local tbl = cjson.decode(json).data 167 | return tbl, build 168 | end 169 | 170 | --- Parses the CSV listfile 171 | function parser:ReadListfile() 172 | if ShouldDownload(listfile_cache) then 173 | print("downloading listfile...") 174 | DownloadFile(listfile_url, listfile_cache) 175 | end 176 | local iter = csv.open(listfile_cache, {separator = ";"}) 177 | local filedata = {} 178 | print("reading listfile...") 179 | for line in iter:lines() do 180 | local fdid, filePath = tonumber(line[1]), line[2] 181 | filedata[fdid] = filePath 182 | end 183 | print("finished reading.") 184 | return filedata 185 | end 186 | 187 | function parser:ExplodeCSV(iter) 188 | for line in iter:lines() do 189 | print(table.unpack(line)) 190 | end 191 | end 192 | 193 | function parser:ExplodeJSON(tbl) 194 | for _, line in pairs(tbl) do 195 | print(table.unpack(line)) 196 | end 197 | end 198 | 199 | function parser:ExplodeListfile(tbl) 200 | for fdid, path in pairs(tbl) do 201 | print(fdid, path) 202 | end 203 | end 204 | 205 | return parser 206 | --------------------------------------------------------------------------------