nextcloud-helper/games/wow/wowmerge.lua

462 lines
No EOL
13 KiB
Lua

--
-- *** IMPORTANT ***
-- *** Set this to your World of Warcraft retail installation directory ***
-- *** Use forward slashes ONLY and ensure a forward slash is at the end ***
-- *** IMPORTANT ***
--
-- example:
-- wowdir = 'C:/Program Files (x86)/World of Warcraft/_retail_/'
--wowdir = 'C:/Program Files (x86)/World of Warcraft/_retail_/'
wowdir = 'C:/Games_SSD/World of Warcraft/_retail_/'
--masterdb = wowdir..'altoholic_merge/'
masterdb = os.getenv('USERPROFILE')..'/Nextcloud/hardlinks/wow-altoholic/'
--<QueryList>
-- <Query Id="0" Path="Security">
-- <Select Path="Security">
--*[System[(EventID=4689)]]
-- and
--*[EventData[Data="C:\Games_SSD\World of Warcraft\_retail_\Wow.exe"]]
--</Select>
-- </Query>
--</QueryList>
-- Lua implementation of PHP scandir function
function listdir(directory)
local i, t, popen = 0, {}, io.popen
for filename in popen('dir "'..directory..'" /b'):lines() do
--msg(filename)
i = i + 1
t[i] = filename
end
return t
end
--- Check if a file or directory exists in this path
function exists(file)
local ok, err, code = os.rename(file, file)
if not ok then
if code == 13 then
-- Permission denied, but it exists
return true
end
end
return ok, err
end
--- Check if a directory exists in this path
function isdir(path)
-- "/" works on both Unix and Windows
return exists(path.."/")
end
-- string splitter
function split(source, delimiters)
local elements = {}
local pattern = '([^'..delimiters..']+)'
string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end);
return elements
end
-- lua keywords
local keywords = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true,
["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true, ["function"]=true,
["if"]=true, ["in"]=true, ["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true,
["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true, ["until"]=true, ["while"]=true}
local function isLuaIdentifier(str)
if type(str) ~= "string" then return false end
-- must be nonempty
if str:len() == 0 then return false end
-- can only contain a-z, A-Z, 0-9 and underscore
if str:find("[^%d%a_]") then return false end
-- cannot begin with digit
if tonumber(str:sub(1, 1)) then return false end
-- cannot be keyword
if keywords[str] then return false end
return true
end
-- wrapper for r_repr that initializes depth at 0
depth = 0
function repr(value)
depth = 0
-- begin recursive repr
return r_repr(value)
end
-- lobotomized repr function from repr.lua$
function r_repr(v)
INDENT = "\t"
local tabs = INDENT:rep(depth)
if type(v) == "string" then
return ("%q"):format(v)
elseif type(v) == "number" then
if v == math.huge then return "math.huge" end
if v == -math.huge then return "-math.huge" end
return tonumber(v)
elseif type(v) == "boolean" then
return tostring(v)
elseif type(v) == "nil" then
return "nil"
elseif type(v) == "table" and type(v.__tostring) == "function" then
return tostring(v.__tostring(v))
elseif type(v) == "table" and getmetatable(v) and type(getmetatable(v).__tostring) == "function" then
return tostring(getmetatable(v).__tostring(v))
elseif type(v) == "table" then
local str = "{\n" .. INDENT .. tabs
local isArray = true
local arrayMax = 0
local arrayCount = 0
for k, v in pairs(v) do
if type(k) ~= "number" then
isArray = false
break
elseif k <= 0 then
isArray = false
break
elseif k > arrayMax then
arrayMax = k
end
arrayCount = arrayCount + 1
end
if isArray and arrayMax == arrayCount then
for i = 1, #v do
if i ~= 1 then
str = str .. ",\n" .. INDENT .. tabs
end
depth = depth + 1
str = str .. r_repr(v[i])
depth = depth - 1
end
else
local keyOrder = {}
local keyValueStrings = {}
for k, v in pairs(v) do
depth = depth + 1
local kStr = isLuaIdentifier(k) and k or ("[" .. r_repr(k) .. "]")
local vStr = r_repr(v)
table.insert(keyOrder, kStr)
keyValueStrings[kStr] = vStr
depth = depth - 1
end
local first = true
for _, kStr in pairs(keyOrder) do
if not first then
str = str .. ",\n" .. INDENT .. tabs
end
str = str .. ("%s = %s"):format(kStr, keyValueStrings[kStr])
first = false
end
end
str = str .. "\n" .. tabs
str = str .. "}"
return str
end
end
function load_file_db(scriptfile)
-- loads Datastore DB without polluting or compromising globals
local env = setmetatable({}, {__index=_G})
assert(pcall(setfenv(assert(loadfile(scriptfile)), env)))
setmetatable(env, nil)
return env
end
function pull_acct_datastore(acct, acctdir, ds)
local dsfile, ds = ds, string.sub(ds,1,-5)
local dsdbname = ds..'DB'
local masterds = masterdb..dsfile
print("Pulling datastore: "..dsdbname .. " with " .. masterds)
--dofile(acctdir..dsfile)
local dsdb = load_file_db(acctdir..dsfile)
dsdb = dsdb[dsdbname]
if dsdb ~= nil then
local oldmasterdata, oldgdata = {}, {}
if exists(masterds) then
oldmasterdata = load_file_db(masterds)
oldgdata = oldmasterdata['GuildsDB']
oldmasterdata = oldmasterdata['CharactersDB']
end
if dsdb['global'] ~= nil then
local masterdata, mastergdata, masterident = {}, {}, nil
if dsdb['global']['Characters'] ~= nil then
local dsc = dsdb['global']['Characters']
local charnames = {}
if oldmasterdata ~= nil then
for k, v in pairs(oldmasterdata) do
masterdata[k] = v
end
end
local i = 0
for cident, cdata in pairs(dsc) do
local lu = cdata.lastUpdate
local cis = split(cident, ".")
local cacct, cserv, cname = cis[1], cis[2], cis[3]
--print(cacct.. " -- "..cserv.." -- "..cname.. " = "..lu)
if cacct == "Default" then
masterident = acct.."."..cserv.."."..cname
if oldmasterdata == nil or oldmasterdata[masterident] == nil or oldmasterdata[masterident].lastUpdate == nil then
masterdata[masterident] = cdata
elseif lu == nil then
masterdata[masterident] = cdata
elseif oldmasterdata[masterident].lastUpdate <= lu then
masterdata[masterident] = cdata
end
if servers[cserv] == nil then
print("Identified server "..cserv.." for "..acct)
servers[cserv] = lu
elseif servers[cserv] < lu then
servers[cserv] = lu
end
end
end
end
if dsdb['global']['Guilds'] ~= nil then
local dsg = dsdb['global']['Guilds']
local gnames = {}
if oldgdata ~= nil then
for k, v in pairs(oldgdata) do
mastergdata[k] = v
end
end
local i = 0
for gident, gdata in pairs(dsg) do
local lu = gdata.lastUpdate
local gis = split(gident, ".")
local gacct, gserv, gname = gis[1], gis[2], gis[3]
--print(gacct.. " -- "..gserv.." -- "..gname.. " = "..lu)
gacct = "Default"
masterident = gacct.."."..gserv.."."..gname
if oldgdata == nil or oldgdata[masterident] == nil or oldgdata[masterident].lastUpdate == nil then
mastergdata[masterident] = gdata
elseif lu == nil then
mastergdata[masterident] = gdata
elseif oldgdata[masterident].lastUpdate <= lu then
mastergdata[masterident] = gdata
end
if servers[gserv] == nil then
if lu == nil then
servers[gserv] = 0
else
servers[gserv] = lu
end
elseif lu == nil then
servers[gserv] = 0
elseif servers[gserv] < lu then
servers[gserv] = lu
end
end
end
print("opening masterds: "..masterds)
masterfile = io.open(masterds, "w+")
masterfile:write("\nCharactersDB = "..repr(masterdata).."\n")
masterfile:write("\nGuildsDB = "..repr(mastergdata).."\n")
masterfile:close()
--print(repr(charnames))
end
end
end
function push_acct_datastore(acct, acctdir, ds)
local dsfile, ds = ds, string.sub(ds,1,-5)
local dsdbname = ds..'DB'
local masterds = masterdb..dsfile
print("Pushing datastore: "..dsdbname .. " with " .. masterds)
--dofile(acctdir..dsfile)
local dsdb = load_file_db(acctdir..dsfile)
local masterdb, mastergdb = {}, {}
if exists(masterds) then
masterdb = load_file_db(masterds)
mastergdb = masterdb["GuildsDB"]
masterdb = masterdb["CharactersDB"]
end
if dsdb ~= nil then
if dsdb[dsdbname] ~= nil then
if dsdb[dsdbname]['global'] ~= nil then
if dsdb[dsdbname]['global']['Characters'] ~= nil then
local dsc = dsdb[dsdbname]['global']['Characters']
for cident, cdata in pairs(masterdb) do
local cis = split(cident, ".")
local cacct, cserv, cname = cis[1], cis[2], cis[3]
local dident = cident
if cacct == acct then
dident = "Default."..cserv.."."..cname
end
local ddata = dsc[dident]
if ddata ~= nil then
if cdata.lastUpdate == nil or ddata.lastUpdate == nil or cdata.lastUpdate >= ddata.lastUpdate then
dsc[dident] = cdata
end
else
dsc[dident] = cdata
end
end
end
if dsdb[dsdbname]['global']['Guilds'] ~= nil then
local dsg = dsdb[dsdbname]['global']['Guilds']
for gident, gdata in pairs(mastergdb) do
local gis = split(gident, ".")
local gacct, gserv, gname = gis[1], gis[2], gis[3]
local dident = "Default."..gserv.."."..gname
local ddata = dsg[dident]
if ddata ~= nil then
if gdata.lastUpdate == nil or ddata.lastUpdate == nil or gdata.lastUpdate >= ddata.lastUpdate then
dsg[dident] = gdata
end
else
dsg[dident] = gdata
end
end
end
end
end
outfile = io.open(acctdir..dsfile..".syncnew", "w+")
for k, v in pairs(dsdb) do
outfile:write("\n"..k.." = "..repr(v).."\n")
end
outfile:close()
os.rename(acctdir..dsfile, acctdir..dsfile..".syncbak")
os.remove(acctdir..dsfile)
os.rename(acctdir..dsfile..".syncnew", acctdir..dsfile)
end
end
domains = {}
servers = {}
function pull_acct(acct, acctdir)
servers = {}
if exists(acctdir) then
print("Pulling account "..acct)
for i, dirname in ipairs(listdir(acctdir)) do
if string.match(dirname, "^DataStore_") and string.match(dirname, "[.]lua$") then
pull_acct_datastore(acct, acctdir, dirname)
elseif dirname == "DataStore.lua" then
pull_acct_datastore(acct, acctdir, dirname)
end
end
end
if domains[acct] == nil then domains[acct] = {} end
for k, v in pairs(servers) do
print("Adding server domain "..k)
domains[acct][k] = { ["lastSharingTimestamp"] = v, ["lastUpdatedWith"] = " " }
end
end
function push_acct(acct, acctdir)
local altfile = "Altoholic.lua"
if exists(acctdir) and exists(acctdir..altfile) then
print("Pushing account "..acct)
for i, dirname in ipairs(listdir(acctdir)) do
if string.match(dirname, "^DataStore_") and string.match(dirname, "[.]lua$") then
push_acct_datastore(acct, acctdir, dirname)
elseif dirname == "DataStore.lua" then
push_acct_datastore(acct, acctdir, dirname)
end
end
-- data stores updated, now update altoholic config to register the new accounts if needed
local acctdomains = {}
for dsacct, servlist in pairs(domains) do
for dsserv, v in pairs(servlist) do
if dsacct == acct then
dsacct = 'Default'
end
local dskey = dsacct..'.'..dsserv
acctdomains[dskey] = v
end
end
local altdb = load_file_db(acctdir..altfile)
if altdb ~= nil then
print("Found altoholicdb for "..acct)
altdb = altdb['AltoholicDB']
end
if altdb ~= nil then
if altdb['global'] == nil then altdb['global'] = {} end
if altdb['global']['Sharing'] == nil then altdb['global']['Sharing'] = {} end
print("Found global domains for "..acct)
altdb['global']['Sharing']['Domains'] = acctdomains
end
if altdb ~= nil then
--print("\nAltoholicDB = "..repr(altdb).."\n")
outfile = io.open(acctdir..altfile..".syncnew", "w+")
outfile:write("\nAltoholicDB = "..repr(altdb).."\n")
outfile:close()
os.rename(acctdir..altfile, acctdir..altfile..".syncbak")
os.remove(acctdir..altfile)
os.rename(acctdir..altfile..".syncnew", acctdir..altfile)
end
end
end
function run()
local accounts = {}
--if not exists(masterdb) then
-- os.execute("mkdir \""..masterdb.."\"")
--end
if not exists(masterdb) then
masterdb = 'C:/Users/def.000/Nextcloud/hardlinks/wow-altoholic/'
print("Using masterdb: "..masterdb)
end
if not exists(wowdir) then
wowdir = 'D:/Games/World of Warcraft/_retail_/'
print("Using wowdir: "..wowdir)
end
if exists(wowdir..'WTF/Account/') then
for i, dirname in ipairs(listdir(wowdir..'WTF/Account/')) do
if dirname ~= "SavedVariables" then
accounts[i] = dirname
end
end
end
for accidx, acc in pairs(accounts) do
--io.write(acc.." ")
local acctdir = wowdir..'WTF/Account/'..acc..'/SavedVariables/'
pull_acct(acc,acctdir)
end
for accidx, acc in pairs(accounts) do
--io.write(acc.." ")
local acctdir = wowdir..'WTF/Account/'..acc..'/SavedVariables/'
push_acct(acc,acctdir)
end
--print()
end
run()