462 lines
No EOL
13 KiB
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() |