WDBParser = { -- Constructor -- @param input: -- Specifies the input file to use -- @param output: -- Specifies the output file to use -- @param structure: -- Specified the table that describes the file structure new = function(self, input, output, structure) self.__index = self local o = setmetatable({}, self) o.input = io.open(input, "rb") o.output = io.open(output, "w") o.structure = structure o.count = 0 if not o.input then o.input = io.open(string.gsub(input, "\\", "/"), "rb") end if not o.output then o.output = io.open(string.gsub(output, "\\", "/"), "2") end assert(o.input, "Could not open input file: " .. input) assert(o.output, "Could not open output file: " .. output) assert(o.structure, "Must specify a structure in the constructor.") o.buffer = o.input:read("*all") o.input:close() o.index = 1 o.len = string.len(o.buffer) print("Reading file with header: " .. self.readAny(o, 20)) return o end, -- WDBParser:readAny() -- Used as a debug function, to read an arbitrary length of bytes in from the buffer readAny = function(self, len) local t = "" for i=1,len do local c = self:read(1) if string.find(c, "%w") then t = t .. c else t = string.format("%s0x%02X", t, string.byte(c)) end end return t end, -- WDBParser:readInteger() -- Reads an integer into the system. Treats all integer input as -- 32-bit signed integers. readInteger = function(self) local num = self:readBinary() return self:bin2dec(num) end, -- WDBParser:readBinary() -- Reads 4 bytes of data from a binary file, and displays it in true binary format -- converts from ASCII display characters to their binary representation readBinary = function(self) local a,b,c,d = self:read(1),self:read(1),self:read(1),self:read(1) a,b,c,d = string.byte(a),string.byte(b),string.byte(c),string.byte(d) a,b,c,d = self:dec2bin(a),self:dec2bin(b),self:dec2bin(c),self:dec2bin(d) return d..c..b..a end, -- WDBParser:readString() -- Reads a null-terminated string in from the standard buffer -- Returns the string without trailing 0x00 readString = function(self) local txt = "" while true do local b = self:read(1) if b == string.char(0) then return txt else txt = txt .. b end end end, -- WDBParser:readString() -- Reads a null-terminated string in from the standard buffer -- Returns the string without trailing 0x00 readFloat = function(self) local num = self:readBinary() local sign = string.sub(num,1,1) local exp = string.sub(num,2,9) local mantissa = string.sub(num,10,-1) if sign == "1" then sign = -1 else sign = 1 end exp = self:bin2dec(exp) - 127 mantissa = self:bin2float(mantissa) if exp < 0 then return 0 end return mantissa * math.pow(2, exp) end, -- WDBParser:bin2dec() -- Converts a number from IEEE uint32 format to a decimal number bin2dec = function(self, num) local sign,int if string.sub(num, 1, 1) == "1" then -- Take twos complement, one's complement it and add one num = string.gsub(num, "0", "*") num = string.gsub(num, "1", "0") num = string.gsub(num, "*", "1") num = tonumber(num, 2) + 1 num = num * -1 else num = tonumber(num, 2) end return num end, -- WDBParser:dec2bin() -- Takes an 8 bit number and coverts it to binary representation -- Used as a helper function for WDBParser:readBin() dec2bin = function(self, num) local i = {128,64,32,16,8,4,2,1} local output = "" for k,v in ipairs(i) do if num >= v then num = num - v output = output .. "1" else output = output .. "0" end end return output end, -- WDBParser:bin2float() -- Used to convert a mantissa into decimal form for final conversion -- Takes a length of bits, and converts it with the most signifigant -- digit being 1/2, followed by 1/4, and so on. bin2float = function(self, num) local answer = 1.0 for i=1,string.len(num) do if tonumber(string.sub(num,i,i)) == 1 then answer = answer + (1 / math.pow(2, i)) end end return answer end, -- WDBParser:readItem() -- Reads the next item in the file, given the table structure -- Currently handles Integer, BitMask, Float and String input. readItem = function(self) self.item = {} self.count = 0 for k,v in ipairs(self.structure) do self.field = v.field if v.type == "Integer" then self.item[v.field] = self:readInteger() elseif v.type == "BitMask" then self.item[v.field] = self:readBinary() elseif v.type == "Float" then self.item[v.field] = self:readFloat() elseif v.type == "String" then self.item[v.field] = self:readString() else error("Error in structure") end end return self.item end, -- WDBParser:printHeader() -- Used in CSV output. Prints the column header printHeader = function(self) for k,v in ipairs(self.structure) do self.output:write(v.field..",") end self.output:write('\n') end, -- WDBParser:printItem() -- Parses the file and writes the listing to the output file. printItem = function(self) local i = self:readItem() if not i then return end for k,v in ipairs(self.structure) do local t = i[v.field] if string.find(t, ",") then i[v.field] = string.format("\"%s\"", string.gsub(i[v.field], "\"", "'")) end if v.type == "BitMask" then i[v.field] = string.sub(i[v.field], -8, -1) end self.output:write(i[v.field]..",") end self.output:write("," .. "http://wow.allakhazam.com/db/item.html?witem=" .. i.itemID) self.output:write('\n') end, -- WDBParser:parse() -- Performs a parse of the loaded file parse = function(self) self:printHeader() local i = 1 while not self:eof() do i = i + 1 self:printItem() end end, -- WDBParser:eof() -- Used as a check to see whether we're out of range on a file eof = function(self) return self.index >= (self.len - 8) end, -- WDBParser:read(len) -- Performs an arbitrary read of len bytes and returns it in ASCII format read = function(self, len) if (self.index + len) > self.len then self:error("Attempt to read beyond the end of file. Index: %s, Length: %s, BufferLength: %s", self.index, len, self.len) else local t = string.sub(self.buffer, self.index, self.index + (len - 1)) self.index = self.index + len return t end end, -- WDBParser:error(format, ...) -- More robust error function, to allow for string.format() implicitly error = function(self, format, ...) error(string.format(format, unpack(arg))) end,}local structure = {-- These are parsed specially in parseItem {field = "itemID", type = "Integer"}, {field = "entryLength", type = "Integer"}, {field = "itemClassID", type = "Integer"}, {field = "itemSubClassID", type = "Integer"}, {field = "name1", type = "String"}, {field = "name2", type = "String"}, {field = "name3", type = "String"}, {field = "name4", type = "String"}, {field = "itemdisplayID", type = "Integer"}, {field = "qualityID", type = "Integer"}, {field = "typeBinFlag", type = "BitMask"}, {field = "buyPrice", type = "Integer"}, {field = "sellPrice", type = "Integer"}, {field = "inventorySlotID", type = "Integer"}, {field = "classBinFlag", type = "BitMask"}, {field = "raceBinFlag", type = "BitMask"}, {field = "itemLevel", type = "Integer"}, {field = "requiredLevel", type = "Integer"}, {field = "requiredSkillID", type = "Integer"}, {field = "requiredSkillLevel", type = "Integer"}, {field = "requiredSpellID", type = "Integer"}, {field = "requiredRankID", type = "Integer"}, {field = "Unknown", type = "Integer"}, {field = "requiredFactionID", type = "Integer"}, {field = "requiredFactionLevel", type = "Integer"}, {field = "isUnique", type = "BitMask"}, {field = "stackAmount", type = "Integer"}, {field = "containerSlots", type = "Integer"}, {field = "stat1ID", type = "Integer"}, {field = "stat1Value", type = "Integer"}, {field = "stat2ID", type = "Integer"}, {field = "stat2Value", type = "Integer"}, {field = "stat3ID", type = "Integer"}, {field = "stat3Value", type = "Integer"}, {field = "stat4ID", type = "Integer"}, {field = "stat4Value", type = "Integer"}, {field = "stat5ID", type = "Integer"}, {field = "stat5Value", type = "Integer"}, {field = "stat6ID", type = "Integer"}, {field = "stat6Value", type = "Integer"}, {field = "stat7ID", type = "Integer"}, {field = "stat7Value", type = "Integer"}, {field = "stat8ID", type = "Integer"}, {field = "stat8Value", type = "Integer"}, {field = "stat9ID", type = "Integer"}, {field = "stat9Value", type = "Integer"}, {field = "stat10ID", type = "Integer"}, {field = "stat10Value", type = "Integer"}, {field = "damage1Min", type = "Float"}, {field = "damage1Max", type = "Float"}, {field = "damage1TypeID", type = "Integer"}, {field = "damage2Min", type = "Float"}, {field = "damage2Max", type = "Float"}, {field = "damage2TypeID", type = "Integer"}, {field = "damage3Min", type = "Float"}, {field = "damage3Max", type = "Float"}, {field = "damage3TypeID", type = "Integer"}, {field = "damage4Min", type = "Float"}, {field = "damage4Max", type = "Float"}, {field = "damage4TypeID", type = "Integer"}, {field = "damage5Min", type = "Float"}, {field = "damage5Max", type = "Float"}, {field = "damage5TypeID", type = "Integer"}, {field = "resistPhysical", type = "Integer"}, {field = "resistHoly", type = "Integer"}, {field = "resistFire", type = "Integer"}, {field = "resistNature", type = "Integer"}, {field = "resistFrost", type = "Integer"}, {field = "resistShadow", type = "Integer"}, {field = "resistArcane", type = "Integer"}, {field = "weaponDelay", type = "Integer"}, {field = "ammoType", type = "Integer"}, {field = "rangeModifier", type = "Float"}, {field = "spell1ID", type = "Integer"}, {field = "spell1TriggerID", type = "Integer"}, {field = "spell1Charges", type = "Integer"}, {field = "spell1Cooldown", type = "Integer"}, {field = "spell1CategoryID", type = "Integer"}, {field = "spell1CategoryCooldown", type = "Integer"}, {field = "spell2ID", type = "Integer"}, {field = "spell2TriggerID", type = "Integer"}, {field = "spell2Charges", type = "Integer"}, {field = "spell2Cooldown", type = "Integer"}, {field = "spell2CategoryID", type = "Integer"}, {field = "spell2CategoryCooldown", type = "Integer"}, {field = "spell3ID", type = "Integer"}, {field = "spell3TriggerID", type = "Integer"}, {field = "spell3Charges", type = "Integer"}, {field = "spell3Cooldown", type = "Integer"}, {field = "spell3CategoryID", type = "Integer"}, {field = "spell3CategoryCooldown", type = "Integer"}, {field = "spell4ID", type = "Integer"}, {field = "spell4TriggerID", type = "Integer"}, {field = "spell4Charges", type = "Integer"}, {field = "spell4Cooldown", type = "Integer"}, {field = "spell4CategoryID", type = "Integer"}, {field = "spell4CategoryCooldown", type = "Integer"}, {field = "spell5ID", type = "Integer"}, {field = "spell5TriggerID", type = "Integer"}, {field = "spell5Charges", type = "Integer"}, {field = "spell5Cooldown", type = "Integer"}, {field = "spell5CategoryID", type = "Integer"}, {field = "spell5CategoryCooldown", type = "Integer"}, {field = "bondID", type = "Integer"}, {field = "description", type = "String"}, {field = "bookTextID", type = "Integer"}, {field = "bookPages", type = "Integer"}, {field = "bookStationaryID", type = "Integer"}, {field = "beginQuestID", type = "Integer"}, {field = "lockPickTalent", type = "Integer"}, {field = "materialID", type = "Integer"}, {field = "sheathID", type = "Integer"}, {field = "randompropertyID", type = "Integer"}, {field = "blockValue", type = "Integer"}, {field = "itemSetID", type = "Integer"}, {field = "durabilityValue", type = "Integer"}, {field = "itemAreaID", type = "Integer"}, {field = "itemMapID", type = "Integer"}, {field = "bagFamily", type = "Integer"},}local parser = WDBParser:new("g:\\world of warcraft\\wowtest\\wdb\\itemcache.wdb", "c:\\temp\\output.csv", structure)parser:parse()