can't be null

can't be null

' Blue Moon
' Lua-table implementation
' uses simple linear probing for collision resolution and a shockingly naive reallocator

SuperStrict

Import "blueallocator.bmx"

Private
Extern
	Function frexp:Double(d:Double, Exp:Int Ptr) = "frexp"
End Extern
Public

Type BlueTable Final
	Function Get:Long(tbl:Byte Ptr, key:Long)
		Local tag:Int = Int Ptr(Varptr(key))[1], idx:Int

		If tag = BlueTypeTag.NANBOX | BlueTypeTag.STR	'string - common case
			idx = Int Ptr(Int(key))[1]
		ElseIf tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX	'number
			Local d:Double ; Long Ptr(Varptr(d))[0] = key ; idx = Abs Int(d)
			If d = idx	'int key
				Local arr:Byte Ptr = Byte Ptr Ptr(tbl)[3]
				If arr And idx < Int Ptr(arr)[-1] Then Return Long Ptr(arr)[idx]	'if this is nil, it won't be in the hash part anyway
			Else
				d = frexp(d, Varptr(idx)) * ($7fffffff - 1024)	'INT_MAX - DBL_MAX_EXP
				idx = Abs(idx) + Int(d)
			EndIf
		ElseIf tag = BlueTypeTag.NANBOX | BlueTypeTag.NIL	'nil -> nil
			Return key
		Else	'pointer (needs improvement)
			idx = Int(key) Shr 3
		EndIf

		Local hashpart:Byte Ptr = Byte Ptr Ptr(tbl)[2]
		If hashpart
			Local hsize:Int = 1 Shl Int Ptr(hashpart)[-1]
			idx = idx & (hsize - 1)	'apparently this is faster than Mod
			For Local i:Int = idx Until hsize	'naive linear probe
				If Long Ptr(hashpart)[2 * i] = key Then Return Long Ptr(hashpart)[2 * i + 1]
			Next
			For Local i:Int = 0 Until idx	'yep
				If Long Ptr(hashpart)[2 * i] = key Then Return Long Ptr(hashpart)[2 * i + 1]
			Next
		EndIf

		Local ret:Long ; Int Ptr(Varptr(ret))[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL	'why can't i shift longs?
		Return ret
	End Function

	Function Set(mem:BlueVMMemory, tbl:Byte Ptr, key:Long, val:Long)
		Local tag:Int = Int Ptr(Varptr(key))[1], idx:Int, slot:Long Ptr = Null

		If tag = BlueTypeTag.NANBOX | BlueTypeTag.NIL Then Return	'nil is not a valid key

		If tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX	'if key is an int and less than arraylength
			Local d:Double ; Long Ptr(Varptr(d))[0] = key ; idx = Abs Int(d)
			If d = idx
				Local arraypart:Byte Ptr = Byte Ptr Ptr(tbl)[3]
				If arraypart <> Null And idx < Int Ptr(arraypart)[-1] Then slot = Long Ptr(arraypart) + idx
			EndIf
		EndIf
		If slot = Null	'elsewise if hashcount < hashsize
			Local hashpart:Byte Ptr = Byte Ptr Ptr(tbl)[2]
			If hashpart <> Null
				Local hsize:Int = 1 Shl Int Ptr(hashpart)[-1]
				If Int Ptr(hashpart)[-2] < hsize	'there's a slot free somewhere

					If tag = BlueTypeTag.NANBOX | BlueTypeTag.STR	'string - common case
						idx = Int Ptr(Int(key))[1]
					ElseIf tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX	'non-integer
						Local d:Double ; Long Ptr(Varptr(d))[0] = key
						d = frexp(d, Varptr(idx)) * ($7fffffff - 1024)	'INT_MAX - DBL_MAX_EXP
						idx = Abs(idx) + Int(d)
					Else	'pointer
						idx = Int(key) Shr 3
					EndIf

					idx = idx & (hsize - 1)	'fastmod
					For Local i:Int = idx Until hsize
						If Long Ptr(hashpart)[2 * i] = key Then slot = Long Ptr(hashpart) + (2 * i + 1)
					Next
					For Local i:Int = 0 Until idx
						If Long Ptr(hashpart)[2 * i] = key Then slot = Long Ptr(hashpart) + (2 * i + 1)
					Next

				EndIf
			EndIf
			If slot Then Int Ptr(hashpart)[-2] :+ 1	'no write barrier needed (not pointer)
		EndIf

		If slot = Null
			Resize(mem, tbl, key) ; Set mem, tbl, key, val
		Else
			mem.Write(slot, val)
		EndIf
	End Function

	Function Resize(mem:BlueVMMemory, tbl:Byte Ptr, key:Long)
		Local hashpart:Byte Ptr = Byte Ptr Ptr(tbl)[2], arraypart:Byte Ptr = Byte Ptr Ptr(tbl)[3]
		Const NILTAG:Int = BlueTypeTag.NANBOX | BlueTypeTag.NIL

		Local asize:Int = 0, tsize:Int = 0, numcount:Int[32]
		If arraypart	'compute new array size
			If Int Ptr(arraypart)[1] <> NILTAG Then numcount[0] = 1
			Local cell:Int = 1, cellp2:Int = 2 ^ cell
			For Local i:Int = 1 Until Int Ptr(arraypart)[-1]
				If i >= cellp2 Then cell :+ 1 ; cellp2 = 2 ^ cell
				If Int Ptr(arraypart)[i * 2 + 1] <> NILTAG Then numcount[cell] :+ 1
			Next
		EndIf
		If hashpart
			For Local i:Int = 0 Until Int Ptr(hashpart)[-2]
				Local tag2:Int = Int Ptr(hashpart)[i * 4 + 1]
				If tag2 & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX
					Local d:Double = Double Ptr(hashpart)[i * 2]
					If d = Abs Int(d) Then numcount[Ceil(Log(d + 1) / Log(2))] :+ 1 Else tsize :+ 1
				Else
					tsize :+ 1	'get started on new table size
				EndIf
			Next
		EndIf
		Local tag:Int = Int Ptr(Varptr(key))[1]	'add key to the appropriate one
		If tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX
			Local d:Double = Double Ptr(Varptr(key))[0]
			If d = Abs Int(d) Then numcount[Ceil(Log(d + 1) / Log(2))] :+ 1 Else tsize :+ 1
		Else
			tsize :+ 1
		EndIf
		For Local i:Int = 0 Until 32	'tally up
			If i Then numcount[i] :+ numcount[i - 1]
			If numcount[i] > 2 ^ i / 2 Then asize = 2 ^ i
		Next

		If arraypart <> Null	'complete new table size with any discarded array elements
			For Local i:Int = asize Until Int Ptr(arraypart)[-1]
				If Int Ptr(arraypart)[i * 2 + 1] <> NILTAG Then tsize :+ 1
			Next
		EndIf

		Local newarray:Byte Ptr = Null, newtable:Byte Ptr = Null	'allocate and copy
		If asize
			newarray = mem.AllocObject(asize * 8 + 8, BlueTypeTag.ARR) + 8
			For Local i:Int = 0 Until asize
				Int Ptr(newarray)[i * 2 + 1] = NILTAG
			Next
			Int Ptr(newarray)[-1] = asize ; Byte Ptr Ptr(tbl)[3] = newarray
		Else
			Int Ptr(tbl)[3] = 0
		EndIf
		If tsize
			newtable = mem.AllocObject(2 ^ tsize * 16 + 8, BlueTypeTag.HASH)
			For Local i:Int = 0 Until 2 ^ tsize * 2
				Int Ptr(newtable)[i * 2 + 1] = NILTAG
			Next
			Int Ptr(newtable)[-1] = tsize ; Byte Ptr Ptr(tbl)[2] = newtable
		Else
			Int Ptr(tbl)[2] = 0
		EndIf

		If arraypart
			For Local i:Int = 0 Until Int Ptr(arraypart)[-1]	'reinsert values
				Local key:Long ; Double Ptr(Varptr(key))[0] = i
				Set mem, tbl, key,Long Ptr(arraypart)[i]
			Next
		EndIf
		If hashpart
			For Local i:Int = 0 Until Int Ptr(hashpart)[-1]
				Set mem, tbl, Long Ptr(hashpart)[i * 2], Long Ptr(hashpart)[i * 2 + 1]
			Next
		EndIf
	End Function
End Type

' Blue Moon
' Lua-table implementation
' uses simple linear probing for collision resolution and a shockingly naive reallocator

SuperStrict

Import "blueallocator.bmx"

Private
Extern
	Function frexp:Double(d:Double, Exp:Int Ptr) = "frexp"
End Extern
Public

Type BlueTable Final
	Function Get:Long(tbl:Byte Ptr, key:Long)
		Local tag:Int = Int Ptr(Varptr(key))[1], idx:Int

		If tag = BlueTypeTag.NANBOX | BlueTypeTag.STR	'string - common case
			idx = Int Ptr(Int(key))[1]
		ElseIf tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX	'number
			Local d:Double ; Long Ptr(Varptr(d))[0] = key ; idx = Abs Int(d)
			If d = idx	'int key
				Local arr:Byte Ptr = Byte Ptr Ptr(tbl)[3]
				If arr And idx < Int Ptr(arr)[-1] Then Return Long Ptr(arr)[idx]	'if this is nil, it won't be in the hash part anyway
			Else
				d = frexp(d, Varptr(idx)) * ($7fffffff - 1024)	'INT_MAX - DBL_MAX_EXP
				idx = Abs(idx) + Int(d)
			EndIf
		ElseIf tag = BlueTypeTag.NANBOX | BlueTypeTag.NIL	'nil -> nil
			Return key
		Else	'pointer (needs improvement)
			idx = Int(key) Shr 3
		EndIf

		Local hashpart:Byte Ptr = Byte Ptr Ptr(tbl)[2]
		If hashpart
			Local hsize:Int = 1 Shl Int Ptr(hashpart)[-1]
			idx = idx & (hsize - 1)	'apparently this is faster than Mod
			For Local i:Int = idx Until hsize	'naive linear probe
				If Long Ptr(hashpart)[2 * i] = key Then Return Long Ptr(hashpart)[2 * i + 1]
			Next
			For Local i:Int = 0 Until idx	'yep
				If Long Ptr(hashpart)[2 * i] = key Then Return Long Ptr(hashpart)[2 * i + 1]
			Next
		EndIf

		Local ret:Long ; Int Ptr(Varptr(ret))[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL	'why can't i shift longs?
		Return ret
	End Function

	Function Set(mem:BlueVMMemory, tbl:Byte Ptr, key:Long, val:Long)
		Local tag:Int = Int Ptr(Varptr(key))[1], idx:Int, slot:Long Ptr = Null

		If tag = BlueTypeTag.NANBOX | BlueTypeTag.NIL Then Return	'nil is not a valid key

		If tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX	'if key is an int and less than arraylength
			Local d:Double ; Long Ptr(Varptr(d))[0] = key ; idx = Abs Int(d)
			If d = idx
				Local arraypart:Byte Ptr = Byte Ptr Ptr(tbl)[3]
				If arraypart <> Null And idx < Int Ptr(arraypart)[-1] Then slot = Long Ptr(arraypart) + idx
			EndIf
		EndIf
		If slot = Null	'elsewise if hashcount < hashsize
			Local hashpart:Byte Ptr = Byte Ptr Ptr(tbl)[2]
			If hashpart <> Null
				Local hsize:Int = 1 Shl Int Ptr(hashpart)[-1]
				If Int Ptr(hashpart)[-2] < hsize	'there's a slot free somewhere

					If tag = BlueTypeTag.NANBOX | BlueTypeTag.STR	'string - common case
						idx = Int Ptr(Int(key))[1]
					ElseIf tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX	'non-integer
						Local d:Double ; Long Ptr(Varptr(d))[0] = key
						d = frexp(d, Varptr(idx)) * ($7fffffff - 1024)	'INT_MAX - DBL_MAX_EXP
						idx = Abs(idx) + Int(d)
					Else	'pointer
						idx = Int(key) Shr 3
					EndIf

					idx = idx & (hsize - 1)	'fastmod
					For Local i:Int = idx Until hsize
						If Long Ptr(hashpart)[2 * i] = key Then slot = Long Ptr(hashpart) + (2 * i + 1)
					Next
					For Local i:Int = 0 Until idx
						If Long Ptr(hashpart)[2 * i] = key Then slot = Long Ptr(hashpart) + (2 * i + 1)
					Next

				EndIf
			EndIf
		EndIf

		If slot = Null
			Resize(mem, tbl, key) ; Set Null, tbl, key, val
		Else
			mem.Write(slot, val)
		EndIf
	End Function

	Function Resize(mem:BlueVMMemory, tbl:Byte Ptr, key:Long)
		Local hashpart:Byte Ptr = Byte Ptr Ptr(tbl)[2], arraypart:Byte Ptr = Byte Ptr Ptr(tbl)[3]
		Const NILTAG:Int = BlueTypeTag.NANBOX | BlueTypeTag.NIL

		Local asize:Int = 0, tsize:Int = 0, numcount:Int[32]
		If arraypart	'compute new array size
			If Int Ptr(arraypart)[1] <> NILTAG Then numcount[0] = 1
			Local cell:Int = 1, cellp2:Int = 2 ^ cell
			For Local i:Int = 1 Until Int Ptr(arraypart)[-1]
				If i >= cellp2 Then cell :+ 1 ; cellp2 = 2 ^ cell
				If Int Ptr(arraypart)[i * 2 + 1] <> NILTAG Then numcount[cell] :+ 1
			Next
		EndIf
		If hashpart
			For Local i:Int = 0 Until Int Ptr(hashpart)[-2]
				Local tag2:Int = Int Ptr(hashpart)[i * 4 + 1]
				If tag2 & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX
					Local d:Double = Double Ptr(hashpart)[i * 2]
					If d = Abs Int(d) Then numcount[Ceil(Log(d + 1) / Log(2))] :+ 1 Else tsize :+ 1
				Else
					tsize :+ 1	'get started on new table size
				EndIf
			Next
		EndIf
		Local tag:Int = Int Ptr(Varptr(key))[1]	'add key to the appropriate one
		If tag & BlueTypeTag.NANBOX_CHK <> BlueTypeTag.NANBOX
			Local d:Double = Double Ptr(Varptr(key))[0]
			If d = Abs Int(d) Then numcount[Ceil(Log(d + 1) / Log(2))] :+ 1 Else tsize :+ 1
		Else
			tsize :+ 1
		EndIf
		For Local i:Int = 0 Until 32	'tally up
			If i Then numcount[i] :+ numcount[i - 1]
			If numcount[i] > 2 ^ i / 2 Then asize = 2 ^ i
		Next

		If arraypart <> Null	'complete new table size with any discarded array elements
			For Local i:Int = asize Until Int Ptr(arraypart)[-1]
				If Int Ptr(arraypart)[i * 2 + 1] <> NILTAG Then tsize :+ 1
			Next
		EndIf

		Local newarray:Byte Ptr = Null, newtable:Byte Ptr = Null	'allocate and copy
		If asize
			newarray = mem.AllocObject(asize * 8 + 8, BlueTypeTag.ARR) + 8
			For Local i:Int = 0 Until asize
				Int Ptr(newarray)[i * 2 + 1] = NILTAG
			Next
			Int Ptr(newarray)[-1] = asize ; Byte Ptr Ptr(tbl)[3] = newarray
		Else
			Int Ptr(tbl)[3] = 0
		EndIf
		If tsize
			tsize = 2 ^ tsize
			newtable = mem.AllocObject(tsize * 16 + 8, BlueTypeTag.HASH)
			For Local i:Int = 0 Until tsize * 2
				Int Ptr(newtable)[i * 2 + 1] = NILTAG
			Next
			Int Ptr(newtable)[-1] = tsize ; Byte Ptr Ptr(tbl)[2] = newtable
		Else
			Int Ptr(tbl)[2] = 0
		EndIf

		For Local i:Int = 0 Until Int Ptr(arraypart)[-1]	'reinsert values
			Set Null, tbl, Double(i),Long Ptr(arraypart)[i]
		Next
		For Local i:Int = 0 Until Int Ptr(hashpart)[-1]
			Set Null, tbl, Long Ptr(hashpart)[i * 2], Long Ptr(hashpart)[i * 2 + 1]	'Null used here as a sentinel (it should not be used again, so...)
		Next
	End Function
End Type


Changed default resolution to 640x480 to match the other targets (HTML5, Flash and GLFW)

SuperStrict

Global _errStack:TList = New TList;
Global _errInfo:String="";

Graphics 640, 480

bbInit();
bbMain();

'${TRANSCODE_BEGIN}
'${TRANSCODE_END}

SuperStrict

Global _errStack:TList = New TList;
Global _errInfo:String="";

Graphics 800, 600

bbInit();
bbMain();

'${TRANSCODE_BEGIN}
'${TRANSCODE_END}


Fix for bmk check on Windows

Fix for bmk check on Windows

SuperStrict

Framework brl.blitz
Import brl.standardio
Import brl.maxutil
Import brl.ramstream

' We have to import this for BMax 1.39 on Linux because of a dependency issue in brl.system
' http://blitzbasic.com/Community/posts.php?topic=90547
?Linux
Import brl.glgraphics
?

Import bah.volumes
Import gman.zipengine
Import htbaapub.rest

Import duct.variables
Import duct.objectmap
Import duct.json
Import duct.locale
Import duct.argparser
Import duct.time

Incbin "locales/en.loc"

Include "src/logger.bmx"
Include "src/errors.bmx"
Include "src/config.bmx"
Include "src/dependencies.bmx"
Include "src/module.bmx"
Include "src/sources.bmx"
Include "src/utils.bmx"
Include "src/arghandler.bmx"
Include "src/impl/help.bmx"
Include "src/impl/version.bmx"
Include "src/impl/modpath.bmx"
Include "src/impl/install.bmx"
Include "src/impl/update.bmx"
Include "src/impl/list.bmx"

Global logger:mxLogger = New mxLogger
Global mainapp:mxApp
New mxApp.Create(AppArgs[1..]) ' Skip the first element because it is the program's location
mainapp.Run()

Rem
	bbdoc: Maximus app.
	about: This handles the basic flow of the program (initiation, parsing, command calling, exiting..)
End Rem
Type mxApp

	Const c_version:String = "0.01"
	Const c_configfile:String = "maximus.config"

	Field m_apppath:String
	Field m_maxpath:String, m_binpath:String, m_modpath:String

	Field m_confighandler:mxConfigHandler
	Field m_defaultlocale:dLocale, m_locale:dLocale
	Field m_sourcesfile:String = "sources", m_sourcesurl:String = "http://maximus.htbaa.com/module/sources/json"
	Field m_useragent:String
	Field m_autoupdatesources:Int = True

	Field m_args:dIdentifier
	Field m_arghandler:mxArgumentHandler
	Field m_sourceshandler:mxSourcesHandler

	Field m_updateimpl:mxUpdateImpl
	Field m_sourcesupdated:Int

	Rem
		bbdoc: Create a new mxApp.
		returns: Itself.
	End Rem
	Method Create:mxApp(args:String[])
		mainapp = Self
		SetArgs(args)
		OnInit()
		Return Self
	End Method

	Rem
		bbdoc: Called when the application is created.
		returns: Nothing.
	End Rem
	Method OnInit()
		m_apppath = GetUserAppDir() + "/"
		?Linux
		m_apppath:+ ".maximus/"
		?Not Linux
		m_apppath:+ "maximus/"
		?
		If FileType(m_apppath) = FILETYPE_NONE
			If Not CreateDir(m_apppath, False)
				ThrowError(_s("error.createperms", [m_apppath]))
			End If
		End If
		ChangeDir(m_apppath)
		m_confighandler = New mxConfigHandler.Create(m_apppath + c_configfile)
		m_confighandler.LoadDefaultLocale()
		m_confighandler.Load()
		If Not m_maxpath
			Try
				SetMaxPath(BlitzMaxPath())
			Catch e:Object
				ThrowError(_s("error.notfound.maxenv"))
			End Try
		End If
		If Not m_modpath Then SetModPath(m_maxpath + "/mod", False)
		m_arghandler = New mxArgumentHandler
		m_arghandler.AddArgImpl(New mxHelpImpl)
		m_arghandler.AddArgImpl(New mxVersionImpl)
		m_arghandler.AddArgImpl(New mxModPathImpl)
		m_arghandler.AddArgImpl(New mxInstallImpl)
		m_updateimpl = New mxUpdateImpl
		m_arghandler.AddArgImpl(m_updateimpl)
		m_arghandler.AddArgImpl(New mxListImpl)
		UpdateSources()
		m_sourceshandler = New mxSourcesHandler.FromFile(m_apppath + m_sourcesfile)
		If m_sourceshandler = Null
			' Don't throw an error here, the user may be updating the sources (an error will occur otherwise)
			logger.LogWarning(_s("error.load.sources.file", [m_apppath + m_sourcesfile]))
		End If
		Local os:String
		?Win32
			os = "Windows"
		?Linux
			os = "Linux"
		?MacOS
			os = "MacOS"
		?
		m_useragent = "Maximus/" + c_version + " (" + os + "; " + m_locale.GetName() + ")"
	End Method

	Rem
		bbdoc: Called when the application is ended (this includes #ThrowError calls).
		returns: Nothing.
	End Rem
	Method OnExit()
	End Method

	Rem
		bbdoc: Run the application.
		returns: Nothing.
	End Rem
	Method Run()
		Local argimpl:mxArgumentImplementation, isopt:Int
		For Local arg:dIdentifier = EachIn m_args.GetValues()
			argimpl = m_arghandler.GetArgImplFromAlias(arg.GetName())
			isopt = (arg.GetName()[0] = 45)
			If argimpl <> Null
				If isopt = True
					argimpl.SetCallConvention(mxCallConvention.OPTION)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
				Else
					argimpl.SetCallConvention(mxCallConvention.COMMAND)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt = True
					ThrowCommonError(mxOptErrors.UNKNOWN, arg.GetName())
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg.GetName())
				End If
			End If
		Next
		OnExit()
	End Method

	Rem
		bbdoc: Update the sources list.
		returns: Nothing.
		about: Updates when: the sources file is greater than 23 hours old; the sources file does not exist.
	End Rem
	Method UpdateSources()
		If m_autoupdatesources And Not m_sourcesupdated
			Local ctime:dTime, ftime:dTime
			ctime = New dTime.CreateFromCurrent()
			ftime = New dTime.CreateFromFile(m_apppath + m_sourcesfile)
			'DebugLog("Auto-update file time: c" + ctime.Get() + " | " + ftime.Get() + ", hours: " + (ctime.Get() - ftime.Get()) / 3600)
			If Not ftime Or (ctime.Get() - ftime.Get()) / 3600 > 23
				logger.LogMessage(_s("arg.update.autoupdate"))
				m_updateimpl.SetCallConvention(mxCallConvention.COMMAND)
				m_updateimpl.SetArgs(New dIdentifier.Create())
				m_updateimpl.CheckArgs()
				m_updateimpl.Execute()
			End If
		End If
	End Method

	Rem
		bbdoc: Set the application's arguments.
		returns: Nothing.
		about: NOTE: This will resolve to '--help' if the given args are Null.
	End Rem
	Method SetArgs(args:String[])
		If args = Null
			args = ["--help"]
		End If
		m_args = dArgParser.ParseArray(args, False, 1)
	End Method

	Rem
		bbdoc: Set the module path.
		returns: Nothing.
		about: This will throw an error if the module path could not be found.
	End Rem
	Method SetModPath(modpath:String, logchange:Int = True)
		m_modpath = FixPathEnding(modpath, True)
		If FileType(m_modpath) = FILETYPE_DIR
			If logchange = True Then logger.LogMessage(_s("message.setmodpath", [m_modpath]))
		Else
			ThrowError(_s("error.notfound.modpath", [m_modpath]))
		End If
	End Method

	Rem
		bbdoc: Set the path to the BlitzMax installation.
		returns: Nothing.
		about: This will throw an error if the path could not be found.
	End Rem
	Method SetMaxPath(maxpath:String)
		m_maxpath = FixPathEnding(maxpath, True)
		m_binpath = m_maxpath + "/bin"
		Local bmk:String
		?Win32
			bmk = "/bmk.exe"
		?Not Win32
			bmk = "/bmk"
		?
		If FileType(m_maxpath) = FILETYPE_NONE
			ThrowError(_s("error.notfound.maxpath", [m_maxpath]))
		Else If FileType(m_binpath + bmk) = FILETYPE_NONE
			ThrowError(_s("error.notfound.bmk", [m_binpath + bmk]))
		End If
	End Method

	Rem
		bbdoc: Set the sources url.
		returns: Nothing.
	End Rem
	Method SetSourcesUrl(url:String)
		m_sourcesurl = url
	End Method

	Rem
		bbdoc: Set the sources file.
		returns: Nothing.
		about: NOTE: This will throw an error if the path is Null (and, later, a warning will be logged if the path does not exist).
	End Rem
	Method SetSourcesFile(file:String)
		If file = Null
			ThrowError(_s("error.sources.setfile"))
		End If
		m_sourcesfile = file
	End Method

End Type

Rem
	bbdoc: Throw an error (terminating the application).
	returns: Nothing.
End Rem
Function ThrowError(error:String)
	logger.LogError(error)
	mainapp.OnExit()
	End
End Function

Rem
	bbdoc: Get the localized text for the given identifier.
	returns: The translated text.
End Rem
Function _s:String(iden:String, extra:String[] = Null)
	Global replacer:TTextReplacer = New TTextReplacer
	Local ltext:dLocalizedText
	If mainapp.m_locale <> Null Then ltext = mainapp.m_locale.TextFromStructureL(iden)
	If ltext = Null Then ltext = mainapp.m_defaultlocale.TextFromStructureL(iden)
	If ltext <> Null
		replacer.SetString(ltext.GetValue())
		replacer.AutoReplacements("{", "}")
		If extra <> Null
			Local i:Int
			For Local rep:TTextReplacement = EachIn replacer.GetList()
				If i > extra.Length Then Exit
				rep.SetReplacement(extra[i])
				i:+ 1
			Next
		End If
		Return replacer.DoReplacements()
	Else
		DebugLog("Failed to find localized text from structure: ~q" + iden + "~q")
	End If
End Function

SuperStrict

Framework brl.blitz
Import brl.standardio
Import brl.maxutil
Import brl.ramstream

' We have to import this for BMax 1.39 on Linux because of a dependency issue in brl.system
' http://blitzbasic.com/Community/posts.php?topic=90547
?Linux
Import brl.glgraphics
?

Import bah.volumes
Import gman.zipengine
Import htbaapub.rest

Import duct.variables
Import duct.objectmap
Import duct.json
Import duct.locale
Import duct.argparser
Import duct.time

Incbin "locales/en.loc"

Include "src/logger.bmx"
Include "src/errors.bmx"
Include "src/config.bmx"
Include "src/dependencies.bmx"
Include "src/module.bmx"
Include "src/sources.bmx"
Include "src/utils.bmx"
Include "src/arghandler.bmx"
Include "src/impl/help.bmx"
Include "src/impl/version.bmx"
Include "src/impl/modpath.bmx"
Include "src/impl/install.bmx"
Include "src/impl/update.bmx"
Include "src/impl/list.bmx"

Global logger:mxLogger = New mxLogger
Global mainapp:mxApp
New mxApp.Create(AppArgs[1..]) ' Skip the first element because it is the program's location
mainapp.Run()

Rem
	bbdoc: Maximus app.
	about: This handles the basic flow of the program (initiation, parsing, command calling, exiting..)
End Rem
Type mxApp

	Const c_version:String = "0.01"
	Const c_configfile:String = "maximus.config"

	Field m_apppath:String
	Field m_maxpath:String, m_binpath:String, m_modpath:String

	Field m_confighandler:mxConfigHandler
	Field m_defaultlocale:dLocale, m_locale:dLocale
	Field m_sourcesfile:String = "sources", m_sourcesurl:String = "http://maximus.htbaa.com/module/sources/json"
	Field m_useragent:String
	Field m_autoupdatesources:Int = True

	Field m_args:dIdentifier
	Field m_arghandler:mxArgumentHandler
	Field m_sourceshandler:mxSourcesHandler

	Field m_updateimpl:mxUpdateImpl
	Field m_sourcesupdated:Int

	Rem
		bbdoc: Create a new mxApp.
		returns: Itself.
	End Rem
	Method Create:mxApp(args:String[])
		mainapp = Self
		SetArgs(args)
		OnInit()
		Return Self
	End Method

	Rem
		bbdoc: Called when the application is created.
		returns: Nothing.
	End Rem
	Method OnInit()
		m_apppath = GetUserAppDir() + "/"
		?Linux
		m_apppath:+ ".maximus/"
		?Not Linux
		m_apppath:+ "maximus/"
		?
		If FileType(m_apppath) = FILETYPE_NONE
			If Not CreateDir(m_apppath, False)
				ThrowError(_s("error.createperms", [m_apppath]))
			End If
		End If
		ChangeDir(m_apppath)
		m_confighandler = New mxConfigHandler.Create(m_apppath + c_configfile)
		m_confighandler.LoadDefaultLocale()
		m_confighandler.Load()
		If Not m_maxpath
			Try
				SetMaxPath(BlitzMaxPath())
			Catch e:Object
				ThrowError(_s("error.notfound.maxenv"))
			End Try
		End If
		If Not m_modpath Then SetModPath(m_maxpath + "/mod", False)
		m_arghandler = New mxArgumentHandler
		m_arghandler.AddArgImpl(New mxHelpImpl)
		m_arghandler.AddArgImpl(New mxVersionImpl)
		m_arghandler.AddArgImpl(New mxModPathImpl)
		m_arghandler.AddArgImpl(New mxInstallImpl)
		m_updateimpl = New mxUpdateImpl
		m_arghandler.AddArgImpl(m_updateimpl)
		m_arghandler.AddArgImpl(New mxListImpl)
		UpdateSources()
		m_sourceshandler = New mxSourcesHandler.FromFile(m_apppath + m_sourcesfile)
		If m_sourceshandler = Null
			' Don't throw an error here, the user may be updating the sources (an error will occur otherwise)
			logger.LogWarning(_s("error.load.sources.file", [m_apppath + m_sourcesfile]))
		End If
		Local os:String
		?Win32
			os = "Windows"
		?Linux
			os = "Linux"
		?MacOS
			os = "MacOS"
		?
		m_useragent = "Maximus/" + c_version + " (" + os + "; " + m_locale.GetName() + ")"
	End Method

	Rem
		bbdoc: Called when the application is ended (this includes #ThrowError calls).
		returns: Nothing.
	End Rem
	Method OnExit()
	End Method

	Rem
		bbdoc: Run the application.
		returns: Nothing.
	End Rem
	Method Run()
		Local argimpl:mxArgumentImplementation, isopt:Int
		For Local arg:dIdentifier = EachIn m_args.GetValues()
			argimpl = m_arghandler.GetArgImplFromAlias(arg.GetName())
			isopt = (arg.GetName()[0] = 45)
			If argimpl <> Null
				If isopt = True
					argimpl.SetCallConvention(mxCallConvention.OPTION)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
				Else
					argimpl.SetCallConvention(mxCallConvention.COMMAND)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt = True
					ThrowCommonError(mxOptErrors.UNKNOWN, arg.GetName())
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg.GetName())
				End If
			End If
		Next
		OnExit()
	End Method

	Rem
		bbdoc: Update the sources list.
		returns: Nothing.
		about: Updates when: the sources file is greater than 23 hours old; the sources file does not exist.
	End Rem
	Method UpdateSources()
		If m_autoupdatesources And Not m_sourcesupdated
			Local ctime:dTime, ftime:dTime
			ctime = New dTime.CreateFromCurrent()
			ftime = New dTime.CreateFromFile(m_apppath + m_sourcesfile)
			'DebugLog("Auto-update file time: c" + ctime.Get() + " | " + ftime.Get() + ", hours: " + (ctime.Get() - ftime.Get()) / 3600)
			If Not ftime Or (ctime.Get() - ftime.Get()) / 3600 > 23
				logger.LogMessage(_s("arg.update.autoupdate"))
				m_updateimpl.SetCallConvention(mxCallConvention.COMMAND)
				m_updateimpl.SetArgs(New dIdentifier.Create())
				m_updateimpl.CheckArgs()
				m_updateimpl.Execute()
			End If
		End If
	End Method

	Rem
		bbdoc: Set the application's arguments.
		returns: Nothing.
		about: NOTE: This will resolve to '--help' if the given args are Null.
	End Rem
	Method SetArgs(args:String[])
		If args = Null
			args = ["--help"]
		End If
		m_args = dArgParser.ParseArray(args, False, 1)
	End Method

	Rem
		bbdoc: Set the module path.
		returns: Nothing.
		about: This will throw an error if the module path could not be found.
	End Rem
	Method SetModPath(modpath:String, logchange:Int = True)
		m_modpath = FixPathEnding(modpath, True)
		If FileType(m_modpath) = FILETYPE_DIR
			If logchange = True Then logger.LogMessage(_s("message.setmodpath", [m_modpath]))
		Else
			ThrowError(_s("error.notfound.modpath", [m_modpath]))
		End If
	End Method

	Rem
		bbdoc: Set the path to the BlitzMax installation.
		returns: Nothing.
		about: This will throw an error if the path could not be found.
	End Rem
	Method SetMaxPath(maxpath:String)
		m_maxpath = FixPathEnding(maxpath, True)
		m_binpath = m_maxpath + "/bin"
		If FileType(m_maxpath) = FILETYPE_NONE
			ThrowError(_s("error.notfound.maxpath", [m_maxpath]))
		Else If FileType(m_binpath + "/bmk") = FILETYPE_NONE
			ThrowError(_s("error.notfound.bmk", [m_binpath + "/bmk"]))
		End If
	End Method

	Rem
		bbdoc: Set the sources url.
		returns: Nothing.
	End Rem
	Method SetSourcesUrl(url:String)
		m_sourcesurl = url
	End Method

	Rem
		bbdoc: Set the sources file.
		returns: Nothing.
		about: NOTE: This will throw an error if the path is Null (and, later, a warning will be logged if the path does not exist).
	End Rem
	Method SetSourcesFile(file:String)
		If file = Null
			ThrowError(_s("error.sources.setfile"))
		End If
		m_sourcesfile = file
	End Method

End Type

Rem
	bbdoc: Throw an error (terminating the application).
	returns: Nothing.
End Rem
Function ThrowError(error:String)
	logger.LogError(error)
	mainapp.OnExit()
	End
End Function

Rem
	bbdoc: Get the localized text for the given identifier.
	returns: The translated text.
End Rem
Function _s:String(iden:String, extra:String[] = Null)
	Global replacer:TTextReplacer = New TTextReplacer
	Local ltext:dLocalizedText
	If mainapp.m_locale <> Null Then ltext = mainapp.m_locale.TextFromStructureL(iden)
	If ltext = Null Then ltext = mainapp.m_defaultlocale.TextFromStructureL(iden)
	If ltext <> Null
		replacer.SetString(ltext.GetValue())
		replacer.AutoReplacements("{", "}")
		If extra <> Null
			Local i:Int
			For Local rep:TTextReplacement = EachIn replacer.GetList()
				If i > extra.Length Then Exit
				rep.SetReplacement(extra[i])
				i:+ 1
			Next
		End If
		Return replacer.DoReplacements()
	Else
		DebugLog("Failed to find localized text from structure: ~q" + iden + "~q")
	End If
End Function


Version 0.04

Version 0.04

Rem
	Copyright (c) 2012 Christiaan Kras

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
End Rem

SuperStrict

Rem
	bbdoc: htbaapub.zmq
	about: This module is still an early work in progress. If you run into any
	bugs please report them at https://github.com/Htbaa/zmq.mod/issues.
EndRem
Module htbaapub.zmq
ModuleInfo "Name: htbaapub.zmq"
ModuleInfo "Description: A wrapper for ZeroMQ"
ModuleInfo "Version: 0.04"
ModuleInfo "License: MIT"
ModuleInfo "Author: Christiaan Kras"
ModuleInfo "ZeroMQ: http://www.zeromq.org"
ModuleInfo "ZeroMQ License: LGPL (see contents of ./zeromq)"
ModuleInfo "Git repository: https://github.com/Htbaa/zmq.mod/"
ModuleInfo "Issues: https://github.com/Htbaa/zmq.mod/issues"
ModuleInfo "ZeroMQ Windows binaries: http://www.zeromq.org/distro:microsoft-windows"
ModuleInfo "History: 0.04"
ModuleInfo "History: Updated to ZMQ 2.2.0"
ModuleInfo "History: Updated example1 to reflect API changes"
ModuleInfo "History: Improved test coverage"
ModuleInfo "History: 0.03"
ModuleInfo "History: Fixed message data corruption for first sent message (gh-3)"
ModuleInfo "History: Added TZMQ_Message.CreateFromString to create messages from a String. The TZMQ_Message.Create signature has also changed to accept a Byte Ptr and a length"
ModuleInfo "History: Fixed potential memory leak in TZMQ_Socket.Send when an exception would be raised"
ModuleInfo "History: Added TZMQ_Socket_Exception and TZMQ_Message_Exception"
ModuleInfo "History: Added a couple of unit tests (bah.maxunit required)"
ModuleInfo "History: 0.02"
ModuleInfo "History: Added more examples; Hello World server and client"
ModuleInfo "History: Added more examples; Weather Update server and client"
ModuleInfo "History: TZMQ_Socket.SetSockOpt() now properly sets options"
ModuleInfo "History: TZMQ_Message.Close() now properly frees up memory"
ModuleInfo "History: 0.01"
ModuleInfo "History: First release of htbaapub.zmq using ZMQ 2.1.11. No support yet for input/output multiplexing with zmq_poll()"

?Linux
	Import "-lzmq"
?Win32
	Import "zeromq/lib/libzmq-v100-mt.lib"
?

Import "glue.c"
Include "exception.bmx"
Include "constants.bmx"
Include "context.bmx"
Include "socket.bmx"
Include "message.bmx"

'Glue/Helper functions
Extern "C"

	Rem
		bbdoc: Create an empty zmq_message_t struct
		returns: Returns a pointer to a new zmq_message_t struct
	End Rem
	Function bmx_zmq_message_t:Byte Ptr()

	Rem
		bbdoc: Create a zmq_message_t struct with preset data
		returns: Returns a pointer to a new zmq_message_t struct with given data
	End Rem
	Function bmx_zmq_new_message:Byte Ptr(str$z)

	Rem
		bbdoc: Alias for free(), for cleaning up data structures
	End Rem
	Function bmx_zmq_free(msg:Byte Ptr)
End Extern

'ZeroMQ functions
Extern
	Function zmq_version(major:Int Var, minor:Int Var, patch:Int Var)

	Function zmq_init:Byte Ptr(io_threads:Int)
	Function zmq_term:Int(context:Byte Ptr)

	Function zmq_errno:Int()
	Function zmq_strerror$z(errnum:Int)

	Function zmq_socket:Byte Ptr(context:Byte Ptr, socket_type:Int)
	Function zmq_close:Int(s:Byte Ptr)
	Function zmq_setsockopt:Int(s:Byte Ptr, option:Int, optval$z, length:Int)
	Function zmq_getsockopt:Int(s:Byte Ptr, option:Int, optval:Byte Ptr, length:Int Var)
	'ZMQ_EXPORT Int zmq_getsockopt (void *s, Int option, void *optval, size_t *optvallen);
	Function zmq_bind(s:Byte Ptr, addr$z)
	Function zmq_connect:Int(s:Byte Ptr, addr$z)
	Function zmq_send:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)
	Function zmq_recv:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)

	Function zmq_msg_init:Int(msg:Byte Ptr)
	Function zmq_msg_init_size:Int(msg:Byte Ptr, size:Long)
	Function zmq_msg_init_data:Int(msg:Byte Ptr, data:Byte Ptr, size:Long, ffn:Byte Ptr, hint:Byte Ptr)
	Function zmq_msg_close:Int(msg:Byte Ptr)
	Function zmq_msg_move:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_copy:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_data:Byte Ptr(msg:Byte Ptr)
	Function zmq_msg_size:Int(msg:Byte Ptr)

	Function zmq_poll:Int(items:Byte Ptr, nitems:Int, timeout:Long)

	Function zmq_device:Int(device:Int, insocket:Byte Ptr, outsocket:Byte Ptr)
