Auto-Update für eigene Spiele

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Midimaster

Betreff: Auto-Update für eigene Spiele

BeitragSa, Okt 01, 2011 15:30
Antworten mit Zitat
Benutzer-Profile anzeigen
Diese Funktion wird in ein Spiel eingebaut. Sie sucht dann auf einem fernen Server, ob es ein Update des Spiels gibt, läd das Update (im Hintergrund) in ein temp. Verzeichnis, beendet das Spiel und startet es neu.

Das Ganze besteht eigentlich aus zwei Programmen. Das Downloaden wird mit einer Funktion im Spiel (z.b. SPIEL.EXE) durchgeführt. Die Daten werden zunächst in einem temp.Verzeichnis zwischengespeichert. Am Ende startet SPIEL.EXE eine RESTART.EXE und beendet sich selbst. Diese RESTART.EXE kopiert nun die temp. Datei auf die SPIEL.EXE, startet SPIEL.EXE erneut und beendet sich selbst.

Hier ein Beispiel für MaxGui:
Erzeuge aus diesem Quellcode eine MYSPIEL.EXE. Damit es komplett funktioniert muss im gleichen Verzeichnis auch noch die RESTART.EXE vorhanden sein (siehe nächste CodeBox).

BlitzMax: [AUSKLAPPEN]
SuperStrict
Import MaxGUI.Drivers
Global Window:TGadget , UpdateButton:TGadget
Local flags%=WINDOW_TITLEBAR | WINDOW_CLIENTCOORDS|WINDOW_CENTER
Window= CreateWindow("Leeres Test Window" , 0 , 0 , 600 , 400 , Null , Flags%)
UpdateButton=CreateButton("Suche Update", 10,10,100,40,Window,BUTTON_PUSH)

' 5 Schritte sind zum Benützen der Funktion notwendig

' SCHRITT I: Erstellen eines T-Update-Objekt:
Global Update:TUpdate = TUpdate.CreateMe(Window) ' Window alsParent-Fenster

' SCHRITT II: Die nötigen Parameter werden übergeben:
Update.SetDatei "http://www.midimaster.de" , "download/Setup_Ohr.exe" , "Info.txt" , "1.0.1"

CreateTimer 10
While WaitEvent()
Local tmpGadget:TGadget
Select EventID()
Case EVENT_TIMERTICK
'Schritt IV: setzt event. lfd Update fort
Update.UpdateLauft

Case EVENT_GADGETACTION
Local tmpGadget:TGadget = TGadget(EventSource())
' If tmpGadget
Select tmpGadget
Case UpdateButton
' Schritt III: Dies öffnet das Update-Fenster
Update.NextStep -1

Default
' Schritt V: leitet Events an das Update-Fenster
Update.Event tmpGadget

End Select
Case EVENT_WINDOWCLOSE
End
End Select
Wend






Type TUpdate
Const RESET%=-1 , BEGINN%=0 , SOLL_ICH%=1, NIX_DA%=2 , TRANSFER%=3 , NEUSTART%=4 , SPAETER%=5 , RESTART%=6
Const YES%=1 , NO%=0 , FINISH%=2 , ABBRUCH%=3
Global Stream:TStream , SaveStream:TStream

Global Parent:TGadget , ProgressWindow:TGadget , ProgBar:TGadget
Global Label:TGadget , InfoZeile:TGadget , OkButton:Tgadget, AbbruchButton:Tgadget

Global AktMode% , URL$, Source$ , FinalDatei$ , Groesse% , InfoDatei$ , AlteVersion$ , NeueVersion$ , Running%




