und habe Thalius code angepasst.
die anpassungen, sind eigentliche proceduren die in andere HTTP projekte verwendet wurden ..
1. Procedure URLGetBasicAuth von luis
2. Procedure HTTP_CryptedUserPass von GPI
hostname wurde von benutzername und password bereinigt....
; Add more Checks & stuff here...
extras erweitert.
Code: Alles auswählen
; PureHTTP Include - Version: 0.42b
; OS: Linux / Windows / AmigaOS / MacOSX
; Author: Marius Eckardt (Thalius)
; a lil lib for HTTP ( for now just Downloading ;)
; Last Changes: 08.11.2007
;
; PureHTTP is aimed to be a HTTP 1.0-Compatible Crossplatform Library for use in your Projects.
; This code is PublicDomain - Credits apprechiated if you use it tho ;)
;
; Thx go to mp303 who brought me on the Idea initially with some Networkquestions in the Bugsection. :)
;
; Note: This Code is supposed to be fast but not super optimized. Basically ok for the Job - if you do optimizations
; / improvements you feel the Community could benefit from feel free to post them. :)
;
; Features:
; - HTTP Download to File with :
; - HTTP HEADER Info
; - HTTP Error Handling
; - HTTP 300 / 302 Redirect support
; - Connection Timeout Handling
; - Callback Support
; - Download Timer
; - KB/s o Meter
; - Limit Download Speed
;
; Changes:
; 0.2b:
; - Added Callback Function Support
; 0.3b:
; - lil Optimization
; 0.4b:
; - more Optimizations
; - Fixed Speedlimit To a per Kb/s Value
; - Added PureHTTP_SplitURL(url.s, type.l) - Splits an URL to Host/Path/Filename
; - Extended Example a little
; 0.41 - 0.42b:
; - small Optimizations
; - Added Redirect Support
;
; ToDo:
; - Bugfixes ?
; - Extend HTTP Header Structure to support more of HTTP/1.0
; - Download to Memory
; - HTTP Resume Support
; - GUI with Callback Example
; - HTTP POST Functionality
; - Manual
;- Konstants
; Network
#PureHTTP_Name = "PureHTTP" ; UserAgent Name
#PureHTTP_Defaultfile = "index.html" ; Default for index Files ( used to parse URLs )
#PureHTTP_Buffersize = 4096 ; Chunk-/Buffersize (This Value is balanced ok for average usage. There might be a speedgain on large files increasing this, but slowdown for smaller files)
#PureHTTP_Timeout = 10000 ; Network Timeout in ms
; Status
Enumeration
#PureHTTP_STATUS_CONNECTING ; Connecting
#PureHTTP_STATUS_CONNECTED ; Connected
#PureHTTP_STATUS_GOTHEADER ; Got Header info Filesize / Content-Type
#PureHTTP_STATUS_RECEIVE ; Receiving...
#PureHTTP_STATUS_REDIRECT ; HTTP Redirect
#PureHTTP_STATUS_IDLE ; Idle, Waiting for Data...
#PureHTTP_STATUS_FINISHED ; Download Finished
#PureHTTP_STATUS_TIMEOUT ; Timeout
#PureHTTP_STATUS_HTTPERROR ; HTTP Error / Return
#PureHTTP_STATUS_FILEERROR ; Can't create file
#PureHTTP_STATUS_ABORT ; Download Aborted
EndEnumeration
; HTTP URL Related
#PureHTTP_URL_Protocol = "http://"
; URL Return-Selection Konstants
Enumeration
#PureHTTP_URL_TO_HOST ; Host
#PureHTTP_URL_TO_PATH ; Path
#PureHTTP_URL_TO_FILE ; Filename
EndEnumeration
; HTTP Protocol Related
#HTTP_Port = 80
#HTTP_Protocol10 = "HTTP/1.0"
#HTTP_Protocol11 = "HTTP/1.1"
#HTTP_ContentLength = "Content-Length:"
#HTTP_ContentType = "Content-Type:"
; HTTP Retruncodes
#HTTP_CODE_OK = 200 ; OK
#HTTP_CODE_REDIRECT = 300 ; Redirect
#HTTP_CODE_TREDIRECT = 302 ; Transparent Redirect
Enumeration ; PBL constants
#PBL_OK = $0001
#PBL_WRITE_TO_FILE
#PBL_WRITE_TO_MEMORY
;******************************************************************************
;*** ERROR CONSTANTS **********************************************************
;******************************************************************************
#PBL_ERR_CALL_FAILED = $8000
#PBL_ERR_INVALID_PARAMETERS
#PBL_ERR_OUT_OF_MEMORY
#PBL_ERR_ABORT_REQUESTED
#PBL_ERR_NOT_FOUND
#PBL_ERR_FILE_IO
#PBL_ERR_FILE_CREATION
#PBL_ERR_HTTP_STATUS
#PBL_ERR_INTERNET_OPEN
#PBL_ERR_INTERNET_CONNECT
#PBL_ERR_INTERNET_SET_PROXY
#PBL_ERR_INTERNET_BASIC_AUTH
#PBL_ERR_INTERNET_HTTP_OPEN
#PBL_ERR_INTERNET_HTTP_SEND
#PBL_ERR_INTERNET_HTTP_QUERY_STATUS
#PBL_ERR_INTERNET_READ
#PBL_ERR_INTERNET_CLOSE
EndEnumeration
Global sTemp1.STRING, sTemp2.STRING,WebPass.s
Global lRetVal.l
;- Structures
; HTTP Header Struct
Structure _PureHTTP_HEADER
Returncode.l ; HTTP Return Code -> 200 = OK -> 404 = File Not Found
ContentLength.l ; Reported Content-Size
ContentType.s ; Reported Content-Type
HTTPReturnString.s ; HTTP Retrunstring -> "HTTP ERROR 404 Not Found"
EndStructure
; File Struct
Structure _PureHTTP_GET_FILE
; File
id.l ; FileIDThread ( in case we download multiple files at once )
*Callback ; Callback Function
ConID.l ; ConnectionID
host.s ; Host to Connect to
Path.s ; Path to File on Host
outputfile.s ; Path to local Filetarget
Status.l ; Download Status
; HTTP Header Meta Info
Header._PureHTTP_HEADER
; Network
*Buffer ; Pointer to our Paketbuffer
Totalbytes.l ; Holds actual Total bytes received from this File
StartTimer.l ; Start Timer in ms
Timer.l ; Current Our Timer in ms
Totaltime.l ; Total DownloadTime
limitspeed.l ; Limit Downloadspeed in Kb/s
kbps.f ; KB/s
EndStructure
;- Procs
; -- Semi Internal Functions
; Builds GET HTTP Header, kinda interna therefor in CAPS
; Returns HTTP HeaderString
Procedure.s HTTP_CryptedUserPass(user$,pass$); - Create the authorization string
If user$
conc$=user$+":"+pass$
OutputBuffer = AllocateMemory(Len(conc$)*2)
Base64Encoder(@conc$,Len(conc$),OutputBuffer,Len(conc$)*2)
penc$=PeekS(OutputBuffer)
FreeMemory(OutputBuffer)
Else
penc$=""
EndIf
ProcedureReturn penc$
EndProcedure
Procedure.l URLGetBasicAuth (sURL.s, *sUser.STRING, *sPassword.STRING)
;Author luis HTTPGetFromWeb() 2.02
; [in] sURL
; The full url to process (for example http://user:pass@www.somedomain.com/index.html)
; [out] *sUser
; Pointer to a STRING that will contain the user name extracted from the full url
; [out] *sPassword
; Pointer to a STRING that will contain the password extracted from the full url
; [return]
; #PBL_OK
; #PBL_ERR_NOT_FOUND
Protected lPos.l, lPosStart.l, lPosEnd.l
lPos = FindString(sURL, "@", 1)
If lPos
lPosEnd = lPos
lPosStart = FindString(sURL, "/", 1)
If Mid(sURL, lPosStart, 2) = "//"
lPos = FindString(sURL, ":", lPosStart + 2)
*sPassword\s = Mid(sURL, lPos + 1, lPosEnd - lPos - 1)
*sUser\s = Mid(sURL, lPosStart + 2, lPos - lPosStart - 2)
If Len(*sPassword\s) And Len(*sUser\s)
ProcedureReturn #PBL_OK
EndIf
EndIf
EndIf
*sUser\s = ""
*sPassword\s = ""
ProcedureReturn #PBL_ERR_NOT_FOUND
EndProcedure
Procedure.s PureHTTP_BUILDHEADER_HTTP_GET(*this._PureHTTP_GET_FILE)
Protected extras.s
If Len(*this\Path) <= 1
*this\Path = "/"
EndIf
; Add more Checks & stuff here...
extras.s = Chr(13) + Chr(10) + "User-Agent: " + #PureHTTP_Name
If WebPass<>""
extras+Chr(13) + Chr(10) +"Authorization: Basic "+WebPass+#CRLF$
EndIf
ProcedureReturn "GET " + *this\Path + " " + #HTTP_Protocol10 + Chr(13) + Chr(10) + "host: "+ *this\host + Chr(13) + Chr(10) +"Connection: Close" + extras.s + Chr(13) + Chr(10) + Chr(13) + Chr(10)
EndProcedure
; Set Download Status
; kinda Interna-helper function
; Can be used to send an event to thread
; for ex: PureHTTP_SET_STATUS(@mydownload, #PureHTTP_STATUS_ABORT )
; Aborts download.
Procedure PureHTTP_SET_STATUS(*this._PureHTTP_GET_FILE, Status.l)
;// Set Status
*this\Status = Status.l
;// Call Callback
If *this\Callback <> 0
CallFunctionFast(*this\Callback,*this)
EndIf
EndProcedure
; -- User Functions
; Func: PureHTTP_SplitURL(url.s, [ #Pure_HTTP_URL_* ])
; Desc: Little Helper Function to Extract Host/Path/Filename out of an URL
; Returns: String: Hostname, Path, Filename
; See Types:
; #PureHTTP_URL_HOST ; Host
; #PureHTTP_URL_PATH ; Path
; #PureHTTP_URL_FILE ; Filename
;
; Example: Debug PureHTTP_SplitURL("http://www.wavemage.com/edscore/5-EndTitle.mp3",#PureHTTP_URL_FILE)
; See example below for more...
Procedure.s PureHTTP_SplitURL(url.s, type.l = #PureHTTP_URL_TO_HOST)
Protected i.l, a.l
Result.s = ""
;// Do we have a http:// ?
a.l = FindString(url.s,#PureHTTP_URL_Protocol,0)
If a.l
url.s = Right(url.s,Len(url.s) - Len(#PureHTTP_URL_Protocol))
If type.l >= #PureHTTP_URL_TO_HOST And type.l <= #PureHTTP_URL_TO_FILE
;// Host
Result.s = StringField(url.s,0,"/")
If type.l = #PureHTTP_URL_TO_FILE
;// Filename
;// Find File
While Len(StringField(url.s,i,"/")) > 1
Result.s = StringField(url.s,i,"/")
i + 1
Wend
;// Sure we got a File ?
If FindString(Result.s,".",0) = 0 Or Result.s = StringField(url.s,0,"/")
;// No ? Assuming its an index HTML
Result.s = #PureHTTP_Defaultfile
EndIf
EndIf
;// Path
If type.l = #PureHTTP_URL_TO_PATH
Result.s = Right(url.s,Len(url.s)-Len(Result.s))
EndIf
EndIf
EndIf
at.l=FindString(Result.s,"@",1)
If at.l
Result.s=Right(Result.s,Len(Result.s)-at.l)
EndIf
ProcedureReturn Result.s
EndProcedure
; Func: PureHTTP_Get_File(@myfile)
; Returns: Statuscode See: #PureHTTP_STATUS_*
; Desc: Downloads File
Procedure PureHTTP_Get_File(*this._PureHTTP_GET_FILE)
Protected offset.l, eoffset.l, cloffset.l, ctoffset.l, Bytes.l, PaketCounter.l, *SEvent, *outfile
;// Set Status
PureHTTP_SET_STATUS(*this, #PureHTTP_STATUS_CONNECTING)
*this\ConID = OpenNetworkConnection( *this\host , #HTTP_Port )
If *this\ConID
;// Set Status
PureHTTP_SET_STATUS(*this, #PureHTTP_STATUS_CONNECTED)
Debug "* Connected"
;// Allocate Receivebuffer
*this\Buffer = AllocateMemory(#PureHTTP_Buffersize)
;// Init Timer
*this\StartTimer = ElapsedMilliseconds()
*this\Timer = *this\StartTimer
;// Send HTTP Request
SendNetworkString(*this\ConID,PureHTTP_BUILDHEADER_HTTP_GET(*this))
Debug "* Sending Request"
;// Wait for incoming Data ...
Debug "* Waiting for Reply"
Repeat
*SEvent = NetworkClientEvent(*this\ConID)
Select *SEvent
;// We received Data!
Case #PB_NetworkEvent_Data
Debug "* Receiving..."
;// Now go Fetch!
Repeat
Bytes.l = ReceiveNetworkData(*this\ConID, *this\Buffer, #PureHTTP_Buffersize)
;// Is this the first Paket containing the Header ?
If PaketCounter.l = 0
; PrintN(PeekS(*this\Buffer,#PureHTTP_Buffersize)) ;<- lil Debug For the header
;// Basic Process Header - Get DataOffsets
eoffset.l = FindString(PeekS(*this\Buffer, 200), #HTTP_Protocol10, 0) + Len(#HTTP_Protocol10)
If eoffset.l = Len(#HTTP_Protocol10) ;// We got a HTTP1.1 response, find new offset.
eoffset.l = FindString(PeekS(*this\Buffer, 200), #HTTP_Protocol11, 0) + Len(#HTTP_Protocol11)
EndIf
;// We sure in for a right Response now ?
If eoffset.l = Len(#HTTP_Protocol10) ; no .. still no offset
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_HTTPERROR)
Else ; Yeah baby!
cloffset.l = FindString(PeekS(*this\Buffer, #PureHTTP_Buffersize), #HTTP_ContentLength, eoffset.l) + Len(#HTTP_ContentLength)
ctoffset.l = FindString(PeekS(*this\Buffer, #PureHTTP_Buffersize), #HTTP_ContentType, eoffset.l) + Len(#HTTP_ContentType)
offset.l = FindString(PeekS(*this\Buffer, #PureHTTP_Buffersize),Chr(13) + Chr(10) + Chr(13) + Chr(10),eoffset.l) + 3
;// Do we have a Content-Length Info? if so, set Size
If cloffset.l <> Len(#HTTP_ContentLength)
*this\Header\ContentLength = Val(LTrim(RTrim(PeekS(*this\Buffer+cloffset.l,FindString(PeekS(*this\Buffer+cloffset.l,#PureHTTP_Buffersize-cloffset.l),Chr(13) + Chr(10),0)))))
EndIf
;// Set Content-Type
If ctoffset.l <> Len(#HTTP_ContentType)
*this\Header\ContentType = LTrim(RTrim(PeekS(*this\Buffer+ctoffset.l,FindString(PeekS(*this\Buffer+ctoffset.l,#PureHTTP_Buffersize-ctoffset.l),Chr(13) + Chr(10),0))))
EndIf
;// Select Header Response
*this\Header\Returncode = Val(PeekS(*this\Buffer+eoffset.l, 3))
Select *this\Header\Returncode
Case #HTTP_CODE_OK
;// Create File
If Len(*this\outputfile) > 0
*outfile = CreateFile(#PB_Any,*this\outputfile+".part")
EndIf
;//Set status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_GOTHEADER)
;// Write first bytes to File
If *outfile
Debug "Writing HeaderData"
*this\Totalbytes = Bytes.l - offset.l
WriteData(*outfile, *this\Buffer + offset.l, Bytes.l - offset.l)
Else
;// Set Status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_FILEERROR)
EndIf
;// Process HTTP REDIRECT
Case #HTTP_CODE_REDIRECT To #HTTP_CODE_TREDIRECT
Debug "302 -> Redirect"
;// Set Status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_REDIRECT)
;// Create File
*outfile = CreateFile(#PB_Any,*this\outputfile+".part")
;//Set status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_GOTHEADER)
;// Write first bytes to File
If *outfile
Debug "Writing HeaderData"
*this\Totalbytes = Bytes.l - offset.l
WriteData(*outfile, *this\Buffer + offset.l, Bytes.l - offset.l)
Else
;// Set Status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_FILEERROR)
EndIf
Default ; Unknown Error Code
;// Set Status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_HTTPERROR)
*this\Header\HTTPReturnString = PeekS(*this\Buffer+eoffset.l,FindString(PeekS(*this\Buffer+eoffset.l,200),Chr(13) + Chr(10),0))
EndSelect
EndIf
Else ;// Download Finished ?
If Bytes.l = 0
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_FINISHED)
Debug "** FINISHED **"
Else ;// No? ok then write some ...
*this\Totalbytes + Bytes.l
;// Set Status
PureHTTP_SET_STATUS(*this, #PureHTTP_STATUS_RECEIVE)
;// write Data To File
If *outfile
WriteData(*outfile,*this\Buffer,Bytes.l)
Else
;// Set Status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_FILEERROR)
Debug "Fileerror!"
EndIf
;// Update KB/s
*this\kbps = (*this\Totalbytes / ((ElapsedMilliseconds()-*this\StartTimer)/1000)) / 1024
Debug StrF(*this\kbps) + " kb/s"
;// Update Timer
*this\Timer = ElapsedMilliseconds()
EndIf
EndIf
PaketCounter.l + 1
;// Limit Speed to
If *this\limitspeed <> 0
While *this\kbps > *this\limitspeed
;// Update KB/s & wait out our Limit...
*this\kbps = (*this\Totalbytes / ((ElapsedMilliseconds()-*this\StartTimer)/1000)) / 1024
Delay(10)
Wend
EndIf
Until *this\Status >= #PureHTTP_STATUS_FINISHED
;// Close File
If *outfile
CloseFile(*outfile)
CopyFile(*this\outputfile+".part",*this\outputfile)
DeleteFile(*this\outputfile+".part")
EndIf
Default
;// Set Status
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_IDLE)
Debug "- IDLE -"
EndSelect
;// Check Timeout
If ElapsedMilliseconds() - *this\Timer > #PureHTTP_Timeout
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_TIMEOUT)
EndIf
;// Don't burn CPU while waiting...
Delay(10)
Until *this\Status >= #PureHTTP_STATUS_FINISHED
;// Free Memory
If *this\Buffer
FreeMemory(*this\Buffer)
EndIf
*this\Totaltime = ElapsedMilliseconds() - *this\StartTimer
If *this\ConID
CloseNetworkConnection(*this\ConID)
EndIf
Else
;// Connection Timeout
PureHTTP_SET_STATUS(*this,#PureHTTP_STATUS_TIMEOUT)
EndIf
ProcedureReturn *this\Status
EndProcedure
; **** END OF INCLUDE ****
; ******************************************************************************************************************************
;-Simple EXAMPLE
; ******************************************************************************************************************************
;- Example: Our happy Callback Function
Procedure MyCallback(*this._PureHTTP_GET_FILE)
Protected Pct.f
;// Calculate Percent
If *this\Status >= #PureHTTP_STATUS_GOTHEADER And *this\Totalbytes <> 0
Pct.f = ( 100 / *this\Header\ContentLength ) * *this\Totalbytes
EndIf
;// Print Status to Console
Select *this\Status
Case #PureHTTP_STATUS_GOTHEADER
PrintN("* Writing Header Information...")
Case #PureHTTP_STATUS_RECEIVE
PrintN("Received: "+Str(*this\Totalbytes/1024)+" kb -> "+StrF(Pct.f)+" % -> @ "+StrF(*this\kbps)+" kb/s")
Case #PureHTTP_STATUS_FINISHED
PrintN("* Finished! -> Assembling File...")
EndSelect
EndProcedure
;- Example: Basic Init
InitNetwork()
OpenConsole()
PrintN("-= PureHTTP Example =-")
; Set Example URL http://www.wavemage.com/edscore/5-EndTitle.mp3
PrintN(" > Enter URL to Download...")
;myurl.s = InputRequester("PureHTTP","Enter URL to Download (ex: http://www.purebasic.com ):","http://www.wavemage.com/edscore/5-EndTitle.mp3")
myurl.s="http://PBuser:space@purebasic.nu2-boot.de/test.exe"
;// Initialize our Download Object, THIS CAN BE A VARIBLE, ARRAY OR LINKEDLIST-ENTRY
myfile._PureHTTP_GET_FILE
;// Now we use our lil SplitURL Helper function to fill in Values
;// ofc we can do this manually too ;)
;// ex. myfile\host = "www.purebasic.com"
;
;// Set Host to Connect to
myfile\host = PureHTTP_SplitURL(myurl.s,#PureHTTP_URL_TO_HOST)
Debug "Host:"+myfile\host
;// Set Path on Host
myfile\Path = PureHTTP_SplitURL(myurl.s,#PureHTTP_URL_TO_PATH)
Debug "Path:"+myfile\Path
;// Set Path and File to write to
PrintN(" > Enter Path/File to Save to...")
myfile\outputfile = GetCurrentDirectory() + PureHTTP_SplitURL(myurl.s,#PureHTTP_URL_TO_FILE)
Debug "File:"+myfile\outputfile
URLGetBasicAuth (myurl, @sTemp1, @sTemp2)
Debug "URLGetBasicAuth = " + sTemp1\s + ":" + sTemp2\s
WebPass.s=HTTP_CryptedUserPass(sTemp1\s,sTemp2\s)
;// Advanced:
;// Callback Function ( this Parameter is Optional! )
myfile\Callback = @MyCallback()
;// Nice lil Feature to limit Speed ( this Parameter is Optional! )
PrintN(" > Enter Speedlimit in KB/s (if any)...")
myfile\limitspeed = Val(InputRequester("PureHTTP","Enter Speedlimit in KB/s -> 0 Means no Limit.","0")) ; Limit Speed to 100 kb/s
;// -- All set, lets go! --
PrintN("")
PrintN("* Starting Download of: "+Chr(10)+"http://"+myfile\host+myfile\Path+Chr(10))
PrintN("* Saving File at: "+Chr(10)+myfile\outputfile+Chr(10))
PrintN("* Connecting... "+Chr(10))
;// Start Download
Status.l = PureHTTP_Get_File(@myfile)
;// Select Status after we are done.
Select Status.l
Case #PureHTTP_STATUS_CONNECTING ; Connecting
Case #PureHTTP_STATUS_CONNECTED ; Connected
Case #PureHTTP_STATUS_RECEIVE ; Receiving...
Case #PureHTTP_STATUS_IDLE ; Idle, Waiting for Data...
Case #PureHTTP_STATUS_FINISHED ; Download Finished
PrintN("")
PrintN("*** File Download Finished ***")
PrintN("HTTP-Header Info -> "+"HTTP Return-Code: " +Str(myfile\Header\Returncode))
PrintN("Content-Type: "+myfile\Header\ContentType)
PrintN("Reported-Size : "+Str(myfile\Header\ContentLength)+" byte")
PrintN("")
PrintN("File : "+myfile\outputfile)
PrintN("Size : "+Str(myfile\Totalbytes/1024)+" kb")
PrintN("Time : "+Str(Round((myfile\Totaltime/1000)-0.5,1))+" s")
PrintN("Speed : "+StrF((myfile\Totalbytes / (myfile\Totaltime/1000)) / 1024)+" kb/s")
PrintN("")
Case #PureHTTP_STATUS_TIMEOUT ; Timeout
PrintN("* Connection Timeout!")
Case #PureHTTP_STATUS_HTTPERROR ; HTTP Error / Return
PrintN("HTTP ERROR "+myfile\Header\HTTPReturnString)
Case #PureHTTP_STATUS_REDIRECT
PrintN("HTTP REDIRECT 302")
Case #PureHTTP_STATUS_FILEERROR ; Cant create file
PrintN("Unable to Write File: "+myfile\outputfile)
Case #PureHTTP_STATUS_ABORT ; Download Aborted
EndSelect
PrintN("Press Enter to exit ...")
Input()
CloseConsole()
End
; IDE Options = PureBasic 4.10 Beta 3 (Linux - x86)
; ExecutableFormat = Console
; CursorPosition = 209
; FirstLine = 206