End Extern

Rem
	bbdoc: General ZMQ functions
End Rem
Type TZMQ

	Rem
		bbdoc: Get ZMQ Version
		returns: String
	End Rem
	Function Version:String()
		Local major:Int, minor:Int, patch:Int
		zmq_version(major, minor, patch)
		Return major + "." + minor + "." + patch
	End Function

	Rem
		bbdoc: Get current ZMQ error descriptions
		returns: String
	End Rem
	Function Error:String()
		Return zmq_strerror( TZMQ.ErrorNumber() )
	End Function

	Rem
		bbdoc: Get current ZMQ error number
		returns: Int
	End Rem
	Function ErrorNumber:Int()
		Return zmq_errno()
	End Function
End Type

Rem
	Copyright (c) 2012 Christiaan Kras

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
End Rem

SuperStrict

Rem
	bbdoc: htbaapub.zmq
	about: This module is still an early work in progress. If you run into any
	bugs please report them at https://github.com/Htbaa/zmq.mod/issues.
EndRem
Module htbaapub.zmq
ModuleInfo "Name: htbaapub.zmq"
ModuleInfo "Description: A wrapper for ZeroMQ"
ModuleInfo "Version: 0.03"
ModuleInfo "License: MIT"
ModuleInfo "Author: Christiaan Kras"
ModuleInfo "ZeroMQ: http://www.zeromq.org"
ModuleInfo "ZeroMQ License: LGPL (see contents of ./zeromq)"
ModuleInfo "Git repository: https://github.com/Htbaa/zmq.mod/"
ModuleInfo "Issues: https://github.com/Htbaa/zmq.mod/issues"
ModuleInfo "ZeroMQ Windows binaries: http://www.zeromq.org/distro:microsoft-windows"
ModuleInfo "History: Updated to ZMQ 2.2.0"
ModuleInfo "History: Updated example1 to reflect API changes"
ModuleInfo "History: Improved test coverage"
ModuleInfo "History: 0.03"
ModuleInfo "History: Fixed message data corruption for first sent message (gh-3)"
ModuleInfo "History: Added TZMQ_Message.CreateFromString to create messages from a String. The TZMQ_Message.Create signature has also changed to accept a Byte Ptr and a length"
ModuleInfo "History: Fixed potential memory leak in TZMQ_Socket.Send when an exception would be raised"
ModuleInfo "History: Added TZMQ_Socket_Exception and TZMQ_Message_Exception"
ModuleInfo "History: Added a couple of unit tests (bah.maxunit required)"
ModuleInfo "History: 0.02"
ModuleInfo "History: Added more examples; Hello World server and client"
ModuleInfo "History: Added more examples; Weather Update server and client"
ModuleInfo "History: TZMQ_Socket.SetSockOpt() now properly sets options"
ModuleInfo "History: TZMQ_Message.Close() now properly frees up memory"
ModuleInfo "History: 0.01"
ModuleInfo "History: First release of htbaapub.zmq using ZMQ 2.1.11. No support yet for input/output multiplexing with zmq_poll()"