Function CreateMe:TUpdate(locParent:TGadget)
' erstellt das Update-Fenster als Tochter der Hauptanwendung
Parent=locParent
Local loc:TUpdate=New TUpdate
Local flags%=WINDOW_TITLEBAR | WINDOW_CLIENTCOORDS|WINDOW_CENTER
ProgressWindow= CreateWindow(StripExt(StripDir(AppFile)) + " Update" , 0 , 0 , 300 , 200 , Parent , Flags%)
label=CreateLabel("",30,70,250,25, ProgressWindow)
InfoZeile=CreateLabel("",30,100,250,25, ProgressWindow)
ProgBar = CreateProgBar(10,10,200,20,ProgressWindow)
OkButton=CreateButton("Ok", 210,170,80,20,ProgressWindow,BUTTON_PUSH)
AbbruchButton=CreateButton("Abbruch", 140,170,60,20,ProgressWindow,BUTTON_PUSH)
HideGadget ProgressWindow
Return loc
End Function





Function SetDatei (EchtUrl$, locSource$ , VersionInfoDatei$ , Version$)
' legt benötigte Parameter fest
'
' EchtURL = Server von der das Update kommt (Form: "http:://www.abc.de")
'
' Source = Pfad und Datei des Updates auf dem Server (ohne Slash am Anfang: "download/spiel.exe"
'
' VersionInfoDatei = txt-Datei auf dem Server, die Versionsinformationen enthält
' diese Datei muss eine Zeile enthalten: z.b. "spiel.exe=2.0.0"
'
' Version = akt. Versionsnummer der derzeitigen Datei (auf dem User-Rechner) als String
'
Source=locSource
FinalDatei = StripDir(Source)
Groesse= HttpFileSize(EchtUrl + "/" + Source)
If Instr(EchtUrl,"http://www.")
URL = EchtUrl.Replace("http://www.", "http::") + "/"
Else
RuntimeError "no valid URL"
EndIf
InfoDatei=VersionInfoDatei
AlteVersion=Version
End Function





Function Event (Gadget:TGadget)
' wird vom Hauptprogramm bei "Case EVENT_GADGETACTION" aufgerufen
Select Gadget
Case OKButton
NextStep YES
Case AbbruchButton
NextStep NO
End Select
End Function





Function CheckVorhanden%()
' prüft ob all benötigten Dateien verfügbar sind
Local Stream:TStream , ok% , Text$

' Server vorhanden?
DisableGadget OkButton
SetGadgetText Label,"Suche nach Update..."
Stream = ReadStream(URL + InfoDatei)
If Stream OK=1
If Ok=0
SetGadgetText InfoZeile,"Server nicht erreicht!"
Return False
Else
SetGadgetText InfoZeile,"Prüfe Versionsnummer..."
While Not Eof(Stream)
Text= ReadLine(Stream)
If Left(text,2)="<!"
SetGadgetText InfoZeile , "Informations-Datei nicht gefunden!"
CloseStream Stream
Return False
EndIf
If Instr(Text, FinalDatei) Then Exit
Wend

CloseStream Stream
If Instr(Text, FinalDatei)
Print Text
NeueVersion =Trim(Mid(Text,Instr(Text,"=")+1))
' Version neuer?
If NeueVersion=""
SetGadgetText InfoZeile , "Informations-Zeile nicht gefunden!"
Return False
ElseIf NeueVersion=AlteVersion
SetGadgetText InfoZeile,"kein aktuelleres Update vorhanden!"
Return False
EndIf
Else
SetGadgetText InfoZeile , "Informations-Zeile nicht gefunden!"
Return False
EndIf

EndIf
SetGadgetText InfoZeile,"Neue Version: " + NeueVersion

' Sourcedatei vorhanden?
Stream = ReadStream(Url + Source)
If Stream
Text= ReadLine(Stream)
CloseStream stream
If Left(text,2)="<!"
SetGadgetText InfoZeile,"Download nicht gefunden!"
Return False
EndIf
Else
SetGadgetText InfoZeile,"Download nicht gefunden!"
Return False
EndIf

EnableGadget OkButton
Return OK
End Function





Function NextStep(Wie%)
' schaltet Schritt für Schritt durch den Update-Prozess
' "Wie=-1" erzwingt einen Neustart der Update-Prozedur
' "Wie=YES" oder "Wie=NO" geben die Entscheidung des Users bekannt
If Wie=RESET
AktMode=BEGINN
ShowGadget ProgressWindow
SetGadgetText Label,""
SetGadgetText OkButton ,"OK"
ShowGadget OKButton
UpdateProgBar progbar,0.0
EndIf

