diff --git a/Launch.lua b/Launch.lua
index 66724ee8..a40ef14f 100644
--- a/Launch.lua
+++ b/Launch.lua
@@ -14,8 +14,19 @@ SetMainObject(launch)
function launch:OnInit()
ConPrintf("Loading main script...")
+ local mainFile = io.open("Modules/Main.lua")
+ if mainFile then
+ mainFile:close()
+ else
+ if LoadModule("Update", "CHECK") then
+ Exit("Failed to install.")
+ else
+ Restart()
+ end
+ return
+ end
local errMsg
- errMsg, self.main = PLoadModule("Modules/main", self)
+ errMsg, self.main = PLoadModule("Modules/Main", self)
if errMsg then
self:ShowErrMsg("Error loading main script: %s", errMsg)
elseif not self.main then
diff --git a/LaunchInstall.lua b/LaunchInstall.lua
index 23aed92a..301a2522 100644
--- a/LaunchInstall.lua
+++ b/LaunchInstall.lua
@@ -5,12 +5,18 @@
-- Installation bootstrap
--
+local basicFiles = {
+ ["Launch.lua"] = "https://raw.githubusercontent.com/Openarl/PathOfBuilding/master/Launch.lua",
+ ["Update.lua"] = "https://raw.githubusercontent.com/Openarl/PathOfBuilding/master/Update.lua",
+}
local curl = require("lcurl")
-local outFile = io.open("Launch.lua", "wb")
-local easy = curl.easy()
-easy:setopt_url("https://raw.githubusercontent.com/Openarl/PathOfBuilding/master/Launch.lua")
-easy:setopt_writefunction(outFile)
-easy:perform()
-easy:close()
-outFile:close()
+for name, url in pairs(basicFiles) do
+ local outFile = io.open(name, "wb")
+ local easy = curl.easy()
+ easy:setopt_url(url)
+ easy:setopt_writefunction(outFile)
+ easy:perform()
+ easy:close()
+ outFile:close()
+end
Restart()
\ No newline at end of file
diff --git a/PathOfBuilding.sln b/PathOfBuilding.sln
index 3bb666a3..a5a5a3b7 100644
--- a/PathOfBuilding.sln
+++ b/PathOfBuilding.sln
@@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
Launch.lua = Launch.lua
LaunchInstall.lua = LaunchInstall.lua
+ Update.lua = Update.lua
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gem DB", "Gem DB", "{EE9D06F5-7310-4567-AB18-6923BCF0A2A1}"
diff --git a/Update.lua b/Update.lua
new file mode 100644
index 00000000..baacaa89
--- /dev/null
+++ b/Update.lua
@@ -0,0 +1,218 @@
+#@
+-- Path of Building
+--
+-- Module: Update
+-- Checks for and applies updates
+--
+local mode = ...
+
+local xml = require("xml")
+local sha1 = require("sha1")
+local curl = require("lcurl")
+local lzip = require("lzip")
+
+local function downloadFile(url, outName)
+ local outFile = io.open(outName, "wb")
+ local easy = curl.easy()
+ easy:setopt_url(url)
+ easy:setopt_writefunction(outFile)
+ easy:perform()
+ local code = easy:getinfo(curl.INFO_RESPONSE_CODE)
+ easy:close()
+ outFile:close()
+ if code ~= 200 then
+ ConPrintf("Download failed (code %d)", code)
+ os.remove(outName)
+ return true
+ end
+end
+
+if mode == "CHECK" then
+ ConPrintf("Checking for update...")
+
+ -- Load and process local manifest
+ local localVer
+ local localPlatform
+ local localFiles = { }
+ local localManXML = xml.LoadXMLFile("manifest.xml")
+ local localSource
+ if localManXML and localManXML[1].elem == "PoBVersion" then
+ for _, node in ipairs(localManXML[1]) do
+ if type(node) == "table" then
+ if node.elem == "Version" then
+ localVer = node.attrib.number
+ localPlatform = node.attrib.platform
+ elseif node.elem == "Source" then
+ if node.attrib.part == "program" then
+ localSource = node.attrib.url
+ end
+ elseif node.elem == "File" then
+ localFiles[node.attrib.name] = { sha1 = node.attrib.sha1, part = node.attrib.part, platform = node.attrib.platform }
+ end
+ end
+ end
+ end
+ if not localVer or not localSource or not next(localFiles) then
+ ConPrintf("Update failed: invalid local manifest")
+ return true
+ end
+
+ -- Download and process remote manifest
+ local remoteVer
+ local remoteFiles = { }
+ local remoteManText = ""
+ local remoteSources = { }
+ local easy = curl.easy()
+ easy:setopt_url(localSource.."manifest.xml")
+ easy:setopt_writefunction(function(data)
+ remoteManText = remoteManText..data
+ return true
+ end)
+ easy:perform()
+ easy:close()
+ local remoteManXML = xml.ParseXML(remoteManText)
+ if remoteManXML and remoteManXML[1].elem == "PoBVersion" then
+ for _, node in ipairs(remoteManXML[1]) do
+ if type(node) == "table" then
+ if node.elem == "Version" then
+ remoteVer = node.attrib.number
+ elseif node.elem == "Source" then
+ if not remoteSources[node.attrib.part] then
+ remoteSources[node.attrib.part] = { }
+ end
+ remoteSources[node.attrib.part][node.attrib.platform or "any"] = node.attrib.url
+ elseif node.elem == "File" then
+ if not node.attrib.platform or node.attrib.platform == localPlatform then
+ remoteFiles[node.attrib.name] = { sha1 = node.attrib.sha1, part = node.attrib.part, platform = node.attrib.platform }
+ end
+ end
+ end
+ end
+ end
+ if not remoteVer or not next(remoteSources) or not next(remoteFiles) then
+ ConPrintf("Update failed: invalid remote manifest")
+ return true
+ end
+
+ -- Build lists of files to be updated or deleted
+ local updateFiles = { }
+ for name, data in pairs(remoteFiles) do
+ data.name = name
+ if not localFiles[name] or localFiles[name].sha1 ~= data.sha1 then
+ table.insert(updateFiles, data)
+ elseif localFiles[name] then
+ local file = io.open(name, "rb")
+ if not file then
+ ConPrintf("Warning: '%s' doesn't exist, it will be re-downloaded", data.name)
+ table.insert(updateFiles, data)
+ else
+ local content = file:read("*a")
+ file:close()
+ if data.sha1 ~= sha1(content) then
+ ConPrintf("Warning: Integrity check on '%s' failed, it will be replaced", data.name)
+ table.insert(updateFiles, data)
+ end
+ end
+ end
+ end
+ local deleteFiles = { }
+ for name, data in pairs(localFiles) do
+ data.name = name
+ if not remoteFiles[name] then
+ table.insert(deleteFiles, data)
+ end
+ end
+
+ if #updateFiles == 0 and #deleteFiles == 0 then
+ ConPrintf("Update failed: nothing to update")
+ return
+ end
+
+ MakeDir("Update")
+ ConPrintf("Downloading update...")
+
+ -- Download files that need updating
+ local failedFile = false
+ local zipFiles = { }
+ for _, data in ipairs(updateFiles) do
+ local partSources = remoteSources[data.part]
+ local source = partSources[localPlatform] or partSources["any"]
+ local fileName = "Update/"..data.name:gsub("[\\/]","{slash}")
+ data.updateFileName = fileName
+ local zipName = source:match("/([^/]+%.zip)$")
+ if zipName then
+ if not zipFiles[zipName] then
+ ConPrintf("Downloading %s...", zipName)
+ local zipFileName = "Update/"..zipName
+ downloadFile(source, zipFileName)
+ zipFiles[zipName] = lzip.open(zipFileName)
+ end
+ local zip = zipFiles[zipName]
+ if zip then
+ local zippedFile = zip:OpenFile(data.name)
+ if zippedFile then
+ local file = io.open(fileName, "wb")
+ file:write(zippedFile:Read("*a"))
+ file:close()
+ zippedFile:close()
+ else
+ ConPrintf("Couldn't extract '%s' from '%s' (extract failed)", data.name, zipName)
+ failedFile = true
+ end
+ else
+ ConPrintf("Couldn't extract '%s' from '%s' (zip open failed)", data.name, zipName)
+ failedFile = true
+ end
+ elseif source == "" then
+ ConPrintf("File '%s' has no source", data.name)
+ failedFile = true
+ else
+ ConPrintf("Downloading %s...", data.name)
+ if downloadFile(source..data.name, fileName) then
+ failedFile = true
+ end
+ end
+ end
+ for name, zip in pairs(zipFiles) do
+ zip:Close()
+ os.remove(name)
+ end
+ if failedFile then
+ ConPrintf("Update failed: failed to get all required files")
+ return true
+ end
+
+ -- Create new manifest
+ localManXML = { elem = "PoBVersion" }
+ table.insert(localManXML, { elem = "Version", attrib = { number = remoteVer, platform = localPlatform } })
+ for part, platforms in pairs(remoteSources) do
+ for platform, url in pairs(platforms) do
+ table.insert(localManXML, { elem = "Source", attrib = { part = part, platform = platform ~= "any" and platform, url = url } })
+ end
+ end
+ for name, data in pairs(remoteFiles) do
+ table.insert(localManXML, { elem = "File", attrib = { name = data.name, sha1 = data.sha1, part = data.part, platform = data.platform } })
+ end
+ xml.SaveXMLFile(localManXML, "Update/manifest.xml")
+
+ -- Build list of operations to apply the update
+ local ops = { }
+ for _, data in pairs(updateFiles) do
+ table.insert(ops, 'copy "'..data.updateFileName..'" "'..data.name..'"')
+ table.insert(ops, 'delete "'..data.updateFileName..'"')
+ end
+ for _, data in pairs(deleteFiles) do
+ table.insert(ops, 'delete "'..data.name..'"')
+ end
+ table.insert(ops, 'copy "Update/manifest.xml" "manifest.xml"')
+ table.insert(ops, 'delete "manifest.xml"')
+
+ -- Write operations file
+ local opFile = io.open("Update/opFile.txt", "w")
+ opFile:write(table.concat(ops, "\n"))
+ opFile:close()
+end
+
+
+
+
diff --git a/manifest.xml b/manifest.xml
index a4908f6e..64e0dad2 100644
--- a/manifest.xml
+++ b/manifest.xml
@@ -3,7 +3,8 @@
-
+
+
@@ -48,10 +49,10 @@
-
+
-
+
@@ -86,4 +87,5 @@
+