8f3114e377
Signed-off-by: Michael Fabian Dirks <michael.dirks@project-kube.de>
435 lines
13 KiB
Plaintext
435 lines
13 KiB
Plaintext
SuperStrict
|
|
|
|
Import BaH.libcurl
|
|
Import BRL.Bank
|
|
Import BRL.FileSystem
|
|
Import BRL.LinkedList
|
|
Import BRL.StandardIO
|
|
Import BRL.Stream
|
|
Import BRL.Timer
|
|
Import "Clock.c"
|
|
Import "Digest.bmx"
|
|
|
|
Type TPatcher
|
|
Const STATE_DEFAULT:Byte = 0
|
|
Const STATE_PATCHINFO:Byte = 1
|
|
Const STATE_CHECKING:Byte = 2
|
|
Const STATE_PREPATCH:Byte = 3
|
|
Const STATE_PATCHING:Byte = 4
|
|
Const STATE_COMPLETE:Byte = 5
|
|
Const STATE_LAUNCHER:Byte = 6
|
|
Const SSTATE_ENTER:Byte = 0
|
|
Const SSTATE_WORK:Byte = 1
|
|
Const SSTATE_LEAVE:Byte = 2
|
|
Const SERVER:String = "http://sirius-online.us.to/patch/"
|
|
|
|
' State-based Patcher
|
|
Field m_bState:Byte = TPatcher.STATE_DEFAULT
|
|
Field m_bSubState:Byte = TPatcher.SSTATE_ENTER
|
|
|
|
' lib/cURL is used for downloading of data.
|
|
Field m_oCurlMulti:TCurlMulti = Null
|
|
Field m_oCurl:TCurlEasy = Null
|
|
Field m_fCurlProgress:Float
|
|
|
|
' State: All
|
|
Field m_oTaskList:TList = New TList
|
|
Field m_fProgress:Float = 0
|
|
|
|
' State: Patchinfo, Checking, Patching
|
|
Field m_oFileHashList:TList = New TList
|
|
Field m_iFileCount:Int = 0
|
|
|
|
' State: Checking, Patching
|
|
Field m_oFileLoader:TAsyncLoader = New TAsyncLoader
|
|
Field m_oFileHashPair:TFileHash = Null
|
|
|
|
' State: Patching
|
|
Field m_oFileList:TList = New TList
|
|
Field m_oFile:String, m_oFileDL:String
|
|
Field m_oFileStream:TStream
|
|
|
|
' Construction
|
|
Method New()
|
|
EndMethod
|
|
|
|
Function Create:TPatcher()
|
|
Local oPatcher:TPatcher = New TPatcher
|
|
Return oPatcher
|
|
EndFunction
|
|
|
|
' Deconstruction
|
|
Method Destroy()
|
|
m_oCurlMulti.multiCleanup()
|
|
m_oCurl.cleanup()
|
|
EndMethod
|
|
|
|
|
|
' Basically the Main Loop of the Patcher, handles everything. Call GetShutdown:String() if you want to know when the patcher is telling you to run an executable and close.
|
|
Method Update()
|
|
' Check
|
|
Select m_bState
|
|
Case TPatcher.STATE_DEFAULT
|
|
If m_bSubState = TPatcher.SSTATE_ENTER Then
|
|
m_oCurlMulti = TCurlMulti.Create()
|
|
m_oCurl = m_oCurlMulti.newEasy()
|
|
m_oCurl.setOptInt(CURLOPT_FOLLOWLOCATION, 1)
|
|
m_oCurl.setOptInt(CURLOPT_CRLF, False)
|
|
m_oCurl.setOptString(CURLOPT_USERAGENT, "Sirius Online Launcher")
|
|
m_oCurl.setOptString(CURLOPT_REFERER, TPatcher.SERVER)
|
|
m_oCurl.setOptString(CURLOPT_URL, TPatcher.SERVER + "info")
|
|
m_oCurl.setProgressCallback(TPatcher.ProgressCallback, Self)
|
|
m_oCurl.setWriteString()
|
|
m_oTaskList.AddFirst("n[WAIT] Getting patch information...")
|
|
|
|
m_bSubState = TPatcher.SSTATE_WORK
|
|
EndIf
|
|
|
|
If m_bSubState = TPatcher.SSTATE_WORK Then
|
|
Local iResult:Int = Perform()
|
|
|
|
m_fProgress = m_fCurlProgress
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("g[" + RSet(String(Int(m_fProgress * 100)), 3) + "%] Retrieving patch information...")
|
|
|
|
If iResult = CURLM_OK Then
|
|
If m_fCurlProgress = 1.0 And m_iCurlMultiHandles = 0 Then
|
|
m_bSubState = TPatcher.SSTATE_LEAVE
|
|
EndIf
|
|
Else
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("g[FAIL] Retrieving patch information... ("+CurlError(iResult)+")")
|
|
SetState(TPatcher.STATE_COMPLETE)
|
|
EndIf
|
|
EndIf
|
|
|
|
If m_bSubState = TPatcher.SSTATE_LEAVE Then
|
|
m_oCurlMulti.multiRemove(m_oCurl)
|
|
m_oCurlMulti.multiCleanup()
|
|
m_oCurlMulti = Null
|
|
|
|
Local oCurlRCode:Int = m_oCurl.getInfo().responseCode()
|
|
Local stResult:String = m_oCurl.toString()
|
|
m_oCurl.cleanup()
|
|
m_oCurl = Null
|
|
|
|
If oCurlRCode <> 404 Then
|
|
m_oFileHashList.Clear()
|
|
If stResult.length > 0 Then
|
|
Local stFileHashArr:String[] = stResult.Split("~n")
|
|
For Local stFileHashPair:String = EachIn stFileHashArr
|
|
If stFileHashPair.length > 0 And stFileHashPair[0..1] <> "#" Then ' Ignore Comments
|
|
Local stPairArr:String[] = stFileHashPair.Split(":")
|
|
m_oFileHashList.AddLast(TFileHash.Create(stPairArr[0], stPairArr[1]))
|
|
EndIf
|
|
Next
|
|
m_iFileCount = m_oFileHashList.Count()
|
|
m_oFileList.Clear()
|
|
SetState(TPatcher.STATE_PATCHINFO) ' Got Patchinfo, and it had information.
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("g[ OK ] Retrieving patch information... done.")
|
|
Else
|
|
SetState(TPatcher.STATE_COMPLETE)
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("e[FAIL] Retrieving patch information... invalid information file.")
|
|
EndIf
|
|
Else
|
|
SetState(TPatcher.STATE_COMPLETE)
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("e[FAIL] Retrieving patch information... file not found.")
|
|
EndIf
|
|
EndIf
|
|
Case TPatcher.STATE_CHECKING
|
|
If m_bSubState = TPatcher.SSTATE_ENTER Then ' Used to load a new File
|
|
m_oFileHashPair = TFileHash(m_oFileHashList.RemoveFirst())
|
|
If m_oFileHashPair <> Null Then
|
|
m_oFileLoader.Initialize(m_oFileHashPair.stName)
|
|
m_oTaskList.AddFirst("n[WAIT] Checking '" + m_oFileHashPair.stName + "'...")
|
|
|
|
m_bSubState = TPatcher.SSTATE_WORK
|
|
Else
|
|
m_bSubState = TPatcher.SSTATE_LEAVE
|
|
EndIf
|
|
EndIf
|
|
|
|
If m_bSubState = TPatcher.SSTATE_WORK Then
|
|
If m_oFileLoader.m_bComplete = 0 Then
|
|
m_oFileLoader.Process(2, 1024)
|
|
m_fProgress = ((m_iFileCount - (m_oFileHashList.Count() + 1)) / Float(m_iFileCount)) + m_oFileLoader.m_fProgress * (1.0 / m_iFileCount)
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("g[" + RSet(String(Int(m_fProgress * 100)), 3) + "%] Checking '" + m_oFileHashPair.stName + "'...")
|
|
Else
|
|
If m_oFileLoader.m_bComplete = 1
|
|
If m_oFileLoader.m_oBank <> Null Then
|
|
Local stHash:String = MD5Bank(m_oFileLoader.m_oBank).ToUpper()
|
|
If stHash <> m_oFileHashPair.stHash Then
|
|
m_oFileList.AddLast(m_oFileHashPair.stName)
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("h[ OK ] Checking '" + m_oFileHashPair.stName + "'... requires update.")
|
|
Else
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("g[ OK ] Checking '" + m_oFileHashPair.stName + "'... up to date.")
|
|
EndIf
|
|
Else
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("e[FAIL] Checking '" + m_oFileHashPair.stName + "'... (File in use?)")
|
|
EndIf
|
|
Else
|
|
m_oFileList.AddLast(m_oFileHashPair.stName)
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("h[ OK ] Checking '" + m_oFileHashPair.stName + "'... missing.")
|
|
EndIf
|
|
m_bSubState = TPatcher.SSTATE_ENTER
|
|
m_oFileLoader.Cleanup()
|
|
EndIf
|
|
EndIf
|
|
|
|
If m_bSubState = TPatcher.SSTATE_LEAVE Then
|
|
If m_oFileList.Count() > 0 Then
|
|
m_iFileCount = m_oFileList.Count()
|
|
SetState(TPatcher.STATE_PREPATCH)
|
|
Else
|
|
SetState(TPatcher.STATE_COMPLETE)
|
|
EndIf
|
|
EndIf
|
|
Case TPatcher.STATE_PATCHING
|
|
If m_bSubState = TPatcher.SSTATE_ENTER Then
|
|
Local m_oEntry:Object = m_oFileList.RemoveFirst()
|
|
If m_oEntry <> Null Then
|
|
m_oFile = String(m_oEntry);m_oFileDL = m_oFile + ".part"
|
|
|
|
CreateDir(ExtractDir(m_oFile), True)
|
|
m_oFileStream = WriteFile(m_oFileDL)
|
|
If m_oFileStream <> Null Then
|
|
m_oCurlMulti = TCurlMulti.Create()
|
|
m_oCurl = m_oCurlMulti.newEasy()
|
|
m_oCurl.setOptInt(CURLOPT_FOLLOWLOCATION, 1)
|
|
m_oCurl.setOptInt(CURLOPT_CRLF, False)
|
|
m_oCurl.setOptString(CURLOPT_USERAGENT, "Sirius Online Launcher")
|
|
m_oCurl.setOptString(CURLOPT_REFERER, TPatcher.SERVER + "info")
|
|
m_oCurl.setOptString(CURLOPT_URL, TPatcher.SERVER + m_oFile)
|
|
m_oCurl.setWriteStream(m_oFileStream)
|
|
m_oCurl.setProgressCallback(TPatcher.ProgressCallback, Self)
|
|
m_oTaskList.AddFirst("n[WAIT] Downloading '" + m_oFile + "'...")
|
|
|
|
m_bSubState = TPatcher.SSTATE_WORK
|
|
EndIf
|
|
Else
|
|
SetState(TPatcher.STATE_DEFAULT)
|
|
EndIf
|
|
EndIf
|
|
|
|
If m_bSubState = TPatcher.SSTATE_WORK Then
|
|
Local iResult:Int = Perform()
|
|
m_fProgress = ((m_iFileCount - (m_oFileList.Count() + 1)) / Float(m_iFileCount)) + m_fCurlProgress * (1.0 / m_iFileCount)
|
|
If iResult = CURLM_OK Then
|
|
m_oFileStream.Flush()
|
|
If m_fCurlProgress = 1.0 And m_iCurlMultiHandles = 0 Then
|
|
m_oCurlMulti.multiRemove(m_oCurl)
|
|
m_oCurlMulti.multiCleanup()
|
|
|
|
Local oCurlRCode:Int = m_oCurl.getInfo().responseCode()
|
|
m_oCurl.cleanup()
|
|
|
|
' Cleanup(1)
|
|
m_oCurl = Null
|
|
m_oCurlMulti = Null
|
|
m_oFileStream.Close()
|
|
m_oFileStream = Null
|
|
|
|
If oCurlRCode <> 404 Then
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("g[ OK ] Downloading '" + m_oFile + "'...")
|
|
|
|
' Rename files
|
|
If m_oFile <> "Launcher.exe" Then
|
|
RenameFile(m_oFile, m_oFile + ".old")
|
|
If RenameFile(m_oFileDL, m_oFile) Then
|
|
DeleteFile(m_oFile + ".old")
|
|
Else
|
|
RenameFile(m_oFile + ".old", m_oFile)
|
|
EndIf
|
|
Else' If we patched the launcher, tell the parent program to start the new one.
|
|
RenameFile(m_oFileDL, "-" + m_oFile)
|
|
m_oFileList.Clear() ' Clear the list to be sure
|
|
SetState(TPatcher.STATE_LAUNCHER)
|
|
EndIf
|
|
|
|
Else
|
|
DeleteFile(m_oFileDL)
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("e[FAIL] Downloading '" + m_oFile + "'... file not found.")
|
|
EndIf
|
|
|
|
'Cleanup (2)
|
|
m_oFileDL = Null
|
|
m_oFile = Null
|
|
|
|
If m_oFileList.Count() = 0 Then
|
|
m_bSubState = TPatcher.SSTATE_LEAVE
|
|
Else
|
|
m_bSubState = TPatcher.SSTATE_ENTER
|
|
EndIf
|
|
Else
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("n[" + RSet(String(Int(m_fCurlProgress * 100)), 3) + "%] Downloading '" + m_oFile + "'...")
|
|
EndIf
|
|
Else
|
|
m_oFileStream.Close()
|
|
m_oTaskList.RemoveFirst()
|
|
m_oTaskList.AddFirst("e[FAIL] Downloading '" + m_oFile + "'... " + CurlError(iResult))
|
|
EndIf
|
|
EndIf
|
|
|
|
If m_bSubState = TPatcher.SSTATE_LEAVE And m_bState = TPatcher.STATE_PATCHING Then
|
|
SetState(TPatcher.STATE_DEFAULT)
|
|
EndIf
|
|
EndSelect
|
|
EndMethod
|
|
|
|
Method Advance()
|
|
Select m_bState
|
|
Case TPatcher.STATE_PATCHINFO
|
|
SetState(TPatcher.STATE_CHECKING)
|
|
Case TPatcher.STATE_PREPATCH
|
|
SetState(TPatcher.STATE_PATCHING)
|
|
Case TPatcher.STATE_COMPLETE
|
|
m_stProcessToRun = "ExeFile.exe"
|
|
Case TPatcher.STATE_LAUNCHER
|
|
m_stProcessToRun = "-Launcher.exe 1"
|
|
EndSelect
|
|
EndMethod
|
|
|
|
Field m_iCurlMultiHandles:Int, m_bCurlMultiState:Byte = 0
|
|
Method Perform:Int()
|
|
Local iResult:Int = 0
|
|
|
|
iResult = MultiPerform()
|
|
Select m_bCurlMultiState
|
|
Case 0, 2
|
|
m_bCurlMultiState = (m_bCurlMultiState + 1) Mod 3
|
|
Case 1
|
|
If m_oCurlMulti.multiSelect(0.1) <> -1 And m_iCurlMultiHandles Then
|
|
iResult = MultiPerform()
|
|
If m_iCurlMultiHandles = 0 Then m_bCurlMultiState = 2
|
|
EndIf
|
|
EndSelect
|
|
|
|
Return iResult
|
|
EndMethod
|
|
|
|
Method MultiPerform:Int()
|
|
Local iResult:Int = m_oCurlMulti.multiPerform(m_iCurlMultiHandles)
|
|
While iResult = CURLM_CALL_MULTI_PERFORM
|
|
iResult = m_oCurlMulti.multiPerform(m_iCurlMultiHandles)
|
|
Wend
|
|
Return iResult
|
|
EndMethod
|
|
|
|
' State Management
|
|
Method SetState(bState:Byte)
|
|
m_bState = bState
|
|
m_bSubState = TPatcher.SSTATE_ENTER
|
|
m_fProgress = 0.0
|
|
EndMethod
|
|
|
|
Method GetState:Byte()
|
|
Return m_bState
|
|
EndMethod
|
|
|
|
' Information
|
|
Method GetProgress:Float()
|
|
Return m_fProgress
|
|
EndMethod
|
|
|
|
|
|
|
|
Field m_stProcessToRun:String = NUll
|
|
Method GetShutdown:String()
|
|
Return m_stProcessToRun
|
|
EndMethod
|
|
' Callbacks
|
|
Function ProgressCallback:Int(oPatcherObj:Object, dDownloadTotal:Double, dDownloadNow:Double, dUploadTotal:Double, dUploadNow:Double)
|
|
If dDownloadTotal = 0 And dDownloadNow = 0 And dUploadTotal = 0 And dUploadNow = 0 Then TPatcher(oPatcherObj).m_fCurlProgress = 0.0;Return 0
|
|
TPatcher(oPatcherObj).m_fCurlProgress = (dDownloadNow / dDownloadTotal)
|
|
EndFunction
|
|
EndType
|
|
|
|
Type TFileHash
|
|
Field stName:String, stHash:String
|
|
|
|
Function Create:TFileHash(stName:String, stHash:String)
|
|
Local oFH:TFileHash = New TFileHash
|
|
oFH.stName = stName
|
|
oFH.stHash = stHash
|
|
Return oFH
|
|
EndFunction
|
|
EndType
|
|
|
|
Type TAsyncLoader
|
|
Field m_oBank:TBank
|
|
Field m_oStream:TStream, m_iFileSize:Int
|
|
Field m_fProgress:Float, m_bComplete:Byte
|
|
|
|
Method Initialize(stFile:String)
|
|
If FileType(stFile) = FILETYPE_FILE Then
|
|
m_oStream = ReadFile(stFile)
|
|
If m_oStream <> Null Then
|
|
m_iFileSize = FileSize(stFile)
|
|
m_oBank = CreateBank(m_iFileSize)
|
|
If m_oBank <> Null Then
|
|
m_fProgress = 0.0
|
|
m_bComplete = False
|
|
Else
|
|
m_oStream.Close()
|
|
EndIf
|
|
Else
|
|
m_fProgress = 1.0
|
|
m_bComplete = -2
|
|
EndIf
|
|
Else
|
|
m_fProgress = 1.0
|
|
m_bComplete = -2
|
|
EndIf
|
|
EndMethod
|
|
|
|
Method Process(iMs:Float, iBufferSize:Int = 1024)
|
|
Local iClockStart:Int = getClock()
|
|
If (m_oStream <> Null And m_oBank <> Null) And m_oStream.Eof() = False Then
|
|
Local iClockNow:Int = getClock()
|
|
Repeat
|
|
Local iRemaining:Int = Min(m_iFileSize - m_oStream.Pos(), iBufferSize)
|
|
m_oBank.Read(m_oStream, m_oStream.Pos(), iRemaining)
|
|
|
|
iClockNow = getClock()
|
|
Until m_oStream.Eof() = True or getClockDiff(iClockStart, iClockNow) > (iMs / 1000.0 * getClocksPerSecond())
|
|
|
|
m_fProgress = m_oStream.Pos() / Float(m_iFileSize)
|
|
m_bComplete = m_oStream.Eof()
|
|
If m_oStream.Eof() = True Then
|
|
m_oStream.Close()
|
|
m_oStream = Null
|
|
EndIf
|
|
ElseIf m_bComplete = 0 Then
|
|
m_fProgress = 1.0
|
|
m_bComplete = -1
|
|
EndIf
|
|
EndMethod
|
|
|
|
Method Cleanup()
|
|
m_oBank = Null
|
|
EndMethod
|
|
EndType
|
|
|
|
|
|
|
|
|
|
Function DebugClock(Text:String)
|
|
DebugLog getClock() + ":" + Text
|
|
EndFunction
|
|
|
|
Extern "c"
|
|
Function getClocksPerSecond:Int()="getClocksPerSecond_"
|
|
Function getClock:Int()="getClock_"
|
|
Function getClockDiff:Int(clockStart:Int, clockEnd:Int)="getClockDiff_"
|
|
EndExtern |