Select AktMode
Case BEGINN
If CheckVorhanden()=False
DisableGadget OkButton
AktMode=NIX_DA
Else
SetGadgetText Label,"Ein Update steht bereit!"
SetGadgetText OkButton,"Übertragen"
AktMode=SOLL_ICH
EndIf
Case SOLL_ICH
If Wie = NO Then
HideGadget ProgressWindow
Else
AktMode=TRANSFER
UpdateLauft RESET
EndIf
Case TRANSFER
If Wie = NO Then
Running=ABBRUCH
EndIf
Case NEUSTART
If Wie =NO Then
AktMode=SPAETER
HideGadget ProgressWindow
Else
AktMODE=RESTART
Neustarten
EmitEvent CreateEvent:TEvent( EVENT_WINDOWCLOSE,Parent)
EndIf
Case NIX_DA
HideGadget ProgressWindow

End Select
End Function





Function UpdateLauft(WasTun%=0)
Global Z#, B#
Select WasTun
Case RESET
' der eigentliche Download beginnt
Local TempDir$=getenv_("TEMP") +"/"
' holt vom Server:
Stream = ReadStream(Url + Source)

'legt ins temp-dir ab:
SaveStream = WriteStream( TempDir + StripExt(FinalDatei) + ".Bin")
DisableGadget OkButton
SetGadgetText Label, "Daten werden übertragen "
Running=YES
Z=0
b=Groesse/256/95 ' Stepzahl für Progressbar (eigentlich :100, aber so läuft sie nicht zu früh auf 100%)
Default
' wird vom Hauptprogramm von Case EVENT_TIMERTICK gerufen
If Running=YES
' Download läuft
' geholt werden jedesmal 30 Blöcke a 256 Bytes =7.7kB
While Eof(Stream)=0
z=z+1
CopyBytes Stream, SaveStream,256
If (Int(z) Mod 30)=0
UpdateProgBar progbar,z/b/100.0
Return
EndIf
Wend
Running=FINISH

ElseIf Running=FINISH
CloseStream Stream
CloseStream SaveStream
' lässt die Progressbar noch auf 100% laufen:
Fake100 z/b/100.0

Running=NO
AktMode= NEUSTART
SetGadgetText Label,"Das Programm jetzt neu starten?"
SetGadgetText OkButton,"Neu starten"
EnableGadget OkButton
EnableGadget AbbruchButton
ElseIf Running=ABBRUCH
' User hat vorzeitigen Abbruch gewünscht
CloseStream Stream
CloseStream SaveStream
Running=NO
HideGadget ProgressWindow
EndIf
End Select
End Function