?Linux
	Import "-lzmq"
?Win32
	Import "zeromq/lib/libzmq-v100-mt.lib"
?

Import "glue.c"
Include "exception.bmx"
Include "constants.bmx"
Include "context.bmx"
Include "socket.bmx"
Include "message.bmx"

'Glue/Helper functions
Extern "C"

	Rem
		bbdoc: Create an empty zmq_message_t struct
		returns: Returns a pointer to a new zmq_message_t struct
	End Rem
	Function bmx_zmq_message_t:Byte Ptr()

	Rem
		bbdoc: Create a zmq_message_t struct with preset data
		returns: Returns a pointer to a new zmq_message_t struct with given data
	End Rem
	Function bmx_zmq_new_message:Byte Ptr(str$z)

	Rem
		bbdoc: Alias for free(), for cleaning up data structures
	End Rem
	Function bmx_zmq_free(msg:Byte Ptr)
End Extern

'ZeroMQ functions
Extern
	Function zmq_version(major:Int Var, minor:Int Var, patch:Int Var)

	Function zmq_init:Byte Ptr(io_threads:Int)
	Function zmq_term:Int(context:Byte Ptr)

	Function zmq_errno:Int()
	Function zmq_strerror$z(errnum:Int)

	Function zmq_socket:Byte Ptr(context:Byte Ptr, socket_type:Int)
	Function zmq_close:Int(s:Byte Ptr)
	Function zmq_setsockopt:Int(s:Byte Ptr, option:Int, optval$z, length:Int)
	Function zmq_getsockopt:Int(s:Byte Ptr, option:Int, optval:Byte Ptr, length:Int Var)
	'ZMQ_EXPORT Int zmq_getsockopt (void *s, Int option, void *optval, size_t *optvallen);
	Function zmq_bind(s:Byte Ptr, addr$z)
	Function zmq_connect:Int(s:Byte Ptr, addr$z)
	Function zmq_send:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)
	Function zmq_recv:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)

	Function zmq_msg_init:Int(msg:Byte Ptr)
	Function zmq_msg_init_size:Int(msg:Byte Ptr, size:Long)
	Function zmq_msg_init_data:Int(msg:Byte Ptr, data:Byte Ptr, size:Long, ffn:Byte Ptr, hint:Byte Ptr)
	Function zmq_msg_close:Int(msg:Byte Ptr)
	Function zmq_msg_move:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_copy:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_data:Byte Ptr(msg:Byte Ptr)
	Function zmq_msg_size:Int(msg:Byte Ptr)

	Function zmq_poll:Int(items:Byte Ptr, nitems:Int, timeout:Long)

	Function zmq_device:Int(device:Int, insocket:Byte Ptr, outsocket:Byte Ptr)
