diff --git a/leaderboard.lua b/leaderboard.lua index 4ceb83e..bc633b5 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -35,7 +35,7 @@ local StatTrack = false local UNCLAIMED = "Unclaimed Record" local HELP_MESSAGE = "\x89Leaderboard Commands:\nretry exit findmap changelevel spba_clearcheats lb_gui rival scroll encore" -local FILENAME = "leaderboard.txt" +local FILENAME = "leaderboard" -- Retry / changelevel map local nextMap = nil @@ -150,15 +150,36 @@ local cv_interrupt = CV_RegisterVar({ end }) +local function djb2(message) + local digest = 5381 + for c in message:gmatch(".") do + digest = (($ << 5) + $) + string.byte(c) + end + + return digest +end + +local function mapChecksum(mapnum) + local mh = mapheaderinfo[mapnum] + if not mh then + return nil + end + + local digest = string.format("%04x", djb2(mh.lvlttl+mh.subttl+mh.zonttl)) + return string.sub(digest, #digest - 3) +end + local function setST(t, map, flags, scoreTable) local mode = flags & ST_SEP + local cks = mapChecksum(map) t[mode] = t[mode] or {} - t[mode][map] = scoreTable + t[mode][tostring(map)..(cks or "")] = scoreTable end local function getST(t, map, flags) local mode = flags & ST_SEP - return t[mode] and t[mode][map] or nil + local cks = mapChecksum(map) + return t[mode] and t[mode][tostring(map)..(cks or "")] or nil end local function setScoreTable(map, flags, scoreTable) @@ -222,7 +243,7 @@ local cv_spb_separate = CV_RegisterVar({ end }) -local function score_t(map, name, skin, color, time, splits, flags, stat) +local function score_t(map, name, skin, color, time, splits, flags, stat, checksum) return { ["map"] = map, ["name"] = name, @@ -231,7 +252,8 @@ local function score_t(map, name, skin, color, time, splits, flags, stat) ["time"] = time, ["splits"] = splits, ["flags"] = flags, - ["stat"] = stat + ["stat"] = stat, + ["checksum"] = checksum } end @@ -253,12 +275,47 @@ local function stat_str(stat) return "0" end +local function writeScore(fh, score) + fh:write( + score["map"], "\t", + score["name"], "\t", + score["skin"], "\t", + score["color"], "\t", + score["time"], "\t", + table.concat(score["splits"], " "), "\t", + score["flags"], "\t", + stat_str(score["stat"]), "\t", + score["checksum"], "\n" + ) +end + +local function saveScores() + local f = assert(io.open(FILENAME + ".txt", "w")) + if f == nil then + print("Failed to open file for writing: " + FILENAME + ".txt") + return + end + + for _, tbl in pairs(lb) do + for _, scoreTable in pairs(tbl) do + for _, score in ipairs(scoreTable) do + writeScore(f, score) + end + end + end + + f:close() +end + -- Read the leaderboard -local f = io.open(FILENAME, "r") +local f = io.open(FILENAME + ".txt", "r") if f then + -- track if scores checksums have been written + local checksumWrite = false + for l in f:lines() do -- Leaderboard is stored in the following tab separated format - -- mapnum, name, skin, color, time, splits, flags, stat + -- mapnum, name, skin, color, time, splits, flags, stat, map_checksum local t = {} for word in (l+"\t"):gmatch("(.-)\t") do table.insert(t, word) @@ -287,17 +344,41 @@ if f then end end + local mapnum = tonumber(t[1]) + + -- Checksums + local cks = t[9] + + if cks == nil then + checksumWrite = true + cks = mapChecksum(mapnum) + + -- We have no previous recognition of this map + -- remove it + if cks == nil then + local rsfh = io.open(FILENAME + "_removed_scores.txt", "a") + if not rsfh then + print("Failed to open file: ", FILENAME + "_removed_scores.txt") + continue + end + rsfh:write(l, "\n") + rsfh:close() + continue + end + end + table.insert( scoreTable, score_t( - tonumber(t[1]), + mapnum, t[2], t[3], t[4], tonumber(t[5]), spl, flags, - stats + stats, + cks ) ) @@ -306,8 +387,13 @@ if f then sortScores() f:close() + + -- save scores + if checksumWrite then + saveScores() + end else - print("Failed to open file: ", FILENAME) + print("Failed to open file: ", FILENAME + ".txt") end function allowJoin(v) @@ -1129,7 +1215,8 @@ local function saveTime(player) timeFinished, splits, Flags, - stat_t(player.HMRs or pskin.kartspeed, player.HMRw or pskin.kartweight) + stat_t(player.HMRs or pskin.kartspeed, player.HMRw or pskin.kartweight), + mapChecksum(gamemap) ) -- Check if you beat your previous best @@ -1174,30 +1261,7 @@ local function saveTime(player) StatTrack = true end - local f = assert(io.open(FILENAME, "w")) - if f == nil then - print("Failed to open file for writing: " + FILENAME) - return - end - - for _, tbl in pairs(lb) do - for _, scoreTable in pairs(tbl) do - for _, score in ipairs(scoreTable) do - f:write( - score["map"], "\t", - score["name"], "\t", - score["skin"], "\t", - score["color"], "\t", - score["time"], "\t", - table.concat(score["splits"], " "), "\t", - score["flags"], "\t", - stat_str(score["stat"]), "\n" - ) - end - end - end - - f:close() + saveScores() end -- DEBUGGING