Function Fake100(Value#)
' witziger Gag: Progressbar läuft auf 100%
SetGadgetText Label, "Übertragung vollständig!"
RedrawGadget label
Repeat
UpdateProgBar progbar,Value
RedrawGadget progbar
value:+0.01
Delay 100
Until value>1
Delay 500
End Function




Function NeuStarten()
' ruft das Partnerprogramm RESTART.EXE auf und übergibt dafür den Dateinamen
Local process:TProcess = CreateProcess("restart.exe -" + FinalDatei)

' vergißt die Bindung zur RESTART.EXE, dadurch wird RESTART.EXE nicht gleich mitbeendet werden:
If process Then
TProcess.ProcessList.Remove process
EndIf
End Function



Function HttpFileSize:Int(url$)
' stellt die Dateigröße auf einem fernen Server fest
If Instr(url,"http://")
url$ = url$.Replace("http://", "")
Local slashPos% = url$.Find("/"), host$, file$

If slashPos <> -1
host$ = url$[..slashPos]
file$ = url$[slashPos..]
Else
Return -1
EndIf

Local stream:TStream=OpenStream("tcp::" + host$, 80)
If Not stream Then Return -1
stream.WriteLine "HEAD " + file$ + " HTTP/1.0"
stream.WriteLine "Host: " + host$
stream.WriteLine ""

While Not Eof(stream)
Local in$ = stream.ReadLine()

If in$.Find("Content-Length:") <> -1
stream.Close()
Return Int(in$[in$.Find(":") + 1..].Trim())
EndIf
Wend

stream.Close()
EndIf
Return -1
End Function

End Type


Hinweis:
Die im Beispiel verwendeten Test-Angaben führen zum Download einer harmlosen 300k Datei SETUP_OHR.EXE von meinem Server. Damit könnt ihr den Download experimentieren. (SETUP_OHR.EXE ist ein Installationsprogramm für eines meiner Spiele. Wenn es am Ende erfolgreich startet, kann man es einfach abbrechen.) Die im Beispiel verwendetete INFO.TXT befindet sich auch permanent auf meinem Server und enthält Zeilen mit den Versioninformationen meiner Programme.

Jetzt die RESTART.EXE (muss so genannt werden)

BlitzMax: [AUSKLAPPEN]
SuperStrict

'--TRICKY : ----------------------------------
'entweder kleines Framework
Framework BRL.Blitz
Import Pub.FreeProcess
Import BRL.Retro
'oder mit grafischer Oberfläche
' dann die Zeile FRAMEWORKS auskommentieren
' und in der Funkton "DrawTextEx()" die
' Zeilen Rem und EndRem auskommentieren!
DrawTextEx " ", MIT_GRAPHICS
'---------------------------------------

Global Datei$ , Source$, Dest$, y%
Const MIT_GRAPHICS%=-1 , MIT_FLIP%=1

' nur für Testzwecke:
DrawTextEx "Number of arguments: "+AppArgs.length
For Local arg:String = EachIn AppArgs
DrawTextEx arg
Next


' die ZielDatei wird als APP-Argument übergeben:
If AppArgs.Length>1
Datei=Mid(AppArgs[1],2,-1)
Dest=AppDir +"/" + Datei
EndIf


' die Sourcedatei liegt im Temp-Dir:
Source= getenv_("TEMP") + "/" + StripExt(Datei) + ".Bin"


' nur für Testzwecke:
DrawTextEx "Source=" + source
DrawTextEx "Dest= " + Dest, MIT_FLIP
DrawTextEx "Copying..."


If FileType(Source)=1
' temp-Datei auf echte EXE kopieren:
CopyFile Source , Dest
Else
RuntimeError "error source file not found"
End
EndIf
DrawTextEx "fertig" , MIT_FLIP

' sich selber beenden und dabei EXE aufrufen:
Local process:TProcess=CreateProcess(Dest)
TProcess.ProcessList.Remove process

End




' nur für Testzwecke:
Function DrawTextEx(Text$, Flag%=0)
Rem
If Flag = MIT_GRAPHICS
Graphics 900,300
Else
DrawText Text,0,y
y:+15
If Flag=1
Flip
WaitKey
EndIf
EndIf
End Rem

End Function



Um den Auto-Update in eigenen Programmen nutzen zu können, müssen nur Änderungen an einer Zeile in der MySpiel.Exe vorgenommen werden. Angenommen die Versionsnummer der aktuellen MySpiel.Exe ist 1.0.4, angenommen es soll später einmal die neue MySpiel.Exe 1.0.5 von dem Server www.myserver.de heruntergeladen werden können. Angenommen die Info-Datei heißt bei dir dort MyInFo.TxT. Dann ist aktuell folgende Zeile im Code anzupassen:

BlitzMax: [AUSKLAPPEN]
Update.SetDatei "http://www.myserver.de" , "MySpiel.Exe" ,  "MyInFo.TxT" , "1.0.4"


Wichtig ist, dass es dabei auf die Groß/Kleinschreibung ankommt!

Erscheint später einmal wirklich die 1.0.5 oder die 2.0.0, so muss auf dem Server eine Datei MyInFo.TxT vorhanden sein, die neben beliebig weiteren Zeilen vor allem diese Zeile enthalten muss:
Code: [AUSKLAPPEN]
MySpiel.Exe=2.0.0


Klickt der User draußen nun auf den Update-Knopf, wird die Information auf dem Server entdeckt, dass es ein Update gibt und der Update-Prozess startet.

Hier nun eine Silent-Version ohne MaxGui:

BlitzMax: [AUSKLAPPEN]
SuperStrict
Graphics 800,600

' 4 Schritte sind zum Benützen der Funktion notwendig

' SCHRITT I: Erstellen eines T-Update-Objekt:
Global Update:TUpdate = TUpdate.CreateMe()

' SCHRITT II: Die nötigen Parameter werden übergeben:
Update.SetDatei "http://www.midimaster.de" , "download/Setup_Ohr.exe" , "Info.txt" , "1.0.0"


Local FPS:TTimer=CreateTimer(10)
Print "press <U> to check for Update"


Repeat
If KeyHit(KEY_U) Then
' Schritt III: Dies startet den Update-Prozess
Update.FirstStep
EndIf

'Schritt IV: setzt event. lfd Update fort
Update.UpdateLauft

Flip
WaitTimer FPS
Until AppTerminate()




Type TUpdate
Const RESET%=-1 , BEGINN%=0 , NIX_DA%=2 , TRANSFER%=3 , RESTART%=6
Const YES%=1 , NO%=0 , FINISH%=2 , ABBRUCH%=3
Global Stream:TStream , SaveStream:TStream

Global AktMode% , URL$, Source$ , FinalDatei$ , Groesse% , InfoDatei$ , AlteVersion$ , NeueVersion$ , Running%




Function CreateMe:TUpdate()
' erstellt ein TUpdate-Objekt
Local loc:TUpdate=New TUpdate
Return loc
End Function





Function SetDatei (EchtUrl$, locSource$ , VersionInfoDatei$ , Version$)
' legt benötigte Parameter fest
'
' EchtURL = Server von der das Update kommt (Form: "http:://www.abc.de")
'
' Source = Pfad und Datei des Updates auf dem Server (ohne Slash am Anfang: "download/spiel.exe"
'
' VersionInfoDatei = txt-Datei auf dem Server, die Versionsinformationen enthält
' diese Datei muss eine Zeile enthalten: z.b. "spiel.exe=2.0.0"
'
' Version = akt. Versionsnummer der derzeitigen Datei (auf dem User-Rechner) als String
'
Source=locSource
FinalDatei = StripDir(Source)
Groesse= HttpFileSize(EchtUrl + "/" + Source)
If Instr(EchtUrl,"http://www.")
URL = EchtUrl.Replace("http://www.", "http::") + "/"
Else
RuntimeError "no valid URL"
EndIf
InfoDatei=VersionInfoDatei
AlteVersion=Version
End Function






Function CheckVorhanden%()
' prüft ob all benötigten Dateien verfügbar sind
Local Stream:TStream , ok% , Text$

' Server vorhanden?
Print "Suche nach Informations-Datei"
Stream = ReadStream(URL + InfoDatei)
If Stream OK=1
If Ok=0
Print "Server nicht erreicht!"
Return False
Else
Print "Prüfe Versionsnummer..."
While Not Eof(Stream)
Text= ReadLine(Stream)
If Left(text,2)="<!"
Print "Informations-Datei nicht gefunden!"
CloseStream Stream
Return False
EndIf
If Instr(Text, FinalDatei) Then Exit
Wend

CloseStream Stream
If Instr(Text, FinalDatei)
NeueVersion =Trim(Mid(Text,Instr(Text,"=")+1))
' Version neuer?
If NeueVersion=""
Print "Informations-Zeile nicht gefunden!"
Return False
ElseIf NeueVersion=AlteVersion
Print "kein aktuelleres Update vorhanden!"
Return False
EndIf
Else
Print "Informations-Zeile nicht gefunden!"
Return False
EndIf

EndIf
Print "Neue Version: " + NeueVersion

' Sourcedatei vorhanden?
Stream = ReadStream(Url + Source)
If Stream
Text= ReadLine(Stream)
CloseStream stream
If Left(text,2)="<!"
Print "Download nicht gefunden!"
Return False
EndIf
Else
Print "Download nicht gefunden!"
Return False
EndIf
Return OK
End Function





Function FirstStep()
Print "S T A R T U P D A T E P R O Z E S S"
AktMode=BEGINN
If CheckVorhanden()=False
AktMode=NIX_DA
Print "Abbruch, kein Update möglich"
Else
Print "Ein Update steht bereit!"
AktMode=TRANSFER
UpdateLauft RESET
EndIf
End Function





Function UpdateLauft%(WasTun%=0)
Global Z#, B#
Select WasTun
Case RESET
' der eigentliche Download beginnt
Local TempDir$=getenv_("TEMP") +"/"
' holt vom Server:
Stream = ReadStream(Url + Source)
If Stream =Null
Print "ERROR II"
EndIf

'legt ins temp-dir ab:
SaveStream = WriteStream( TempDir + StripExt(FinalDatei) + ".Bin")
Print "Daten werden übertragen "
Running=YES
Z=0
b=Groesse/256/95 ' Stepzahl für Progressbar (eigentlich :100, aber so läuft sie nicht zu früh auf 100%)
Default
' wird vom Hauptprogramm von Case EVENT_TIMERTICK gerufen
If Running=YES
' Download läuft
' geholt werden jedesmal 30 Blöcke a 256 Bytes =7.7kB
While Eof(Stream)=0
z=z+1
CopyBytes Stream, SaveStream,256
If (Int(z) Mod 30)=0
Print Int(z/b) + "%"
Return 1
EndIf
Wend
Running=FINISH

ElseIf Running=FINISH
CloseStream Stream
CloseStream SaveStream
' lässt die Progressbar noch auf 100% laufen:
Fake100 z/b/100.0

Running=NO
AktMODE=RESTART
Neustarten
End
ElseIf Running=ABBRUCH
' User hat vorzeitigen Abbruch gewünscht
CloseStream Stream
CloseStream SaveStream
Running=NO
Return 0
EndIf
End Select
End Function




Function Fake100(Value#)
Print "100%"
Print "Übertragung vollständig!"
End Function




Function NeuStarten()
' ruft das Partnerprogramm RESTART.EXE auf und übergibt dafür den Dateinamen
Local process:TProcess = CreateProcess("restart.exe -" + FinalDatei)

' vergißt die Bindung zur RESTART.EXE, dadurch wird RESTART.EXE nicht gleich mitbeendet werden:
If process Then
TProcess.ProcessList.Remove process
EndIf
End Function




Function HttpFileSize:Int(url$)
' stellt die Dateigröße auf einem fernen Server fest
If Instr(url,"http://")
url$ = url$.Replace("http://", "")
Local slashPos% = url$.Find("/"), host$, file$

If slashPos <> -1
host$ = url$[..slashPos]
file$ = url$[slashPos..]
Else
Return -1
EndIf

Local stream:TStream=OpenStream("tcp::" + host$, 80)
If Not stream Then Return -1
stream.WriteLine "HEAD " + file$ + " HTTP/1.0"
stream.WriteLine "Host: " + host$
stream.WriteLine ""

While Not Eof(stream)
Local in$ = stream.ReadLine()

If in$.Find("Content-Length:") <> -1
stream.Close()
Return Int(in$[in$.Find(":") + 1..].Trim())
EndIf
Wend

stream.Close()
EndIf
Return -1
End Function

End Type

Die RESTART.EXE bleibt hierfür die gleiche. Die Anmerkungen (siehe oben) sind ebenfalls gleich.

Für Kommentare und Verbesserungsvorschläge wäre ich dankbar.
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe
 

Hangman

BeitragSa, Okt 01, 2011 17:33
Antworten mit Zitat
Benutzer-Profile anzeigen
Astrein, vielen Dank.
Ich habe Berthold gebrochen.

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group