End Extern

Rem
	bbdoc: General ZMQ functions
End Rem
Type TZMQ

	Rem
		bbdoc: Get ZMQ Version
		returns: String
	End Rem
	Function Version:String()
		Local major:Int, minor:Int, patch:Int
		zmq_version(major, minor, patch)
		Return major + "." + minor + "." + patch
	End Function

	Rem
		bbdoc: Get current ZMQ error descriptions
		returns: String
	End Rem
	Function Error:String()
		Return zmq_strerror( TZMQ.ErrorNumber() )
	End Function

	Rem
		bbdoc: Get current ZMQ error number
		returns: Int
	End Rem
	Function ErrorNumber:Int()
		Return zmq_errno()
	End Function
End Type


b3dloader now can load from TBB3DChunk

b3dloader now can load from TBB3DChunk

Strict

Rem
	bbdoc: Blitz3D model loader for MaxB3D
End Rem
Module MaxB3D.B3DLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.BoneAnimator
Import Prime.libB3D

Type TMeshLoaderB3D Extends TMeshLoader
	Field _mesh:TMesh

	Method Run(mesh:TMesh,stream:TStream,url:Object)
		Local model:TBB3DChunk = TBB3DChunk(url)
		If model=Null model = TBB3DChunk.Load(stream)
		If model=Null Return False

		Local olddir$=CurrentDir()
		If String(url) ChangeDir(ExtractDir(String(url)))

		Local texture:TTexture[model.texs.length]
		Local brush:TBrush[model.brus.length]

		For Local i=0 To texture.length-1
			Local chunk:TTEXSChunk=model.texs[i]
			texture[i]=_currentworld.AddTexture(chunk.file,chunk.flags)
			If texture[i]
				texture[i].SetBlend chunk.blend
				texture[i].SetPosition chunk.x_pos,chunk.y_pos
				texture[i].SetScale chunk.x_scale,chunk.y_scale
				texture[i].SetRotation chunk.rotation*(180.0/Pi)
				If chunk.flags&65536 texture[i].SetCoords 1
			EndIf
		Next

		For Local i=0 To brush.length-1
			Local chunk:TBRUSChunk=model.brus[i]
			brush[i]=_currentworld.AddBrush()
			brush[i].SetName chunk.name
			brush[i].SetColor chunk.red*255,chunk.green*255,chunk.blue*255;brush[i].SetAlpha chunk.alpha
			brush[i].SetShine chunk.shininess
			brush[i].SetBlend chunk.blend;brush[i].SetFX chunk.fx
			For Local j=0 To chunk.n_texs-1
				If chunk.texture_id[j]>-1 brush[i].SetTexture texture[chunk.texture_id[j]],j
			Next
		Next
		ChangeDir olddir

		' model.dump StandardIOStream

		If model.node
			ParseNode model.node,mesh,brush,mesh
			Return True
		EndIf
		Return False
	End Method

	Method ParseNode(node:TNODEChunk,parent:TEntity,brush:TBrush[],entity:TEntity=Null)
		Local meshchunk:TMESHChunk=TMESHChunk(node.kind),bonechunk:TBONEChunk=TBONEChunk(node.kind)

		Select node.kind
		Case Null
			entity=_currentworld.AddPivot(parent)
		Case meshchunk
			If entity=Null entity=_currentworld.AddMesh("//empty",parent)
			Local mesh:TMesh=TMesh(entity)
			mesh._animator=New TBoneAnimator
			_mesh=mesh

			If meshchunk.brush_id>-1 entity.SetBrush brush[meshchunk.brush_id]

			Local vrts:TVRTSChunk=meshchunk.vrts
			Local vertsurface:TSurface=New TSurface
			vertsurface.Resize(vrts.xyz.length/3,0)

			For Local i=0 To vertsurface.CountVertices()-1
				vertsurface.SetCoords i,vrts.xyz[i*3+0],vrts.xyz[i*3+1],vrts.xyz[i*3+2]
				If vrts.nxyz vertsurface.SetNormal i,vrts.nxyz[i*3+0],vrts.nxyz[i*3+1],vrts.nxyz[i*3+2]
				If vrts.rgba vertsurface.SetColor i,vrts.rgba[i*4+0]*255,vrts.rgba[i*4+1]*255,vrts.rgba[i*4+2]*255,vrts.rgba[i*4+3]
				If vrts.SetSize()>1
					For Local j=0 To vrts.SetCount()-1
						vertsurface.SetTexCoords i,1-vrts.tex_coords[i][j,0],vrts.tex_coords[i][j,1],j
					Next
				EndIf
			Next

			For Local i=0 To meshchunk.tris.length-1
				Local tri:TTRISChunk=meshchunk.tris[i]
				Local surface:TSurface=vertsurface.Copy()
				surface.Resize(-1,tri.vertex_id.length/3)
				If tri.brush_id>-1 surface.SetBrush brush[tri.brush_id]
				For Local t=0 To surface.CountTriangles()-1
					surface.SetTriangle t,tri.vertex_id[t*3+0],tri.vertex_id[t*3+1],tri.vertex_id[t*3+2]
				Next
				If vrts.nxyz=Null surface.UpdateNormals
				mesh.AppendSurface surface
			Next
		Case bonechunk
			entity=_currentworld.AddBone(parent)
			Local bone:TBone=TBone(entity)

			For Local surface:TSurface=EachIn _mesh._surfaces
				bone.AddSurface surface
				For Local i=0 To bonechunk.vertex_id.length-1
					bone.AddVertex surface,bonechunk.vertex_id[i],bonechunk.weight[i]
				Next
			Next

			Local ident_matrix:TMatrix=TMatrix.Identity()
			For Local chunk:TKEYSChunk=EachIn node.keys
				Local keys:TAnimKey[chunk.frame.length]
				Local pos#[]=chunk.position,rot#[]=chunk.rotation,scl#[]=chunk.scale
				For Local i=0 To chunk.frame.length-1
					Local bonekey:TBoneKey=New TBoneKey

					If pos.length>0 bonekey._px=pos[i*3+0];bonekey._py=pos[i*3+1];bonekey._pz=pos[i*3+2]
					If rot.length>0 bonekey._rw=rot[i*4+0];bonekey._rx=rot[i*4+1];bonekey._ry=rot[i*4+2];bonekey._rz=rot[i*4+3]
					If scl.length>0 bonekey._sx=scl[i*3+0];bonekey._sy=scl[i*3+1];bonekey._sz=scl[i*3+2]

					Local key:TAnimKey=New TAnimKey
					key._frame=chunk.frame[i]
					key._object=bonekey
					keys[i]=key
				Next
				TBoneAnimator(_mesh._animator).AddBone bone,keys
			Next
		Default
			entity=_currentworld.AddPivot(parent)
		End Select

		entity.SetName node.name
		entity.SetPosition node.position[0],node.position[1],node.position[2]
		Local pitch#,yaw#,roll#
		TQuaternion.Euler node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3],pitch,yaw,roll
		entity.SetRotation pitch,yaw,roll
		entity.SetScale node.scale[0],node.scale[1],node.scale[2]

		For Local child:TNODEChunk=EachIn node.node
			ParseNode child,entity,brush
		Next
		If TMesh(entity) If TBoneAnimator(TMesh(entity)._animator)._root=Null TMesh(entity)._animator=Null
		If TBone(entity) TBone(entity)._start_matrix=entity.GetMatrix()
	End Method

	Method Info$()
		Return "Blitz3D|b3d"
	End Method
	Method ModuleName$()
		Return "b3dloader"
	End Method
End Type
New TMeshLoaderB3D

Strict

Rem
	bbdoc: Blitz3D model loader for MaxB3D
End Rem
Module MaxB3D.B3DLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.BoneAnimator
Import Prime.libB3D

