-- to use this get the package from https://greatstoneex.github.io/hi2txt-doc/ -- extract the hi2txt.zip and place it in your history path local dat = {} local env = {} local output local curset local path = mame_manager.ui.options.entries.historypath:value():gsub("([^;]+)", "%1/hi2txt") local htmlentites = { ["amp"] = "&", ["quot"] = utf8.char(34), ["big-mid-dot"] = utf8.char(149), ["copyright"] = utf8.char(169), ["mid-dot"] = utf8.char(183), ["one-on-two"] = utf8.char(189), ["ring"] = utf8.char(214), ["acute"] = utf8.char(225), ["y-strike"] = utf8.char(590), ["bridge1"] = utf8.char(761), ["bridge2"] = utf8.char(765), ["bridge3"] = utf8.char(766), ["alpha"] = utf8.char(945), ["beta"] = utf8.char(946), ["gamma"] = utf8.char(947), ["delta"] = utf8.char(948), ["epsilon"] = utf8.char(949), ["zeta"] = utf8.char(950), ["eta"] = utf8.char(951), ["theta"] = utf8.char(952), ["iota"] = utf8.char(953), ["kappa"] = utf8.char(954), ["lambda"] = utf8.char(955), ["mu"] = utf8.char(956), ["nu"] = utf8.char(957), ["xi"] = utf8.char(958), ["omicron"] = utf8.char(959), ["pi"] = utf8.char(960), ["rho"] = utf8.char(961), ["sigmaf"] = utf8.char(962), ["sigma"] = utf8.char(963), ["tau"] = utf8.char(964), ["upsilon"] = utf8.char(965), ["phi"] = utf8.char(966), ["chi"] = utf8.char(967), ["psi"] = utf8.char(968), ["omega"] = utf8.char(969), ["circle-line"] = utf8.char(984), ["two-dots"] = utf8.char(1417), ["inverted-question"] = utf8.char(1567), ["rdquo"] = utf8.char(8221), ["big-dot"] = utf8.char(8226), ["three-dots"] = utf8.char(8230), ["two-exclamations"] = utf8.char(8252), ["broken-question"] = utf8.char(8253), ["asterism"] = utf8.char(8258), ["w-double-strike"] = utf8.char(8361), ["square-2"] = utf8.char(8414), ["roman-numeral-1"] = utf8.char(8544), ["roman-numeral-2"] = utf8.char(8545), ["roman-numeral-3"] = utf8.char(8546), ["roman-numeral-4"] = utf8.char(8547), ["roman-numeral-5"] = utf8.char(8548), ["roman-numeral-6"] = utf8.char(8549), ["roman-numeral-7"] = utf8.char(8550), ["roman-numeral-8"] = utf8.char(8551), ["roman-numeral-9"] = utf8.char(8552), ["roman-numeral-10"] = utf8.char(8553), ["roman-numeral-11"] = utf8.char(8554), ["roman-numeral-12"] = utf8.char(8555), ["small-roman-numeral-1"] = utf8.char(8560), ["small-roman-numeral-2"] = utf8.char(8561), ["small-roman-numeral-3"] = utf8.char(8562), ["small-roman-numeral-4"] = utf8.char(8563), ["small-roman-numeral-5"] = utf8.char(8564), ["small-roman-numeral-6"] = utf8.char(8565), ["small-roman-numeral-7"] = utf8.char(8566), ["small-roman-numeral-8"] = utf8.char(8567), ["small-roman-numeral-9"] = utf8.char(8568), ["small-roman-numeral-10"] = utf8.char(8569), ["small-roman-numeral-11"] = utf8.char(8570), ["small-roman-numeral-12"] = utf8.char(8571), ["left-arrow"] = utf8.char(8592), ["right-double-arrow"] = utf8.char(8658), ["four-lines"] = utf8.char(8803), ["three-mid-dots"] = utf8.char(8943), ["left-foot"] = utf8.char(8968), ["right-foot"] = utf8.char(8968), ["round-7"] = utf8.char(9318), ["square"] = utf8.char(9633), ["dot-in-square"] = utf8.char(9635), ["h-lines-in-square"] = utf8.char(9636), ["v-lines-in-square"] = utf8.char(9637), ["slash-in-square"] = utf8.char(9639), ["antislash-in-square"] = utf8.char(9640), ["black-triangle-right"] = utf8.char(9658), ["black-triangle-down"] = utf8.char(9660), ["two-cubes"] = utf8.char(9707) , ["umbrella"] = utf8.char(9730), ["snowman"] = utf8.char(9731) , ["black-star"] = utf8.char(9733), ["star"] = utf8.char(9734), ["headset"] = utf8.char(9738), ["phone"] = utf8.char(9742), ["hot-beverage"] = utf8.char(9749), ["skull"] = utf8.char(9760), ["ankh"] = utf8.char(9765), ["cross-of-lorraine"] = utf8.char(9768), ["cross-of-jerusalem"] = utf8.char(9769), ["peace"] = utf8.char(9774), ["angry-face"] = utf8.char(9785), ["smiley"] = utf8.char(9786), ["black-smiley"] = utf8.char(9787), ["sun"] = utf8.char(9788), ["moon"] = utf8.char(9789), ["crescent-moon"] = utf8.char(9790), ["woman"] = utf8.char(9792), ["man"] = utf8.char(9794), ["spaceship"] = utf8.char(9798), ["aries"] = utf8.char(9800), ["taurus"] = utf8.char(9801), ["gemini"] = utf8.char(9802), ["cancer"] = utf8.char(9803), ["leo"] = utf8.char(9804), ["virgo"] = utf8.char(9805), ["libra"] = utf8.char(9806), ["scorpio"] = utf8.char(9807), ["sagitarius"] = utf8.char(9808), ["capricorn"] = utf8.char(9809), ["aquarius"] = utf8.char(9810), ["pisces"] = utf8.char(9811), ["amber"] = utf8.char(9816) , ["black-spade"] = utf8.char(9824) , ["heart"] = utf8.char(9825) , ["black-club"] = utf8.char(9827) , ["black-heart"] = utf8.char(9829) , ["black-diamond"] = utf8.char(9830) , ["single-music-note"] = utf8.char(9834), ["double-music-note"] = utf8.char(9835), ["crossed-swords"] = utf8.char(9876), ["baseball"] = utf8.char(9918), ["boat"] = utf8.char(9973), ["scissors"] = utf8.char(9988), ["airplane"] = utf8.char(9992), ["multiplication"] = utf8.char(10005), ["big-exclamation"] = utf8.char(10082), ["left-black-heart"] = utf8.char(10085), ["black-right-arrow-large"] = utf8.char(10152), ["up-arrow-with-stroke"] = utf8.char(10505), ["jp-h-a-small"] = utf8.char(12353), ["jp-h-a"] = utf8.char(12354), ["jp-h-i-small"] = utf8.char(12355), ["jp-h-i"] = utf8.char(12356), ["jp-h-u-small"] = utf8.char(12357), ["jp-h-u"] = utf8.char(12358), ["jp-h-e-small"] = utf8.char(12359), ["jp-h-e"] = utf8.char(12360), ["jp-h-o-small"] = utf8.char(12361), ["jp-h-o"] = utf8.char(12362), ["jp-h-ka"] = utf8.char(12363), ["jp-h-ga"] = utf8.char(12364), ["jp-h-ki"] = utf8.char(12365), ["jp-h-gi"] = utf8.char(12366), ["jp-h-ku"] = utf8.char(12367), ["jp-h-gu"] = utf8.char(12368), ["jp-h-ke"] = utf8.char(12369), ["jp-h-ge"] = utf8.char(12370), ["jp-h-ko"] = utf8.char(12371), ["jp-h-go"] = utf8.char(12372), ["jp-h-sa"] = utf8.char(12373), ["jp-h-za"] = utf8.char(12374), ["jp-h-si"] = utf8.char(12375), ["jp-h-zi"] = utf8.char(12376), ["jp-h-su"] = utf8.char(12377), ["jp-h-zu"] = utf8.char(12378), ["jp-h-se"] = utf8.char(12379), ["jp-h-ze"] = utf8.char(12380), ["jp-h-so"] = utf8.char(12381), ["jp-h-zo"] = utf8.char(12382), ["jp-h-ta"] = utf8.char(12383), ["jp-h-da"] = utf8.char(12384), ["jp-h-ti"] = utf8.char(12385), ["jp-h-di"] = utf8.char(12386), ["jp-h-tu-small"] = utf8.char(12387), ["jp-h-sokuon"] = utf8.char(12387), ["jp-h-tu"] = utf8.char(12388), ["jp-h-du"] = utf8.char(12389), ["jp-h-te"] = utf8.char(12390), ["jp-h-de"] = utf8.char(12391), ["jp-h-to"] = utf8.char(12392), ["jp-h-do"] = utf8.char(12393), ["jp-h-na"] = utf8.char(12394), ["jp-h-ni"] = utf8.char(12395), ["jp-h-nu"] = utf8.char(12396), ["jp-h-ne"] = utf8.char(12397), ["jp-h-no"] = utf8.char(12398), ["jp-h-ha"] = utf8.char(12399), ["jp-h-ba"] = utf8.char(12400), ["jp-h-pa"] = utf8.char(12401), ["jp-h-hi"] = utf8.char(12402), ["jp-h-bi"] = utf8.char(12403), ["jp-h-pi"] = utf8.char(12404), ["jp-h-hu"] = utf8.char(12405), ["jp-h-bu"] = utf8.char(12406), ["jp-h-pu"] = utf8.char(12407), ["jp-h-he"] = utf8.char(12408), ["jp-h-be"] = utf8.char(12409), ["jp-h-pe"] = utf8.char(12410), ["jp-h-ho"] = utf8.char(12411), ["jp-h-bo"] = utf8.char(12412), ["jp-h-po"] = utf8.char(12413), ["jp-h-ma"] = utf8.char(12414), ["jp-h-mi"] = utf8.char(12415), ["jp-h-mu"] = utf8.char(12416), ["jp-h-me"] = utf8.char(12417), ["jp-h-mo"] = utf8.char(12418), ["jp-h-ya-small"] = utf8.char(12419), ["jp-h-youon-a"] = utf8.char(12419), ["jp-h-ya"] = utf8.char(12420), ["jp-h-yu-small"] = utf8.char(12421), ["jp-h-youon-u"] = utf8.char(12421), ["jp-h-yu"] = utf8.char(12422), ["jp-h-yo-small"] = utf8.char(12423), ["jp-h-youon-o"] = utf8.char(12423), ["jp-h-yo"] = utf8.char(12424), ["jp-h-ra"] = utf8.char(12425), ["jp-h-ri"] = utf8.char(12426), ["jp-h-ru"] = utf8.char(12427), ["jp-h-re"] = utf8.char(12428), ["jp-h-ro"] = utf8.char(12429), ["jp-h-wa-small"] = utf8.char(12430), ["jp-h-wa"] = utf8.char(12431), ["jp-h-wi"] = utf8.char(12432), ["jp-h-we"] = utf8.char(12433), ["jp-h-wo"] = utf8.char(12434), ["jp-h-n"] = utf8.char(12435), ["jp-h-vu"] = utf8.char(12436), ["jp-h-ka-small"] = utf8.char(12437), ["jp-h-ke-small"] = utf8.char(12438), ["jp-h-dakuten"] = utf8.char(12443), ["jp-h-handakuten"] = utf8.char(12444), ["jp-k-a-small"] = utf8.char(12449), ["jp-k-a"] = utf8.char(12450), ["jp-k-i-small"] = utf8.char(12451), ["jp-k-i"] = utf8.char(12452), ["jp-k-u-small"] = utf8.char(12453), ["jp-k-u"] = utf8.char(12454), ["jp-k-e-small"] = utf8.char(12455), ["jp-k-e"] = utf8.char(12456), ["jp-k-o-small"] = utf8.char(12457), ["jp-k-o"] = utf8.char(12458), ["jp-k-ka"] = utf8.char(12459), ["jp-k-ga"] = utf8.char(12460), ["jp-k-ki"] = utf8.char(12461), ["jp-k-gi"] = utf8.char(12462), ["jp-k-ku"] = utf8.char(12463), ["jp-k-gu"] = utf8.char(12464), ["jp-k-ke"] = utf8.char(12465), ["jp-k-ge"] = utf8.char(12466), ["jp-k-ko"] = utf8.char(12467), ["jp-k-go"] = utf8.char(12468), ["jp-k-sa"] = utf8.char(12469), ["jp-k-za"] = utf8.char(12470), ["jp-k-si"] = utf8.char(12471), ["jp-k-zi"] = utf8.char(12472), ["jp-k-su"] = utf8.char(12473), ["jp-k-zu"] = utf8.char(12474), ["jp-k-se"] = utf8.char(12475), ["jp-k-ze"] = utf8.char(12476), ["jp-k-so"] = utf8.char(12477), ["jp-k-zo"] = utf8.char(12478), ["jp-k-ta"] = utf8.char(12479), ["jp-k-da"] = utf8.char(12480), ["jp-k-ti"] = utf8.char(12481), ["jp-k-di"] = utf8.char(12482), ["jp-k-tu-small"] = utf8.char(12483), ["jp-k-tu"] = utf8.char(12484), ["jp-k-du"] = utf8.char(12485), ["jp-k-te"] = utf8.char(12486), ["jp-k-de"] = utf8.char(12487), ["jp-k-to"] = utf8.char(12488), ["jp-k-do"] = utf8.char(12489), ["jp-k-na"] = utf8.char(12490), ["jp-k-ni"] = utf8.char(12491), ["jp-k-nu"] = utf8.char(12492), ["jp-k-ne"] = utf8.char(12493), ["jp-k-no"] = utf8.char(12494), ["jp-k-ha"] = utf8.char(12495), ["jp-k-ba"] = utf8.char(12496), ["jp-k-pa"] = utf8.char(12497), ["jp-k-hi"] = utf8.char(12498), ["jp-k-bi"] = utf8.char(12499), ["jp-k-pi"] = utf8.char(12500), ["jp-k-hu"] = utf8.char(12501), ["jp-k-bu"] = utf8.char(12502), ["jp-k-pu"] = utf8.char(12503), ["jp-k-he"] = utf8.char(12504), ["jp-k-be"] = utf8.char(12505), ["jp-k-pe"] = utf8.char(12506), ["jp-k-ho"] = utf8.char(12507), ["jp-k-bo"] = utf8.char(12508), ["jp-k-po"] = utf8.char(12509), ["jp-k-ma"] = utf8.char(12510), ["jp-k-mi"] = utf8.char(12511), ["jp-k-mu"] = utf8.char(12512), ["jp-k-me"] = utf8.char(12513), ["jp-k-mo"] = utf8.char(12514), ["jp-k-ya-small"] = utf8.char(12515), ["jp-k-ya"] = utf8.char(12516), ["jp-k-yu-small"] = utf8.char(12517), ["jp-k-yu"] = utf8.char(12518), ["jp-k-yo-small"] = utf8.char(12519), ["jp-k-yo"] = utf8.char(12520), ["jp-k-ra"] = utf8.char(12521), ["jp-k-ri"] = utf8.char(12522), ["jp-k-ru"] = utf8.char(12523), ["jp-k-re"] = utf8.char(12524), ["jp-k-ro"] = utf8.char(12525), ["jp-k-wa-small"] = utf8.char(12526), ["jp-k-wa"] = utf8.char(12527), ["jp-k-wi"] = utf8.char(12528), ["jp-k-we"] = utf8.char(12529), ["jp-k-wo"] = utf8.char(12530), ["jp-k-n"] = utf8.char(12531), ["jp-k-vu"] = utf8.char(12532), ["jp-k-ka-small"] = utf8.char(12533), ["jp-k-ke-small"] = utf8.char(12534), ["jp-k-va"] = utf8.char(12535), ["jp-k-vi"] = utf8.char(12536), ["jp-k-ve"] = utf8.char(12537), ["jp-k-vo"] = utf8.char(12538), ["jp-k-zero"] = utf8.char(38646), ["jp-k-one"] = utf8.char(19968), ["jp-k-two"] = utf8.char(20108), ["jp-k-three"] = utf8.char(19977), ["jp-k-four"] = utf8.char(22235), ["jp-k-five"] = utf8.char(20116), ["jp-k-six"] = utf8.char(20845), ["jp-k-seven"] = utf8.char(19971), ["jp-k-height"] = utf8.char(20843), ["jp-k-nine"] = utf8.char(20061), ["lama"] = "lama", ["cat-face"] = utf8.char(9786) , ["whale"] = "whale", ["thumbs-up"] = utf8.char(8730) , ["shoe"] = "shoe", ["kiss"] = utf8.char(9786), ["heart-with-arrow"] = utf8.char(9829), ["car-side"] = utf8.char(9936), ["car-front"] = utf8.char(9936), ["mens-symbol"] = utf8.char(9794), ["womens-symbol"] = utf8.char(9792), ["feet"] = utf8.char(128062) } function env.open(file, size, swap) if file == ".hi" then local path = "hi" local ini = emu.file(manager.options.entries.inipath:value(), 1) local ret = ini:open("hiscore.ini") if not ret then local inifile = ini:read(ini:size()) for line in inifile:gmatch("[^\n\r]") do token, value = string.match(line, '([^ ]+) ([^ ]+)'); if token == "hi_path" then path = value break end end end file = path .. "/" .. curset .. ".hi" else file = manager.options.entries.nvram_directory:value() .. "/" .. curset .. "/" .. file end local f = io.open(file, "rb") local content = f:read("*all") f:close() if #content < size then content = content .. string.rep("\0", size - #content) end if swap then if swap == 2 then content = content:gsub("(.)(.)", function(c1, c2) return c2 .. c1 end) elseif swap == 4 then content = content:gsub("(.)(.)(.)(.)", function(c1, c2, c3, c4) return c4 .. c3 .. c2 .. c1 end) else emu.print_verbose("swap " .. swap .. " not supported") end end return content end function env.endianness(bytes, endian) local newbytes = {} if endian == "little_endian" then for i = 1, #bytes do newbytes[i] = bytes[#bytes - i + 1] end else newbytes = bytes end return newbytes end function env.byte_skip(bytes, skip) local newbytes = {} if skip == "odd" then -- lua lists are 1 based so use even indexes for i = 2, #bytes, 2 do newbytes[i/2] = bytes[i] end elseif skip == "even" then for i = 1, #bytes, 2 do newbytes[(i+1)/2] = bytes[i] end elseif skip == "1000" then for i = 1, #bytes, 4 do newbytes[(i+3)/4] = bytes[i] end elseif skip == "0100" then for i = 2, #bytes, 4 do newbytes[(i+2)/4] = bytes[i] end elseif skip == "0010" then for i = 3, #bytes, 4 do newbytes[(i+1)/4] = bytes[i] end elseif skip == "0001" then for i = 4, #bytes, 4 do newbytes[i/4] = bytes[i] end else skip = tonumber(skip) for i = 1, #bytes do if bytes[i] ~= skip then newbytes[#newbytes + 1] = bytes[i] end end end return newbytes end function env.byte_trim(bytes, val) val = tonumber(val) len = #bytes for i = 1, len do if bytes[1] ~= val then return bytes end table.remove(bytes, 1) end return bytes end function env.byte_trunc(bytes, val) val = tonumber(val) for i = 1, #bytes do if bytes[i] == val then break end end while #bytes >= i do table.remove(bytes) end return bytes end function env.byte_swap(bytes, val) local newbytes = {} val = tonumber(val) for i = 1, #bytes do local off = i + val - 1 - 2 * ((i - 1) % val) if off > #bytes then -- ?? break end newbytes[i] = bytes[off] end return newbytes end function env.nibble_skip(bytes, skip) local newbytes = {} if skip == "odd" then for i = 1, #bytes, 2 do val1 = bytes[i]:byte(1) val2 = bytes[i+1]:byte(1) newbytes[(i+1)/2] = string.char(((val1 & 0x0f) << 4) | (val2 & 0x0f)) end elseif skip == "even" then for i = 1, #bytes, 2 do val1 = bytes[i]:byte(1) val2 = bytes[i+1]:byte(1) newbytes[(i+1)/2] = string.char((val1 & 0xf0) | ((val2 & 0xf0) >> 4)) end end return newbytes end function env.bit_swap(bytes, swap) if swap == "yes" then for i = 1, #bytes do val = bytes[i]:byte(1) bytes[i] = string.char(((val & 1) << 7) | ((val & 2) << 5) | ((val & 4) << 3) | ((val & 8) << 1) | ((val & 0x10) >> 1) | ((val & 0x20) >> 3) | ((val & 0x40) >> 5) | ((val & 0x80) >> 7)) end end return bytes end function env.bitmask(bytes, masks) local newbytes = {} bytes = table.concat(bytes) bytes = table.pack(string.unpack(string.rep("c1", #bytes), bytes)) bytes[#bytes] = nil for num, mask in ipairs(masks) do newbytes[#newbytes + 1] = "" for num2, bytemask in ipairs(mask.mask) do local val = bytes[num2]:byte() & bytemask if val ~= 0 then newbytes[#newbytes] = newbytes[#newbytes] .. string.char(val) end end end return newbytes end function env.frombcd(val) local result = 0 local mul = 1 while val ~= 0 do result = result + ((val % 16) * mul) val = val >> 4 mul = mul * 10 end return result end function env.basechar(bytes, base) if base == 32 or base == 40 then local newbytes = {} for num, char in ipairs(bytes) do local nchar = (char:byte(1) << 8) + char:byte(2) local pos = #newbytes for i = 1, 3 do table.insert(newbytes, pos + 1, string.char(nchar % base)) nchar = nchar // base end end return newbytes end emu.print_verbose("data_hiscore: basechar " .. base .. " unimplemented") return bytes end function env.charset_conv(bytes, charset, aoffset) if not aoffset then aoffset = 0 end if type(charset) == "string" then local chartype, offset, delta = charset:match("CS_(%w*)%[?(%-?%d?%d?),?(%d?%d?)%]?") if offset then offset = tonumber(offset) else offset = 0 end if delta then delta = tonumber(delta) else delta = 1 end if chartype == "NUMBER" then for num, char in ipairs(bytes) do char = char:byte() - aoffset - offset if char >= 48 and char <= 57 then bytes[num] = string.char(char) end end return bytes end emu.print_verbose("data_hiscore: charset " .. chartype .. " unimplemented") return bytes end for num, char in ipairs(bytes) do char = char:byte() - aoffset if charset[char] then bytes[num] = charset[char] elseif charset.default then bytes[num] = charset.default end end return bytes end function env.ascii_step(bytes, step) for num, char in ipairs(bytes) do bytes[num] = string.char(char:byte() / step) end return bytes end function env.ascii_offset(bytes, offset) for num, char in ipairs(bytes) do bytes[num] = string.char(char:byte() + offset) end return bytes end function env.index_from_value(col, index) for i = 0, #col - 1 do if col[i].val == index then return i end end return index end env.tostring = tostring env.type = type env.table = { pack = table.pack, concat = table.concat } env.string = { unpack = string.unpack, format = string.format, rep = string.rep, gsub = string.gsub, lower = string.lower, upper = string.upper } env.math = { min = math.min, max = math.max, floor = math.floor } do local function readonly(t) local mt = { __index = t, __newindex = function(t, k, v) return end } return setmetatable({}, mt) end env.table = readonly(env.table) env.string = readonly(env.string) env.math = readonly(env.math) env = readonly(env) end function dat.check(set, softlist) if softlist then return nil end local function xml_parse(file) local table local data = file:read(file:size()) data = data:match("(.*)") local function get_tags(str, parent) local arr = {} while str ~= "" do local tag, attr, stop tag, attr, stop, str = str:match("<([%w!_%-]+) ?(.-)(/?)[ %-]->(.*)") if not tag then return arr end if tag:sub(0, 3) ~= "!--" then local block = {} if stop ~= "/" then local nest nest, str = str:match("(.-)(.*)") local children = get_tags(nest, tag) if not next(children) then nest = nest:gsub("