Type TMeshLoaderB3D Extends TMeshLoader
	Field _mesh:TMesh

	Method Run(mesh:TMesh,stream:TStream,url:Object)
		Local model:TBB3DChunk=TBB3DChunk.Load(url)
		If model=Null Return False

		Local olddir$=CurrentDir()
		If String(url) ChangeDir(ExtractDir(String(url)))

		Local texture:TTexture[model.texs.length]
		Local brush:TBrush[model.brus.length]

		For Local i=0 To texture.length-1
			Local chunk:TTEXSChunk=model.texs[i]
			texture[i]=_currentworld.AddTexture(chunk.file,chunk.flags)
			If texture[i]
				texture[i].SetBlend chunk.blend
				texture[i].SetPosition chunk.x_pos,chunk.y_pos
				texture[i].SetScale chunk.x_scale,chunk.y_scale
				texture[i].SetRotation chunk.rotation*(180.0/Pi)
				If chunk.flags&65536 texture[i].SetCoords 1
			EndIf
		Next

		For Local i=0 To brush.length-1
			Local chunk:TBRUSChunk=model.brus[i]
			brush[i]=_currentworld.AddBrush()
			brush[i].SetName chunk.name
			brush[i].SetColor chunk.red*255,chunk.green*255,chunk.blue*255;brush[i].SetAlpha chunk.alpha
			brush[i].SetShine chunk.shininess
			brush[i].SetBlend chunk.blend;brush[i].SetFX chunk.fx
			For Local j=0 To chunk.n_texs-1
				If chunk.texture_id[j]>-1 brush[i].SetTexture texture[chunk.texture_id[j]],j
			Next
		Next
		ChangeDir olddir

		' model.dump StandardIOStream

		If model.node
			ParseNode model.node,mesh,brush,mesh
			Return True
		EndIf
		Return False
	End Method

	Method ParseNode(node:TNODEChunk,parent:TEntity,brush:TBrush[],entity:TEntity=Null)
		Local meshchunk:TMESHChunk=TMESHChunk(node.kind),bonechunk:TBONEChunk=TBONEChunk(node.kind)

		Select node.kind
		Case Null
			entity=_currentworld.AddPivot(parent)
		Case meshchunk
			If entity=Null entity=_currentworld.AddMesh("//empty",parent)
			Local mesh:TMesh=TMesh(entity)
			mesh._animator=New TBoneAnimator
			_mesh=mesh

			If meshchunk.brush_id>-1 entity.SetBrush brush[meshchunk.brush_id]

			Local vrts:TVRTSChunk=meshchunk.vrts
			Local vertsurface:TSurface=New TSurface
			vertsurface.Resize(vrts.xyz.length/3,0)

			For Local i=0 To vertsurface.CountVertices()-1
				vertsurface.SetCoords i,vrts.xyz[i*3+0],vrts.xyz[i*3+1],vrts.xyz[i*3+2]
				If vrts.nxyz vertsurface.SetNormal i,vrts.nxyz[i*3+0],vrts.nxyz[i*3+1],vrts.nxyz[i*3+2]
				If vrts.rgba vertsurface.SetColor i,vrts.rgba[i*4+0]*255,vrts.rgba[i*4+1]*255,vrts.rgba[i*4+2]*255,vrts.rgba[i*4+3]
				If vrts.SetSize()>1
					For Local j=0 To vrts.SetCount()-1
						vertsurface.SetTexCoords i,1-vrts.tex_coords[i][j,0],vrts.tex_coords[i][j,1],j
					Next
				EndIf
			Next

			For Local i=0 To meshchunk.tris.length-1
				Local tri:TTRISChunk=meshchunk.tris[i]
				Local surface:TSurface=vertsurface.Copy()
				surface.Resize(-1,tri.vertex_id.length/3)
				If tri.brush_id>-1 surface.SetBrush brush[tri.brush_id]
				For Local t=0 To surface.CountTriangles()-1
					surface.SetTriangle t,tri.vertex_id[t*3+0],tri.vertex_id[t*3+1],tri.vertex_id[t*3+2]
				Next
				If vrts.nxyz=Null surface.UpdateNormals
				mesh.AppendSurface surface
			Next
		Case bonechunk
			entity=_currentworld.AddBone(parent)
			Local bone:TBone=TBone(entity)

			For Local surface:TSurface=EachIn _mesh._surfaces
				bone.AddSurface surface
				For Local i=0 To bonechunk.vertex_id.length-1
					bone.AddVertex surface,bonechunk.vertex_id[i],bonechunk.weight[i]
				Next
			Next

			Local ident_matrix:TMatrix=TMatrix.Identity()
			For Local chunk:TKEYSChunk=EachIn node.keys
				Local keys:TAnimKey[chunk.frame.length]
				Local pos#[]=chunk.position,rot#[]=chunk.rotation,scl#[]=chunk.scale
				For Local i=0 To chunk.frame.length-1
					Local bonekey:TBoneKey=New TBoneKey

					If pos.length>0 bonekey._px=pos[i*3+0];bonekey._py=pos[i*3+1];bonekey._pz=pos[i*3+2]
					If rot.length>0 bonekey._rw=rot[i*4+0];bonekey._rx=rot[i*4+1];bonekey._ry=rot[i*4+2];bonekey._rz=rot[i*4+3]
					If scl.length>0 bonekey._sx=scl[i*3+0];bonekey._sy=scl[i*3+1];bonekey._sz=scl[i*3+2]

					Local key:TAnimKey=New TAnimKey
					key._frame=chunk.frame[i]
					key._object=bonekey
					keys[i]=key
				Next
				TBoneAnimator(_mesh._animator).AddBone bone,keys
			Next
		Default
			entity=_currentworld.AddPivot(parent)
		End Select

		entity.SetName node.name
		entity.SetPosition node.position[0],node.position[1],node.position[2]
		Local pitch#,yaw#,roll#
		TQuaternion.Euler node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3],pitch,yaw,roll
		entity.SetRotation pitch,yaw,roll
		entity.SetScale node.scale[0],node.scale[1],node.scale[2]

		For Local child:TNODEChunk=EachIn node.node
			ParseNode child,entity,brush
		Next
		If TMesh(entity) If TBoneAnimator(TMesh(entity)._animator)._root=Null TMesh(entity)._animator=Null
		If TBone(entity) TBone(entity)._start_matrix=entity.GetMatrix()
	End Method

	Method Info$()
		Return "Blitz3D|b3d"
	End Method
	Method ModuleName$()
		Return "b3dloader"
	End Method
End Type
New TMeshLoaderB3D


Added missing BlitzMax Diddy Functions for MonkeyMax

Type diddy
	Function systemMillisecs:Float()
		Return Millisecs()
	EndFunction

	Function flushKeys:Int()
		BlitzMaxFlushKeys()
	EndFunction

	Function getUpdateRate:Int()
		Return app.updateRate;
	EndFunction

	Function showMouse:Int()
		BlitzMaxShowMouse()
	EndFunction

	Function hideMouse:Int()
		BlitzMaxHideMouse()
	EndFunction

	Function setGraphics(w:int, h:int, fullScreen:Int = False)
		local d% = 0
		if fullScreen then d = 32
		Graphics w, h, d, 60
	EndFunction

	Function setMouse(x:Int, y:Int)
		MoveMouse(x, y)
	EndFunction

	Function showKeyboard()
	EndFunction

	Function launchBrowser(address:String, windowName:String)
	EndFunction

	Function launchEmail(email:String, subject:String, text:String)
	EndFunction

	Function realMod:Float( value:Float, amount:Float )
		Return value Mod amount;
	EndFunction

	Function startVibrate(millisecs:int)
	EndFunction

	Function stopVibrate()
	EndFunction

	Function getDayOfMonth:Int()
		Local date$ = CurrentDate()
		Return Int(date[..2])
	EndFunction

	Function getDayOfWeek:Int()
		Return 0
	EndFunction

	Function getMonth:Int()
		Local date$ = CurrentDate()
		Return (Instr("JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC", date$[3..6].ToUpper(), 1) / 3) + 1
	EndFunction

	Function getYear:Int()
		Local date$ = CurrentDate()
		Return Int(date[date.length - 4..]);
	EndFunction

	Function getHours:Int()
		Local time$ = CurrentTime()
		Return Int(time[..2])
	EndFunction

	Function getMinutes:Int()
		Local time$ = CurrentTime()
		Return Int(time[3..5])
	EndFunction

	Function getSeconds:Int()
		Local time$ = CurrentTime()
		Return Int(time[6..8])
	EndFunction

	Function getMilliSeconds:Int()
		Return Millisecs()
	EndFunction

	Function startGps()
	EndFunction

	Function getLatitiude:String()
		Return ""
	EndFunction

	Function getLongitude:String()
		Return ""
	EndFunction

	Function showAlertDialog(title:String, message:String)
	EndFunction

	Function getInputString:String()
		Return "";
	EndFunction

	Function getPixel:Int( x:int, y:int )
'		unsigned char pix[4];
'		glReadPixels(x, app->graphics->height-y ,1 ,1 ,GL_RGBA ,GL_UNSIGNED_BYTE ,pix);
'		return (pix[3]<<24) | (pix[0]<<16) | (pix[1]<<8) |  pix[2];
		Return 0
	EndFunction

EndType

Function BlitzMaxFlushKeys()
	FlushKeys()
EndFunction

Function BlitzMaxShowMouse()
	ShowMouse()
EndFunction

Function BlitzMaxHideMouse()
	HideMouse()
EndFunction

Type diddy
	Function systemMillisecs:Float()
		return Millisecs()
	EndFunction

	Function realMod:Float( value:Float, amount:Float )
		return value Mod amount;
	EndFunction

	Function getYear:Int()
		return 2012;
	EndFunction
EndType


Really fixed String slicing ;)

Really fixed String slicing ;)

--HG--
extra : convert_revision : svn%3Ae934d3cd-0d57-f34a-94d0-9e8d31ae1c29/trunk%40391

' ***** Start lang.bmx ******
Global D2R:Float=0.017453292519943295;
Global R2D:Float=57.29577951308232;

Function pushErr()
	_errStack.AddLast( _errInfo );
EndFunction

Function popErr()
	_errInfo=String(_errStack.ValueAtIndex(_errStack.Count()-1))
	_errStack.RemoveLast();
EndFunction

Function error( err:String )
	If err = "" Then
		End
	Else
		RuntimeError err;
	EndIf
EndFunction

Function resize_string_array:String[]( arr:String[], leng:Int )
	Local i:Int = arr.length;
	arr = arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]="";
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_float_array:Float[]( arr:Float[], leng:Int )
	Local i:Int = arr.length;
	arr = arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_int_array:Int[]( arr:Int[], leng:Int )
	Local i:Int = arr.length;
	arr = arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_object_array:Object[]( arr:_Object[],leng:Int )
	Local i:Int=arr.length;
	arr=arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]=Null;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_array_array_Int:Int[][]( arr:Int[][], leng:Int )
	Local i:Int = arr.length;
	arr = arr[..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[0][i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_array_array_Float:Float[][]( arr:Float[][], leng:Int )
	Local i:Int = arr.length;
	arr = arr[..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[0][i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_array_array_String:String[][]( arr:String[][], leng:Int )
	Local i:Int = arr.length;
	arr = arr[..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[0][i]="";
		i:+1
	Wend
	Return arr;
EndFunction

Function slice_string:String(arr:String, from:Int, term:Int = 0)
	Local le:Int = arr.Length

	If from < 0
		from :+ le
		If from <0 Then from = 0
	Else If from > le
		from = le
	EndIf
	If term <= 0
		term :+ le
	Else If term > le
		term = le
	EndIf

	If term <= from Return arr
	Return Mid(arr, from + 1,  term - from)

EndFunction

' ***** End lang.bmx ******

' ***** Start lang.bmx ******
Global D2R:Float=0.017453292519943295;
Global R2D:Float=57.29577951308232;

Function pushErr()
	_errStack.AddLast( _errInfo );
EndFunction

Function popErr()
	_errInfo=String(_errStack.ValueAtIndex(_errStack.Count()-1))
	_errStack.RemoveLast();
EndFunction

Function error( err:String )
	If err = "" Then
		End
	Else
		RuntimeError err;
	EndIf
EndFunction

Function resize_string_array:String[]( arr:String[], leng:Int )
	Local i:Int = arr.length;
	arr = arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]="";
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_float_array:Float[]( arr:Float[], leng:Int )
	Local i:Int = arr.length;
	arr = arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_int_array:Int[]( arr:Int[], leng:Int )
	Local i:Int = arr.length;
	arr = arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_object_array:Object[]( arr:_Object[],leng:Int )
	Local i:Int=arr.length;
	arr=arr[0..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[i]=Null;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_array_array_Int:Int[][]( arr:Int[][], leng:Int )
	Local i:Int = arr.length;
	arr = arr[..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[0][i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_array_array_Float:Float[][]( arr:Float[][], leng:Int )
	Local i:Int = arr.length;
	arr = arr[..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[0][i]=0;
		i:+1
	Wend
	Return arr;
EndFunction

Function resize_array_array_String:String[][]( arr:String[][], leng:Int )
	Local i:Int = arr.length;
	arr = arr[..leng];
	If( leng<=i ) Return arr;
	While( i<leng )
		arr[0][i]="";
		i:+1
	Wend
	Return arr;
EndFunction

Function slice_string:String(arr:String, from:Int, term:Int = 0)
	Local le:Int = arr.Length

	If from < 0
		from :+ le
		If from <0 Then from = 0
	Else If from > le
		from = le
	EndIf
	If term <= 0
		term :+ le
	Else If term > le
		term = le
	EndIf

	If term <= from Return arr
	Return Mid(arr, from,  term-from)

EndFunction

' ***** End lang.bmx ******


Fixed rotation conversion in MaxB3D.B3DLoader

Strict

Rem
	bbdoc: Blitz3D model loader for MaxB3D
End Rem
Module MaxB3D.B3DLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.BoneAnimator
Import Prime.libB3D

Type TMeshLoaderB3D Extends TMeshLoader
	Field _mesh:TMesh

	Method Run(mesh:TMesh,stream:TStream,url:Object)
		Local model:TBB3DChunk=TBB3DChunk.Load(url)
		If model=Null Return False

		Local olddir$=CurrentDir()
		If String(url) ChangeDir(ExtractDir(String(url)))

		Local texture:TTexture[model.texs.length]
		Local brush:TBrush[model.brus.length]

		For Local i=0 To texture.length-1
			Local chunk:TTEXSChunk=model.texs[i]
			texture[i]=_currentworld.AddTexture(chunk.file,chunk.flags)
			If texture[i]
				texture[i].SetBlend chunk.blend
				texture[i].SetPosition chunk.x_pos,chunk.y_pos
				texture[i].SetScale chunk.x_scale,chunk.y_scale
				texture[i].SetRotation chunk.rotation*(180.0/Pi)
			EndIf
		Next

		For Local i=0 To brush.length-1
			Local chunk:TBRUSChunk=model.brus[i]
			brush[i]=_currentworld.AddBrush()
			brush[i].SetName chunk.name
			brush[i].SetColor chunk.red*255,chunk.green*255,chunk.blue*255;brush[i].SetAlpha chunk.alpha
			brush[i].SetShine chunk.shininess
			brush[i].SetBlend chunk.blend;brush[i].SetFX chunk.fx
			For Local j=0 To chunk.n_texs-1
				If chunk.texture_id[j]>-1 brush[i].SetTexture texture[chunk.texture_id[j]],j
			Next
		Next
		ChangeDir olddir

		' model.dump StandardIOStream

		If model.node
			ParseNode model.node,mesh,brush,mesh
			Return True
		EndIf
		Return False
	End Method

	Method ParseNode(node:TNODEChunk,parent:TEntity,brush:TBrush[],entity:TEntity=Null)
		Local meshchunk:TMESHChunk=TMESHChunk(node.kind),bonechunk:TBONEChunk=TBONEChunk(node.kind)

		Select node.kind
		Case Null
			entity=_currentworld.AddPivot(parent)
		Case meshchunk
			If entity=Null entity=_currentworld.AddMesh("*null*",parent)
			Local mesh:TMesh=TMesh(entity)
			mesh._animator=New TBoneAnimator
			_mesh=mesh

			If meshchunk.brush_id>-1 entity.SetBrush brush[meshchunk.brush_id]

			Local vrts:TVRTSChunk=meshchunk.vrts
			Local vertsurface:TSurface=New TSurface
			vertsurface.Resize(vrts.xyz.length/3,0)

			For Local i=0 To vertsurface.CountVertices()-1
				vertsurface.SetCoords i,vrts.xyz[i*3+0],vrts.xyz[i*3+1],vrts.xyz[i*3+2]
				If vrts.nxyz vertsurface.SetNormal i,vrts.nxyz[i*3+0],vrts.nxyz[i*3+1],vrts.nxyz[i*3+2]
				If vrts.rgba vertsurface.SetColor i,vrts.rgba[i*4+0]*255,vrts.rgba[i*4+1]*255,vrts.rgba[i*4+2]*255,vrts.rgba[i*4+3]
				For Local j=0 To vrts.SetCount()-1
					Local u#,v#
					If vrts.SetSize()>1 u=vrts.tex_coords[i][j,0];v=vrts.tex_coords[i][j,1]
					vertsurface.SetTexCoords i,1-u,v
				Next
			Next

			For Local i=0 To meshchunk.tris.length-1
				Local tri:TTRISChunk=meshchunk.tris[i]
				Local surface:TSurface=vertsurface.Copy()
				surface.Resize(-1,tri.vertex_id.length/3)
				If tri.brush_id>-1 surface.SetBrush brush[tri.brush_id]
				For Local t=0 To surface.CountTriangles()-1
					surface.SetTriangle t,tri.vertex_id[t*3+0],tri.vertex_id[t*3+1],tri.vertex_id[t*3+2]
				Next
				If vrts.nxyz=Null surface.UpdateNormals
				mesh.AppendSurface surface
			Next
		Case bonechunk
			entity=_currentworld.AddBone(parent)
			Local bone:TBone=TBone(entity)

			For Local surface:TSurface=EachIn _mesh._surfaces
				bone.AddSurface surface
				For Local i=0 To bonechunk.vertex_id.length-1
					bone.AddVertex surface,bonechunk.vertex_id[i],bonechunk.weight[i]
				Next
			Next

			Local ident_matrix:TMatrix=TMatrix.Identity()
			For Local chunk:TKEYSChunk=EachIn node.keys
				Local keys:TAnimKey[chunk.frame.length]
				Local pos#[]=chunk.position,rot#[]=chunk.rotation,scl#[]=chunk.scale
				For Local i=0 To chunk.frame.length-1
					Local bonekey:TBoneKey=New TBoneKey

					If pos.length>0 bonekey._px=pos[i*3+0];bonekey._py=pos[i*3+1];bonekey._pz=pos[i*3+2]
					If rot.length>0 bonekey._rw=rot[i*4+0];bonekey._rx=rot[i*4+1];bonekey._ry=rot[i*4+2];bonekey._rz=rot[i*4+3]
					If scl.length>0 bonekey._sx=scl[i*3+0];bonekey._sy=scl[i*3+1];bonekey._sz=scl[i*3+2]

					Local key:TAnimKey=New TAnimKey
					key._frame=chunk.frame[i]
					key._object=bonekey
					keys[i]=key
				Next
				TBoneAnimator(_mesh._animator).AddBone bone,keys
			Next
		Default
			entity=_currentworld.AddPivot(parent)
		End Select

		entity.SetName node.name
		entity.SetPosition node.position[0],node.position[1],node.position[2]
		Local pitch#,yaw#,roll#
		TQuaternion.Euler node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3],pitch,yaw,roll
		entity.SetRotation pitch,yaw,roll
		entity.SetScale node.scale[0],node.scale[1],node.scale[2]

		For Local child:TNODEChunk=EachIn node.node
			ParseNode child,entity,brush
		Next
		If TMesh(entity) If TBoneAnimator(TMesh(entity)._animator)._root=Null TMesh(entity)._animator=Null
		If TBone(entity) TBone(entity)._start_matrix=entity.GetMatrix()
	End Method

	Method Info$()
		Return "Blitz3D|b3d"
	End Method
	Method ModuleName$()
		Return "b3dloader"
	End Method
End Type
New TMeshLoaderB3D

Strict

Rem
	bbdoc: Blitz3D model loader for MaxB3D
End Rem
Module MaxB3D.B3DLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.BoneAnimator
Import Prime.libB3D

Type TMeshLoaderB3D Extends TMeshLoader
	Field _mesh:TMesh

	Method Run(mesh:TMesh,stream:TStream,url:Object)
		Local model:TBB3DChunk=TBB3DChunk.Load(url)
		If model=Null Return False

		Local olddir$=CurrentDir()
		If String(url) ChangeDir(ExtractDir(String(url)))

		Local texture:TTexture[model.texs.length]
		Local brush:TBrush[model.brus.length]

		For Local i=0 To texture.length-1
			Local chunk:TTEXSChunk=model.texs[i]
			texture[i]=_currentworld.AddTexture(chunk.file,chunk.flags)
			If texture[i]
				texture[i].SetBlend chunk.blend
				texture[i].SetPosition chunk.x_pos,chunk.y_pos
				texture[i].SetScale chunk.x_scale,chunk.y_scale
				texture[i].SetRotation chunk.rotation
			EndIf
		Next

		For Local i=0 To brush.length-1
			Local chunk:TBRUSChunk=model.brus[i]
			brush[i]=_currentworld.AddBrush()
			brush[i].SetName chunk.name
			brush[i].SetColor chunk.red*255,chunk.green*255,chunk.blue*255;brush[i].SetAlpha chunk.alpha
			brush[i].SetShine chunk.shininess
			brush[i].SetBlend chunk.blend;brush[i].SetFX chunk.fx
			For Local j=0 To chunk.n_texs-1
				If chunk.texture_id[j]>-1 brush[i].SetTexture texture[chunk.texture_id[j]],j
			Next
		Next
		ChangeDir olddir

		' model.dump StandardIOStream

		If model.node
			ParseNode model.node,mesh,brush,mesh
			Return True
		EndIf
		Return False
	End Method

	Method ParseNode(node:TNODEChunk,parent:TEntity,brush:TBrush[],entity:TEntity=Null)
		Local meshchunk:TMESHChunk=TMESHChunk(node.kind),bonechunk:TBONEChunk=TBONEChunk(node.kind)

		Select node.kind
		Case Null
			entity=_currentworld.AddPivot(parent)
		Case meshchunk
			If entity=Null entity=_currentworld.AddMesh("*null*",parent)
			Local mesh:TMesh=TMesh(entity)
			mesh._animator=New TBoneAnimator
			_mesh=mesh

			If meshchunk.brush_id>-1 entity.SetBrush brush[meshchunk.brush_id]

			Local vrts:TVRTSChunk=meshchunk.vrts
			Local vertsurface:TSurface=New TSurface
			vertsurface.Resize(vrts.xyz.length/3,0)

			For Local i=0 To vertsurface.CountVertices()-1
				vertsurface.SetCoords i,vrts.xyz[i*3+0],vrts.xyz[i*3+1],vrts.xyz[i*3+2]
				If vrts.nxyz vertsurface.SetNormal i,vrts.nxyz[i*3+0],vrts.nxyz[i*3+1],vrts.nxyz[i*3+2]
				If vrts.rgba vertsurface.SetColor i,vrts.rgba[i*4+0]*255,vrts.rgba[i*4+1]*255,vrts.rgba[i*4+2]*255,vrts.rgba[i*4+3]
				For Local j=0 To vrts.SetCount()-1
					Local u#,v#
					If vrts.SetSize()>1 u=vrts.tex_coords[i][j,0];v=vrts.tex_coords[i][j,1]
					vertsurface.SetTexCoords i,1-u,v
				Next
			Next

			For Local i=0 To meshchunk.tris.length-1
				Local tri:TTRISChunk=meshchunk.tris[i]
				Local surface:TSurface=vertsurface.Copy()
				surface.Resize(-1,tri.vertex_id.length/3)
				If tri.brush_id>-1 surface.SetBrush brush[tri.brush_id]
				For Local t=0 To surface.CountTriangles()-1
					surface.SetTriangle t,tri.vertex_id[t*3+0],tri.vertex_id[t*3+1],tri.vertex_id[t*3+2]
				Next
				If vrts.nxyz=Null surface.UpdateNormals
				mesh.AppendSurface surface
			Next
		Case bonechunk
			entity=_currentworld.AddBone(parent)
			Local bone:TBone=TBone(entity)

			For Local surface:TSurface=EachIn _mesh._surfaces
				bone.AddSurface surface
				For Local i=0 To bonechunk.vertex_id.length-1
					bone.AddVertex surface,bonechunk.vertex_id[i],bonechunk.weight[i]
				Next
			Next

			Local ident_matrix:TMatrix=TMatrix.Identity()
			For Local chunk:TKEYSChunk=EachIn node.keys
				Local keys:TAnimKey[chunk.frame.length]
				Local pos#[]=chunk.position,rot#[]=chunk.rotation,scl#[]=chunk.scale
				For Local i=0 To chunk.frame.length-1
					Local bonekey:TBoneKey=New TBoneKey

					If pos.length>0 bonekey._px=pos[i*3+0];bonekey._py=pos[i*3+1];bonekey._pz=pos[i*3+2]
					If rot.length>0 bonekey._rw=rot[i*4+0];bonekey._rx=rot[i*4+1];bonekey._ry=rot[i*4+2];bonekey._rz=rot[i*4+3]
					If scl.length>0 bonekey._sx=scl[i*3+0];bonekey._sy=scl[i*3+1];bonekey._sz=scl[i*3+2]

					Local key:TAnimKey=New TAnimKey
					key._frame=chunk.frame[i]
					key._object=bonekey
					keys[i]=key
				Next
				TBoneAnimator(_mesh._animator).AddBone bone,keys
			Next
		Default
			entity=_currentworld.AddPivot(parent)
		End Select

		entity.SetName node.name
		entity.SetPosition node.position[0],node.position[1],node.position[2]
		Local pitch#,yaw#,roll#
		TQuaternion.Euler node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3],pitch,yaw,roll
		entity.SetRotation pitch,yaw,roll
		entity.SetScale node.scale[0],node.scale[1],node.scale[2]

		For Local child:TNODEChunk=EachIn node.node
			ParseNode child,entity,brush
		Next
		If TMesh(entity) If TBoneAnimator(TMesh(entity)._animator)._root=Null TMesh(entity)._animator=Null
		If TBone(entity) TBone(entity)._start_matrix=entity.GetMatrix()
	End Method

	Method Info$()
		Return "Blitz3D|b3d"
	End Method
	Method ModuleName$()
		Return "b3dloader"
	End Method
End Type
New TMeshLoaderB3D


extremely minimal library implementation

' Blue Moon
' Basic Lua library

' this is the minimal "standard library", designed to be loaded into a VM on start
' most of this doesn't use the convenience interface so as to keep overhead down

' for now this is an Include rather than Import lib due to recursive dependency
' we'll fix that later

Rem
SuperStrict

Import "blueerror.bmx"
Import "bluevm.bmx"
Import "bluetable.bmx"
End Rem

Type BlueBasicLibrary
	Function _Load(vm:BlueVM, _ENV:BlueLuaVal)
		_ENV.Set("_G", _ENV)
		_ENV.Set("getmetatable", vm.ValueFromFunction(getmetatable))
		_ENV.Set("rawget", vm.ValueFromFunction(rawget))
		_ENV.Set("rawset", vm.ValueFromFunction(rawset))
		_ENV.Set("setmetatable", vm.ValueFromFunction(setmetatable))
		_ENV.Set("_VERSION", vm.mem.AllocConstant(_VERSION.Length, Short Ptr(_VERSION))
	End Function

	'assert
	'collectgarbage
	'dofile
	'error
	Function getmetatable:Int(vm:BlueVM, argc:Int, argv:Long Ptr, retv:Long Ptr)
		If argc < 1 Then vm.Error("not enough arguments to getmetatable: expecting object(1)")
		Local tag:Int = Int Ptr(argv)[1]
		If tag = BlueTypeTag.TBLBOX Or tag = BlueTypeTag.USRBOX
			Local meta:Int = Int Ptr Ptr(argv)[0][0]
			If meta
				Int Ptr(retv)[1] = BlueTypeTag.TBLBOX
				Int Ptr(retv)[0] = meta
			Else
				retv[0] = vm.mem.NIL
			EndIf
		Else
			retv[0] = vm.mem.NIL
		EndIf
		Return 1
	End Function
	'ipairs
	'load
	'loadfile
	'next
	'pairs
	'pcall
	'print
	'rawequal
	Function rawget:Int(vm:BlueVM, argc:Int, argv:Long Ptr, retv:Long Ptr)
		If argc < 2 Then vm.Error("not enough arguments to rawget: expecting table(1) and key(2)")
		Local tag:Int = Int Ptr(argv)[1]
		If tag = BlueTypeTag.TBLBOX
			retv[0] = BlueTable.RawGet(Byte Ptr Ptr(argv)[0], argv[1])
		Else
			vm.Error("argument (1) to rawget must be a table")
		EndIf
		Return 1
	End Function
	'rawlen
	Function rawset:Int(vm:BlueVM, argc:Int, argv:Long Ptr, retv:Long Ptr)
		If argc < 3 Then vm.Error("not enough arguments to rawset: expecting table(1), key(2), value(3)")
		Local tag:Int = Int Ptr(argv)[1]
		If tag = BlueTypeTag.TBLBOX
			BlueTable.RawSet(vm.mem, Byte Ptr Ptr(argv)[0], argv[1], argv[2])
			retv[0] = argv[0]
		Else
			vm.Error("argument (1) to rawset must be a table")
		EndIf
		Return 1
	End Function
	'select
	Function setmetatable:Int(vm:BlueVM, argc:Int, argv:Long Ptr, retv:Long Ptr)
		If argc < 2 Then vm.Error("not enough arguments to setmetatable: expecting object(1) and metatable(2)")
		Local tag:Int = Int Ptr(argv)[1]
		If tag = BlueTypeTag.TBLBOX Or tag = BlueTypeTag.USRBOX
			'check if existing metatable has __metatable field and error if so
			tag = Int Ptr(argv + 1)[1]
			If tag <> BlueTypeTag.TBLBOX Then vm.Error("argument (2) to setmetatable must be a table")
			'apply finalizer tag
			'apply weakness flag
			Int Ptr Ptr(argv)[0][0] = Int Ptr(argv + 1)[0]
		EndIf
		retv[0] = argv[0]	'manual doesn't say it's an error to not be a table
		Return 1
	End Function
	'tonumber
	'tostring
	'type
	Const _VERSION:String = "Lua 5.3 (incomplete)"
	'xpcall
End Type

' Blue Moon
' Basic Lua library

' this is the minimal "standard library", designed to be loaded into a VM on start
' most of this doesn't use the convenience interface so as to keep overhead down

' for now this is an Include rather than Import lib due to recursive dependency
' we'll fix that later

Rem
SuperStrict

Import "blueerror.bmx"
Import "bluevm.bmx"
Import "bluetable.bmx"
End Rem

Type BlueBasicLibrary
	Function _Load(_ENV:Byte Ptr)
	End Function

	Function getmetatable()
	End Function
	Function setmetatable()
	End Function

End Type


Now saving metafile when installing a module

refs gh-1

Rem
	bbdoc: Maximus 'install' argument implementation.
End Rem
Type mxInstallImpl Extends dArgumentImplementation

	Field m_instmap:dObjectMap
	Field m_depcheckmap:dObjectMap
	Field m_nobuild:Int = False, m_nounpack:Int = False, m_keeptemp:Int = False
	Field m_nothreaded:Int = False, m_makedocs:Int = False, m_forceinstall:Int = False

	Method New()
		init(["install"])
		m_instmap = New dObjectMap
		m_depcheckmap = New dObjectMap
	End Method

	Rem
		bbdoc: Check the current arguments for errors (according to the specific implementation).
		returns: Nothing.
		about: This method will throw an error if the arguments are invalid.
	End Rem
	Method CheckArgs()
		If GetArgumentCount() = 0
			ThrowCommonError(mxCmdErrors.REQUIRESPARAMS, m_args.GetName())
		End If
		CheckOptions()
	End Method

	Rem
		bbdoc: Get a string describing the typical usage of the argument.
		returns: A string describing the typical usage of the argument.
	End Rem
	Method GetUsage:String()
		Return _s("arg.install.usage")
	End Method

	Rem
		bbdoc: Execute the implementation's operation.
		returns: Nothing.
	End Rem
	Method Execute()
		m_instmap.Clear()
		m_depcheckmap.Clear()
		If mainapp.m_sourceshandler
			Local nfounds:dObjectMap = New dObjectMap
			For Local svar:dStringVariable = EachIn m_args
				Local verid:String = svar.Get(), modid:String = mxModUtils.GetIDFromVerID(verid)
				Local modul:mxModule = mainapp.m_sourceshandler.GetModuleWithID(modid)
				If modul
					Local instmod:mxInstModule = New mxInstModule.Create(verid, modul)
					If instmod.SetVersionFromVerID(verid)
						m_instmap._Insert(modid, instmod)
					Else
						ThrowError(_s("arg.install.notfound.version", [modid, mxModUtils.GetVersionFromVerID(verid)]))
					End If
				Else
					nfounds._Insert(modid, modid)
				End If
			Next
			If nfounds.Count() > 0
				logger.LogError(_s("arg.install.notfound.instmods"))
				Local a:String
				For Local b:String = EachIn nfounds.ValueEnumerator()
					a:+ b + " "
				Next
				a = a[..a.Length - 1]
				logger.LogMessage("~t" + a)
				Return
			End If
			If CheckDependencies()
				If CheckVersions()
					DoInstall()
				End If
			End If
		Else
			ThrowError(_s("error.install.nosources"))
		End If
	End Method

	Rem
		bbdoc: Check the options given to the command.
		returns: Nothing.
	End Rem
	Method CheckOptions()
		m_nobuild = False; m_nounpack = False; m_keeptemp = False
		m_nothreaded = False; m_makedocs = False
		For Local opt:dIdentifier = EachIn m_args
			Select opt.GetName().ToLower()
				Case "-nobuild" m_nobuild = True
				Case "-nounpack" m_nounpack = True
				Case "-keeptemp" m_keeptemp = True
				Case "-nothreaded" m_nothreaded = True
				Case "-makedocs" m_makedocs = True
				Case "-force" m_forceinstall = True
				Default ThrowCommonError(mxOptErrors.UNKNOWN, opt.GetName())
			End Select
		Next
	End Method

	Rem
		bbdoc: Check for version conflictions and warn the user.
		returns: True if the installtion should continue, or False if the user halted the operation.
	End Rem
	Method CheckVersions:Int()
		'For Local instmod:mxInstModule = EachIn m_instmap.ValueEnumerator()
		'	' TODO
		'Next
		Return True
	End Method

	Rem
		bbdoc: Check the dependencies for the modules to be installed.
		returns: Nothing.
	End Rem
	Method CheckDependencies:Int()
		Local addlist:TListEx, nfounds:dObjectMap = New dObjectMap
		For Local instmod:mxInstModule = EachIn m_instmap.ValueEnumerator()
			CheckModuleDependencies(instmod, nfounds)
		Next
		If nfounds.Count() > 0
			logger.LogError(_s("arg.install.notfound.instdeps"))
			Local a:String
			For Local b:String = EachIn nfounds.ValueEnumerator()
				a:+ b + " "
			Next
			a = a[..a.Length - 1]
			logger.LogMessage("~t" + a)
			If m_forceinstall
				m_nobuild = True
				Local resp:String = Input(_s("arg.install.missingdeps") + " ").ToLower()
				If resp = "y" Or resp = "yes"
					Return True
				End If
			End If
			Return False
		End If
		Return True
	End Method

	Rem
		bbdoc: Check the dependencies for the given module and adapt the install list accordingly.
		returns: Nothing.
	End Rem
	Method CheckModuleDependencies(instmod:mxInstModule, nfounds:dObjectMap)
		Local addlist:TListEx = instmod.CheckCompliance()
		If addlist
			For Local modid:String = EachIn addlist
				If Not m_depcheckmap._Contains(modid)
					Local dmodul:mxModule = mainapp.m_sourceshandler.GetModuleWithID(modid)
					m_depcheckmap._Insert(modid, modid)
					If dmodul
						If Not m_instmap._Contains(modid)
							Local dinstmod:mxInstModule = New mxInstModule.Create(modid, dmodul)
							If instmod.GetVersionName().ToLower() = "dev"
								dinstmod.SetVersionFromName("dev")
							Else
								dinstmod.SetVersion(Null)
							End If
							m_instmap._Insert(modid, dinstmod)
							CheckModuleDependencies(dinstmod, nfounds)
						End If
					Else
						nfounds._Insert(modid, modid)
					End If
				End If
			Next
		End If
	End Method

	Rem
		bbdoc: Do the actual installing.
		returns: Nothing.
	End Rem
	Method DoInstall()
		If m_instmap.Count() > 0
			Local a:String, instmod:mxInstModule
			logger.LogMessage(_s("arg.install.modulestoinstall"))
			For instmod = EachIn m_instmap.ValueEnumerator()
				a:+ instmod.GetVerID() + " "
			Next
			a = a[..a.Length - 1]
			logger.LogMessage("~t" + a)
			Local resp:String = Input(_s("arg.install.continuewithinstall") + " ").ToLower()
			If resp = "y" Or resp = "yes"
				For instmod = EachIn m_instmap.ValueEnumerator()
					instmod.FetchSourceArchive()
				Next
				If Not m_nounpack
					For instmod = EachIn m_instmap.ValueEnumerator()
						instmod.Unpack()
					Next
				Else
					logger.LogMessage(_s("message.skipping.unpack"))
				End If
				If Not m_keeptemp Then DeleteDir("tmp/", True)
				If Not m_nobuild
					Local scopes:dObjectMap = New dObjectMap
					For instmod = EachIn m_instmap.ValueEnumerator()
						scopes._Insert(instmod.GetModuleScope(), instmod.GetModuleScope())
					Next
					For Local scope:String = EachIn scopes.ValueEnumerator()
						If mxBMKUtils.MakeMods(scope, m_nothreaded ~ 1) <> 0
							ThrowError(_s("error.install.build", [scope]))
						End If
					Next
					If m_makedocs
						'For instmod = EachIn m_instmap.ValueEnumerator()
						'	mxModUtils.DocMods(instmod.GetID())
						'Next
						' Can't build docs for individual modules yet
						mxModUtils.DocMods()
					End If
				Else
					logger.LogMessage(_s("message.skipping.install"))
				End If
			End If
		Else
			logger.LogMessage(_s("arg.install.nomodulestoinstall"))
		End If
	End Method

End Type

Rem
	bbdoc: Maximus install info module.
	about: This type is used for temporary install information on modules.
End Rem
Type mxInstModule

	Field m_id:String
	Field m_version:mxModuleVersion
	Field m_module:mxModule

	Rem
		bbdoc: Create a new mxInstModule.
		returns: Itself.
	End Rem
	Method Create:mxInstModule(verid:String, modul:mxModule)
		SetID(mxModUtils.GetIDFromVerID(verid))
		SetModule(modul)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the modid to be installed.
		returns: Nothing.
	End Rem
	Method SetID(id:String)
		m_id = id
	End Method

	Rem
		bbdoc: Get the modid to be installed.
		returns: The modid to be installed.
	End Rem
	Method GetID:String()
		Return m_id
	End Method

	Rem
		bbdoc: Set the version from the given versioned-module name (e.g. "foo.bar/1.02").
		returns: True if the version was set, or False if the version could not be found (was not found).
		about: The latest version will be set if the given id is Null (or if it isn't versioned (e.g. "foo.bar", would result in the latest version being selected)).
	End Rem
	Method SetVersionFromVerID:Int(verid:String)
		Return SetVersionFromName(mxModUtils.GetVersionFromVerID(verid))
	End Method

	Rem
		bbdoc: Set the version to be installed from the given version name (e.g. "1.02").
		returns: True if the version was set, or False if the version could not be set (was not found).
		about: The latest version will be set if the given name is Null.
	End Rem
	Method SetVersionFromName:Int(name:String)
		If Not name
			SetVersion(Null) ' Set the latest version (the version wasn't forced)
			Return True
		Else
			Local ver:mxModuleVersion = m_module.GetVersionWithName(name)
			If ver
				SetVersion(ver)
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Set the install version.
		returns: Nothing.
		about: If the given value is Null, it will be automatically set to the latest (non-development) version.
	End Rem
	Method SetVersion(version:mxModuleVersion)
		m_version = version
		If Not m_version
			m_version = m_module.GetLatestVersion()
		End If
	End Method

	Rem
		bbdoc: Get the install version.
		returns: The version to be installed.
	End Rem
	Method GetVersion:mxModuleVersion()
		Return m_version
	End Method

	Rem
		bbdoc: Set the module to be installed.
		returns: Nothing.
	End Rem
	Method SetModule(modul:mxModule)
		m_module = modul
	End Method

	Rem
		bbdoc: Get the module to be installed.
		returns: The module to be installed.
	End Rem
	Method GetModule:mxModule()
		Return m_module
	End Method

	Rem
		bbdoc: Get the module's scope.
		returns: The module's scope.
	End Rem
	Method GetModuleScope:String()
		Return mxModUtils.GetScopeFromID(m_id)
	End Method

	Rem
		bbdoc: Get the module's name.
		returns: The module's name.
	End Rem
	Method GetModuleName:String()
		Return mxModUtils.GetNameFromID(m_id)
	End Method

	Rem
		bbdoc: Get the versioned-module id.
		returns: The exact module to be installed (e.g. "foo.bar/1.02").
	End Rem
	Method GetVerID:String()
		Return m_id + "/" + m_version.GetName()
	End Method

	Rem
		bbdoc: Get the name of the version to be installed.
		returns: The name of the version to be installed.
	End Rem
	Method GetVersionName:String()
		Return m_version.GetName()
	End Method

'#end region Field accessors

	Rem
		bbdoc: Check the dependency compliance of the current module set.
		returns: Null if all dependencies are matched by existing modules, or a list of module ids that are not yet installed.
	End Rem
	Method CheckCompliance:TListEx()
		Return m_version.GetDependencies().CheckCompliance()
	End Method

	Rem
		bbdoc: Fetch the source archive for the version to be installed.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		m_version.FetchSourceArchive()
	End Method

	Rem
		bbdoc: Unpack the version's archive.
		returns: Nothing.
	End Rem
	Method Unpack()
		Local archivepath:String = m_version.GetTemporaryFilePath()
		Local zreader:ZipReader = New ZipReader
		logger.LogMessage(_s("message.unpacking", [archivepath]))
		If zreader.OpenZip(archivepath)
			Local filename:String, outputpath:String
			Local basepath:String = mainapp.m_modpath + "/" + mxModUtils.GetScopeFromID(m_id) + ".mod/"
			For Local fileinfo:SZipFileEntry = EachIn zreader.m_zipFileList.FileList
				filename = fileinfo.zipFileName
				outputpath = basepath + filename
				'DebugLog("Zip outputpath: ~q" + outputpath + "~q")
				If filename[filename.Length - 1] = 47 ' "/"
					CreateDir(outputpath, True)
				Else
					If CreateFileExplicitly(outputpath)
						zreader.ExtractFileToDisk(filename, outputpath, False)
					Else
						zreader.CloseZip()
						ThrowError(_s("error.writeperms", [outputpath]))
					End If
				End If
			Next
			zreader.CloseZip()

			Local metafile:mxMetaFile = New mxMetaFile.Create(basepath + GetModuleName() + ".mod/meta.maximus")
			metafile.SetMetaData(GetModuleScope(), GetModuleName(), GetVersionName())
			metafile.Save()
		Else
			ThrowError(_s("error.install.openarchive", [archivepath]))
		End If
	End Method

End Type

Rem
	bbdoc: Maximus 'install' argument implementation.
End Rem
Type mxInstallImpl Extends dArgumentImplementation

	Field m_instmap:dObjectMap
	Field m_depcheckmap:dObjectMap
	Field m_nobuild:Int = False, m_nounpack:Int = False, m_keeptemp:Int = False
	Field m_nothreaded:Int = False, m_makedocs:Int = False, m_forceinstall:Int = False

	Method New()
		init(["install"])
		m_instmap = New dObjectMap
		m_depcheckmap = New dObjectMap
	End Method

	Rem
		bbdoc: Check the current arguments for errors (according to the specific implementation).
		returns: Nothing.
		about: This method will throw an error if the arguments are invalid.
	End Rem
	Method CheckArgs()
		If GetArgumentCount() = 0
			ThrowCommonError(mxCmdErrors.REQUIRESPARAMS, m_args.GetName())
		End If
		CheckOptions()
	End Method

	Rem
		bbdoc: Get a string describing the typical usage of the argument.
		returns: A string describing the typical usage of the argument.
	End Rem
	Method GetUsage:String()
		Return _s("arg.install.usage")
	End Method

	Rem
		bbdoc: Execute the implementation's operation.
		returns: Nothing.
	End Rem
	Method Execute()
		m_instmap.Clear()
		m_depcheckmap.Clear()
		If mainapp.m_sourceshandler
			Local nfounds:dObjectMap = New dObjectMap
			For Local svar:dStringVariable = EachIn m_args
				Local verid:String = svar.Get(), modid:String = mxModUtils.GetIDFromVerID(verid)
				Local modul:mxModule = mainapp.m_sourceshandler.GetModuleWithID(modid)
				If modul
					Local instmod:mxInstModule = New mxInstModule.Create(verid, modul)
					If instmod.SetVersionFromVerID(verid)
						m_instmap._Insert(modid, instmod)
					Else
						ThrowError(_s("arg.install.notfound.version", [modid, mxModUtils.GetVersionFromVerID(verid)]))
					End If
				Else
					nfounds._Insert(modid, modid)
				End If
			Next
			If nfounds.Count() > 0
				logger.LogError(_s("arg.install.notfound.instmods"))
				Local a:String
				For Local b:String = EachIn nfounds.ValueEnumerator()
					a:+ b + " "
				Next
				a = a[..a.Length - 1]
				logger.LogMessage("~t" + a)
				Return
			End If
			If CheckDependencies()
				If CheckVersions()
					DoInstall()
				End If
			End If
		Else
			ThrowError(_s("error.install.nosources"))
		End If
	End Method

	Rem
		bbdoc: Check the options given to the command.
		returns: Nothing.
	End Rem
	Method CheckOptions()
		m_nobuild = False; m_nounpack = False; m_keeptemp = False
		m_nothreaded = False; m_makedocs = False
		For Local opt:dIdentifier = EachIn m_args
			Select opt.GetName().ToLower()
				Case "-nobuild" m_nobuild = True
				Case "-nounpack" m_nounpack = True
				Case "-keeptemp" m_keeptemp = True
				Case "-nothreaded" m_nothreaded = True
				Case "-makedocs" m_makedocs = True
				Case "-force" m_forceinstall = True
				Default ThrowCommonError(mxOptErrors.UNKNOWN, opt.GetName())
			End Select
		Next
	End Method

	Rem
		bbdoc: Check for version conflictions and warn the user.
		returns: True if the installtion should continue, or False if the user halted the operation.
	End Rem
	Method CheckVersions:Int()
		'For Local instmod:mxInstModule = EachIn m_instmap.ValueEnumerator()
		'	' TODO
		'Next
		Return True
	End Method

	Rem
		bbdoc: Check the dependencies for the modules to be installed.
		returns: Nothing.
	End Rem
	Method CheckDependencies:Int()
		Local addlist:TListEx, nfounds:dObjectMap = New dObjectMap
		For Local instmod:mxInstModule = EachIn m_instmap.ValueEnumerator()
			CheckModuleDependencies(instmod, nfounds)
		Next
		If nfounds.Count() > 0
			logger.LogError(_s("arg.install.notfound.instdeps"))
			Local a:String
			For Local b:String = EachIn nfounds.ValueEnumerator()
				a:+ b + " "
			Next
			a = a[..a.Length - 1]
			logger.LogMessage("~t" + a)
			If m_forceinstall
				m_nobuild = True
				Local resp:String = Input(_s("arg.install.missingdeps") + " ").ToLower()
				If resp = "y" Or resp = "yes"
					Return True
				End If
			End If
			Return False
		End If
		Return True
	End Method

	Rem
		bbdoc: Check the dependencies for the given module and adapt the install list accordingly.
		returns: Nothing.
	End Rem
	Method CheckModuleDependencies(instmod:mxInstModule, nfounds:dObjectMap)
		Local addlist:TListEx = instmod.CheckCompliance()
		If addlist
			For Local modid:String = EachIn addlist
				If Not m_depcheckmap._Contains(modid)
					Local dmodul:mxModule = mainapp.m_sourceshandler.GetModuleWithID(modid)
					m_depcheckmap._Insert(modid, modid)
					If dmodul
						If Not m_instmap._Contains(modid)
							Local dinstmod:mxInstModule = New mxInstModule.Create(modid, dmodul)
							If instmod.GetVersionName().ToLower() = "dev"
								dinstmod.SetVersionFromName("dev")
							Else
								dinstmod.SetVersion(Null)
							End If
							m_instmap._Insert(modid, dinstmod)
							CheckModuleDependencies(dinstmod, nfounds)
						End If
					Else
						nfounds._Insert(modid, modid)
					End If
				End If
			Next
		End If
	End Method

	Rem
		bbdoc: Do the actual installing.
		returns: Nothing.
	End Rem
	Method DoInstall()
		If m_instmap.Count() > 0
			Local a:String, instmod:mxInstModule
			logger.LogMessage(_s("arg.install.modulestoinstall"))
			For instmod = EachIn m_instmap.ValueEnumerator()
				a:+ instmod.GetVerID() + " "
			Next
			a = a[..a.Length - 1]
			logger.LogMessage("~t" + a)
			Local resp:String = Input(_s("arg.install.continuewithinstall") + " ").ToLower()
			If resp = "y" Or resp = "yes"
				For instmod = EachIn m_instmap.ValueEnumerator()
					instmod.FetchSourceArchive()
				Next
				If Not m_nounpack
					For instmod = EachIn m_instmap.ValueEnumerator()
						instmod.Unpack()
					Next
				Else
					logger.LogMessage(_s("message.skipping.unpack"))
				End If
				If Not m_keeptemp Then DeleteDir("tmp/", True)
				If Not m_nobuild
					Local scopes:dObjectMap = New dObjectMap
					For instmod = EachIn m_instmap.ValueEnumerator()
						scopes._Insert(instmod.GetModuleScope(), instmod.GetModuleScope())
					Next
					For Local scope:String = EachIn scopes.ValueEnumerator()
						If mxBMKUtils.MakeMods(scope, m_nothreaded ~ 1) <> 0
							ThrowError(_s("error.install.build", [scope]))
						End If
					Next
					If m_makedocs
						'For instmod = EachIn m_instmap.ValueEnumerator()
						'	mxModUtils.DocMods(instmod.GetID())
						'Next
						' Can't build docs for individual modules yet
						mxModUtils.DocMods()
					End If
				Else
					logger.LogMessage(_s("message.skipping.install"))
				End If
			End If
		Else
			logger.LogMessage(_s("arg.install.nomodulestoinstall"))
		End If
	End Method

End Type

Rem
	bbdoc: Maximus install info module.
	about: This type is used for temporary install information on modules.
End Rem
Type mxInstModule

	Field m_id:String
	Field m_version:mxModuleVersion
	Field m_module:mxModule

	Rem
		bbdoc: Create a new mxInstModule.
		returns: Itself.
	End Rem
	Method Create:mxInstModule(verid:String, modul:mxModule)
		SetID(mxModUtils.GetIDFromVerID(verid))
		SetModule(modul)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the modid to be installed.
		returns: Nothing.
	End Rem
	Method SetID(id:String)
		m_id = id
	End Method

	Rem
		bbdoc: Get the modid to be installed.
		returns: The modid to be installed.
	End Rem
	Method GetID:String()
		Return m_id
	End Method

	Rem
		bbdoc: Set the version from the given versioned-module name (e.g. "foo.bar/1.02").
		returns: True if the version was set, or False if the version could not be found (was not found).
		about: The latest version will be set if the given id is Null (or if it isn't versioned (e.g. "foo.bar", would result in the latest version being selected)).
	End Rem
	Method SetVersionFromVerID:Int(verid:String)
		Return SetVersionFromName(mxModUtils.GetVersionFromVerID(verid))
	End Method

	Rem
		bbdoc: Set the version to be installed from the given version name (e.g. "1.02").
		returns: True if the version was set, or False if the version could not be set (was not found).
		about: The latest version will be set if the given name is Null.
	End Rem
	Method SetVersionFromName:Int(name:String)
		If Not name
			SetVersion(Null) ' Set the latest version (the version wasn't forced)
			Return True
		Else
			Local ver:mxModuleVersion = m_module.GetVersionWithName(name)
			If ver
				SetVersion(ver)
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Set the install version.
		returns: Nothing.
		about: If the given value is Null, it will be automatically set to the latest (non-development) version.
	End Rem
	Method SetVersion(version:mxModuleVersion)
		m_version = version
		If Not m_version
			m_version = m_module.GetLatestVersion()
		End If
	End Method

	Rem
		bbdoc: Get the install version.
		returns: The version to be installed.
	End Rem
	Method GetVersion:mxModuleVersion()
		Return m_version
	End Method

	Rem
		bbdoc: Set the module to be installed.
		returns: Nothing.
	End Rem
	Method SetModule(modul:mxModule)
		m_module = modul
	End Method

	Rem
		bbdoc: Get the module to be installed.
		returns: The module to be installed.
	End Rem
	Method GetModule:mxModule()
		Return m_module
	End Method

	Rem
		bbdoc: Get the module's scope.
		returns: The module's scope.
	End Rem
	Method GetModuleScope:String()
		Return mxModUtils.GetScopeFromID(m_id)
	End Method

	Rem
		bbdoc: Get the module's name.
		returns: The module's name.
	End Rem
	Method GetModuleName:String()
		Return mxModUtils.GetNameFromID(m_id)
	End Method

	Rem
		bbdoc: Get the versioned-module id.
		returns: The exact module to be installed (e.g. "foo.bar/1.02").
	End Rem
	Method GetVerID:String()
		Return m_id + "/" + m_version.GetName()
	End Method

	Rem
		bbdoc: Get the name of the version to be installed.
		returns: The name of the version to be installed.
	End Rem
	Method GetVersionName:String()
		Return m_version.GetName()
	End Method

'#end region Field accessors

	Rem
		bbdoc: Check the dependency compliance of the current module set.
		returns: Null if all dependencies are matched by existing modules, or a list of module ids that are not yet installed.
	End Rem
	Method CheckCompliance:TListEx()
		Return m_version.GetDependencies().CheckCompliance()
	End Method

	Rem
		bbdoc: Fetch the source archive for the version to be installed.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		m_version.FetchSourceArchive()
	End Method

	Rem
		bbdoc: Unpack the version's archive.
		returns: Nothing.
	End Rem
	Method Unpack()
		Local archivepath:String = m_version.GetTemporaryFilePath()
		Local zreader:ZipReader = New ZipReader
		logger.LogMessage(_s("message.unpacking", [archivepath]))
		If zreader.OpenZip(archivepath)
			Local filename:String, outputpath:String
			For Local fileinfo:SZipFileEntry = EachIn zreader.m_zipFileList.FileList
				filename = fileinfo.zipFileName
				outputpath = mainapp.m_modpath + "/" + mxModUtils.GetScopeFromID(m_id) + ".mod/" + filename
				'DebugLog("Zip outputpath: ~q" + outputpath + "~q")
				If filename[filename.Length - 1] = 47 ' "/"
					CreateDir(outputpath, True)
				Else
					If CreateFileExplicitly(outputpath)
						zreader.ExtractFileToDisk(filename, outputpath, False)
					Else
						zreader.CloseZip()
						ThrowError(_s("error.writeperms", [outputpath]))
					End If
				End If
			Next
			zreader.CloseZip()
		Else
			ThrowError(_s("error.install.openarchive", [archivepath]))
		End If
	End Method

End Type