Played with collisions.

Played with collisions.

Strict

Rem
	bbdoc: Provides Blitz3D-style collision detection/handling.
End Rem
Module MaxB3D.B3DCollision
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core

Type TB3DCollisionDriver Extends TCollisionDriver
	Method Update(config:TWorldConfig, speed#)
		Global c_vec_a:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_b:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_radius:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)

		Global c_vec_i:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_j:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_k:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)

		Global c_mat:Byte Ptr=C_CreateMatrixObject(c_vec_i,c_vec_j,c_vec_k)

		Global c_vec_v:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)

		Global c_tform:Byte Ptr=C_CreateTFormObject(c_mat,c_vec_v)
		For Local i=0 To MAX_COLLISION_TYPES-1
			If config.CollisionType[i]=Null Then Continue
			For Local entity:TEntity=EachIn config.CollisionType[i]
				entity._collision=Null

				If entity.GetVisible()=False Continue

				Local x#,y#,z#
				entity.GetPosition x,y,z,True
				C_UpdateVecObject(c_vec_a,x,y,z)
				C_UpdateVecObject(c_vec_b,entity._oldx,entity._oldy,entity._oldz)
				C_UpdateVecObject(c_vec_radius,entity._radiusx,entity._radiusy,entity._radiusx)

				Local c_col_info:Byte Ptr=C_CreateCollisionInfoObject(c_vec_a,c_vec_b,c_vec_radius)
				Local c_coll:Byte Ptr=Null

				Local response
				Repeat
					Local hit=False
					c_coll=C_CreateCollisionObject()
					Local entity2_hit:TEntity=Null
					For Local col_pair:TCollisionPair=EachIn config.CollisionPairs
						If col_pair.src=i
							If config.CollisionType[col_pair.dest]=Null Then Continue

							For Local entity2:TEntity=EachIn config.CollisionType[col_pair.dest]
								If entity2.GetVisible()=False Then Continue
								If entity=entity2 Then Continue

								If QuickCheck(entity,entity2)=False Then Continue

								C_UpdateVecObject(c_vec_i,entity2._matrix._m[0,0],entity2._matrix._m[0,1],-entity2._matrix._m[0,2])
								C_UpdateVecObject(c_vec_j,entity2._matrix._m[1,0],entity2._matrix._m[1,1],-entity2._matrix._m[1,2])
								C_UpdateVecObject(c_vec_k,-entity2._matrix._m[2,0],-entity2._matrix._m[2,1],entity2._matrix._m[2,2])

								C_UpdateMatrixObject(c_mat,c_vec_i,c_vec_j,c_vec_k)
								C_UpdateVecObject(c_vec_v,entity2._matrix._m[3,0],entity2._matrix._m[3,1],entity2._matrix._m[3,2])
								C_UpdateTFormObject(c_tform,c_mat,c_vec_v)

								If col_pair.methd<>COLLISION_METHOD_POLYGON
									C_UpdateCollisionInfoObject(c_col_info,entity2._radiusx,entity2._boxx,entity2._boxy,entity2._boxz,entity2._boxx+entity2._boxwidth,entity2._boxy+entity2._boxheight,entity2._boxz+entity2._boxdepth)
								EndIf

								Local tree:Byte Ptr
								If TMesh(entity2)<>Null tree=TMesh(entity2).TreeCheck()

								hit=C_CollisionDetect(c_col_info,c_coll,c_tform,tree,col_pair.methd)

								If hit Then entity2_hit=entity2;response=col_pair.response
							Next
						EndIf
					Next

					If entity2_hit<>Null
						Local collision:TCollision=New TCollision
						collision.x=C_CollisionX()
						collision.y=C_CollisionY()
						collision.z=C_CollisionZ()
						collision.nx=C_CollisionNX()
						collision.ny=C_CollisionNY()
						collision.nz=C_CollisionNZ()
						collision.entity=entity2_hit
						collision.triangle=C_CollisionTriangle()
						If TMesh(entity2_hit)<>Null collision.surface=C_CollisionSurface()

						entity._collision=entity._collision[..entity._collision.length+1]
						entity._collision[entity._collision.length-1]=collision

						If C_CollisionResponse(c_col_info,c_coll,response)=False Then Exit
					Else
						Exit
					EndIf
					C_DeleteCollisionObject(c_coll)
				Forever

				C_DeleteCollisionObject(c_coll)
				If C_CollisionFinal(c_col_info) entity.SetPosition(C_CollisionPosX(),C_CollisionPosY(),C_CollisionPosZ(),True)
				C_DeleteCollisionInfoObject(c_col_info)
				entity.GetPosition entity._oldx,entity._oldy,entity._oldz,True
			Next
		Next
	End Method

	Function QuickCheck(entity:TEntity,entity2:TEntity)
		Local x#,y#,z#
		entity.GetPosition x,y,z,True
		If entity._oldx=x And entity._oldy=y And entity._oldz=z Return False
		Return True
	End Function
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function B3DCollisionDriver:TB3DCollisionDriver()
	Global _driver:TB3DCollisionDriver=New TB3DCollisionDriver
	Return _driver
End Function

B3DCollisionDriver()

Strict

Rem
	bbdoc: Provides Blitz3D-style collision detection/handling.
End Rem
Module MaxB3D.B3DCollision
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core

Type TB3DCollisionDriver Extends TCollisionDriver
	Method Update(config:TWorldConfig, speed#)
		Global c_vec_a:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_b:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_radius:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)

		Global c_vec_i:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_j:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)
		Global c_vec_k:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)

		Global c_mat:Byte Ptr=C_CreateMatrixObject(c_vec_i,c_vec_j,c_vec_k)

		Global c_vec_v:Byte Ptr=C_CreateVecObject(0.0,0.0,0.0)

		Global c_tform:Byte Ptr=C_CreateTFormObject(c_mat,c_vec_v)
		For Local i=0 To MAX_COLLISION_TYPES-1
			If config.CollisionType[i]=Null Then Continue
			For Local entity:TEntity=EachIn config.CollisionType[i]
				entity._collision=Null

				If entity.GetVisible()=False Continue

				Local x#,y#,z#
				entity.GetPosition x,y,z,True
				C_UpdateVecObject(c_vec_a,x,y,z)
				C_UpdateVecObject(c_vec_b,entity._oldx,entity._oldy,entity._oldz)
				C_UpdateVecObject(c_vec_radius,entity._radiusx,entity._radiusy,entity._radiusx)

				Local c_col_info:Byte Ptr=C_CreateCollisionInfoObject(c_vec_a,c_vec_b,c_vec_radius)
				Local c_coll:Byte Ptr=Null

				Local response
				Repeat
					Local hit=False
					c_coll=C_CreateCollisionObject()
					Local entity2_hit:TEntity=Null
					For Local col_pair:TCollisionPair=EachIn config.CollisionPairs
						If col_pair.src=i
							If config.CollisionType[col_pair.dest]=Null Then Continue

							For Local entity2:TEntity=EachIn config.CollisionType[col_pair.dest]
								If entity2.GetVisible()=False Then Continue
								If entity=entity2 Then Continue

								If QuickCheck(entity,entity2)=False Then Continue

								C_UpdateVecObject(c_vec_i,entity2._matrix._m[0,0],entity2._matrix._m[0,1],-entity2._matrix._m[0,2])
								C_UpdateVecObject(c_vec_j,entity2._matrix._m[1,0],entity2._matrix._m[1,1],-entity2._matrix._m[1,2])
								C_UpdateVecObject(c_vec_k,-entity2._matrix._m[2,0],-entity2._matrix._m[2,1],entity2._matrix._m[2,2])

								C_UpdateMatrixObject(c_mat,c_vec_i,c_vec_j,c_vec_k)
								C_UpdateVecObject(c_vec_v,entity2._matrix._m[3,0],entity2._matrix._m[3,1],-entity2._matrix._m[3,2])
								C_UpdateTFormObject(c_tform,c_mat,c_vec_v)

								If col_pair.methd<>COLLISION_METHOD_POLYGON
									C_UpdateCollisionInfoObject(c_col_info,entity2._radiusx,entity2._boxx,entity2._boxy,entity2._boxz,entity2._boxx+entity2._boxwidth,entity2._boxy+entity2._boxheight,entity2._boxz+entity2._boxdepth)
								EndIf

								Local tree:Byte Ptr
								If TMesh(entity2)<>Null tree=TMesh(entity2).TreeCheck()

								hit=C_CollisionDetect(c_col_info,c_coll,c_tform,tree,col_pair.methd)

								If hit Then entity2_hit=entity2;response=col_pair.response
							Next
						EndIf
					Next

					If entity2_hit<>Null
						Local collision:TCollision=New TCollision
						collision.x=C_CollisionX()
						collision.y=C_CollisionY()
						collision.z=C_CollisionZ()
						collision.nx=C_CollisionNX()
						collision.ny=C_CollisionNY()
						collision.nz=C_CollisionNZ()
						collision.entity=entity2_hit
						collision.triangle=C_CollisionTriangle()
						If TMesh(entity2_hit)<>Null collision.surface=C_CollisionSurface()

						entity._collision=entity._collision[..entity._collision.length+1]
						entity._collision[entity._collision.length-1]=collision

						If C_CollisionResponse(c_col_info,c_coll,response)=False Then Exit
					Else
						Exit
					EndIf
					C_DeleteCollisionObject(c_coll)
				Forever

				C_DeleteCollisionObject(c_coll)
				If C_CollisionFinal(c_col_info) entity.SetPosition(C_CollisionPosX(),C_CollisionPosY(),C_CollisionPosZ(),True)
				C_DeleteCollisionInfoObject(c_col_info)
				entity.GetPosition entity._oldx,entity._oldy,entity._oldz,True
			Next
		Next
	End Method

	Function QuickCheck(entity:TEntity,entity2:TEntity)
		Local x#,y#,z#
		entity.GetPosition x,y,z,True
		If entity._oldx=x And entity._oldy=y And entity._oldz=z Return False
		Return True
	End Function
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function B3DCollisionDriver:TB3DCollisionDriver()
	Global _driver:TB3DCollisionDriver=New TB3DCollisionDriver
	Return _driver
End Function

B3DCollisionDriver()


Reset all install options before setting them

refs gh-17, gh-18

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

	Field m_instmap:dObjectMap
	Field m_depcheckmap:dObjectMap
	Field m_unmanagedmap: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
	Field m_overwriteunmanaged:Int = False

	Method New()
		init(["install"])
		m_instmap = New dObjectMap
		m_depcheckmap = New dObjectMap
		m_unmanagedmap = 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()
		m_unmanagedmap.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()
				'Make sure we only install/update managed modules
				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; m_forceinstall = False; m_overwriteunmanaged = 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
				Case "-overwriteunmanaged" m_overwriteunmanaged = 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()
			Local ver:String = mxModUtils.GetInstalledVersionFromVerID(instmod.GetVerID())
			'Make sure we skip unmanaged modules
			If ver = "unmanaged"
				If Not m_overwriteunmanaged Then m_instmap._Remove(instmod.GetID())
				m_unmanagedmap._Insert(instmod.GetID(), instmod)
			End If
		Next
		Return True
	End Method

	Rem
		bbdoc: Check the dependencies for the modules to be installed.
		returns: Nothing.
	End Rem
	Method CheckDependencies:Int()
		Local 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
				If mainapp.m_userinput.Confirm(_s("arg.install.missingdeps"))
					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

			If m_unmanagedmap.Count()
				If m_overwriteunmanaged
					logger.LogMessage(_s("arg.install.modulestooverwrite"))
				Else
					logger.LogMessage(_s("arg.install.modulestoskip"))
				End If
				For instmod = EachIn m_unmanagedmap.ValueEnumerator()
					a:+instmod.GetVerID() + " "
				Next
				a = a[..a.Length - 1]
				logger.LogMessage("~t" + a)
				a = ""
			End If

			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)
			If mainapp.m_userinput.Confirm(_s("arg.install.continuewithinstall"))
				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"))
			If m_unmanagedmap.Count()
				logger.LogMessage(_s("arg.install.modulestoskip"))
				Local a:String
				For Local instmod:mxInstModule = EachIn m_unmanagedmap.ValueEnumerator()
					a:+instmod.GetVerID() + " "
				Next
				a = a[..a.Length - 1]
				logger.LogMessage("~t" + a)
			End If
		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/"
			DeleteDir(mxModUtils.ModulePath(m_id), True)
			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_unmanagedmap: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
	Field m_overwriteunmanaged:Int = False

	Method New()
		init(["install"])
		m_instmap = New dObjectMap
		m_depcheckmap = New dObjectMap
		m_unmanagedmap = 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()
		m_unmanagedmap.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()
				'Make sure we only install/update managed modules
				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
				Case "-overwriteunmanaged" m_overwriteunmanaged = 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()
			Local ver:String = mxModUtils.GetInstalledVersionFromVerID(instmod.GetVerID())
			'Make sure we skip unmanaged modules
			If ver = "unmanaged"
				If Not m_overwriteunmanaged Then m_instmap._Remove(instmod.GetID())
				m_unmanagedmap._Insert(instmod.GetID(), instmod)
			End If
		Next
		Return True
	End Method

	Rem
		bbdoc: Check the dependencies for the modules to be installed.
		returns: Nothing.
	End Rem
	Method CheckDependencies:Int()
		Local 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
				If mainapp.m_userinput.Confirm(_s("arg.install.missingdeps"))
					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

			If m_unmanagedmap.Count()
				If m_overwriteunmanaged
					logger.LogMessage(_s("arg.install.modulestooverwrite"))
				Else
					logger.LogMessage(_s("arg.install.modulestoskip"))
				End If
				For instmod = EachIn m_unmanagedmap.ValueEnumerator()
					a:+instmod.GetVerID() + " "
				Next
				a = a[..a.Length - 1]
				logger.LogMessage("~t" + a)
				a = ""
			End If

			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)
			If mainapp.m_userinput.Confirm(_s("arg.install.continuewithinstall"))
				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"))
			If m_unmanagedmap.Count()
				logger.LogMessage(_s("arg.install.modulestoskip"))
				Local a:String
				For Local instmod:mxInstModule = EachIn m_unmanagedmap.ValueEnumerator()
					a:+instmod.GetVerID() + " "
				Next
				a = a[..a.Length - 1]
				logger.LogMessage("~t" + a)
			End If
		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/"
			DeleteDir(mxModUtils.ModulePath(m_id), True)
			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


Added Max path-getting (and error checking). Added SetArgs method to mxApp to handle Null arguments. Changed how mainapp was set to avoid a few errors.

Rem
Copyright (c) 2010 Tim Howard

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

Framework brl.blitz
Import brl.standardio
Import brl.maxutil
Import cower.jonk
Import duct.objectmap

Include "src/logger.bmx"
Include "src/errors.bmx"
Include "src/arghandler.bmx"
Include "src/impl/help.bmx"
Include "src/impl/version.bmx"
Include "src/sources.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"

	Field m_maxpath:String

	Field m_args:String[]
	Field m_arghandler:mxArgumentHandler

	Rem
		bbdoc: Create a new mxApp.
		returns: The new mxApp.
	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()
		Try
			m_maxpath = BlitzMaxPath()
		Catch e:Object
			ThrowError("Unable to determine the path to BlitzMax!~nHave you set the 'BMXPATH' environment variable?")
		End Try
		m_arghandler = New mxArgumentHandler
		m_arghandler.AddArgImpl(New mxHelpImpl)
		m_arghandler.AddArgImpl(New mxVersionImpl)
	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
		Local i:Int, arg:String, isopt:Int
		For i = 0 To m_args.Length - 1
			arg = m_args[i]
			argimpl = m_arghandler.GetArgImplFromAlias(arg)
			isopt = arg.StartsWith("-")
			If argimpl <> Null
				If isopt = True
					argimpl.SetArgs(Null)
					argimpl.SetCallConvention(mxCallConvention.OPTION)
					argimpl.Execute()
				Else ' Must be a command (leaving out parameters for options, as that would get really complicated)
					argimpl.SetCallConvention(mxCallConvention.COMMAND)
					ParseCommandArgs(argimpl, m_args[i + 1..])
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt = True
					ThrowCommonError(mxOptErrors.UNKNOWN, arg)
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg)
				End If
			End If
		Next
		OnExit()
	End Method

	Rem
		bbdoc: Parse command arguments.
		returns: Nothing.
	End Rem
	Method ParseCommandArgs(argimpl:mxArgumentImplementation, args:String[])
		' This will need to be parsed eventually, but for now..
		argimpl.SetArgs(args)
	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 = args
	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
Copyright (c) 2010 Tim Howard

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

Framework brl.blitz
Import brl.standardio
Import cower.jonk
Import duct.objectmap

Include "src/logger.bmx"
Include "src/errors.bmx"
Include "src/arghandler.bmx"
Include "src/impl/help.bmx"
Include "src/impl/version.bmx"
Include "src/sources.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"

	Field m_args:String[]
	Field m_arghandler:mxArgumentHandler

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

	Rem
		bbdoc: Called when the application is created.
		returns: Nothing.
	End Rem
	Method OnInit()
		m_arghandler = New mxArgumentHandler
		m_arghandler.AddArgImpl(New mxHelpImpl)
		m_arghandler.AddArgImpl(New mxVersionImpl)
	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
		Local i:Int, arg:String, isopt:Int
		For i = 0 To m_args.Length - 1
			arg = m_args[i]
			argimpl = m_arghandler.GetArgImplFromAlias(arg)
			isopt = arg.StartsWith("-")
			If argimpl <> Null
				If isopt = True
					argimpl.SetArgs(Null)
					argimpl.SetCallConvention(mxCallConvention.OPTION)
					argimpl.Execute()
				Else ' Must be a command (leaving out parameters for options, as that would get really complicated)
					argimpl.SetCallConvention(mxCallConvention.COMMAND)
					ParseCommandArgs(argimpl, m_args[i + 1..])
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt = True
					ThrowCommonError(mxOptErrors.UNKNOWN, arg)
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg)
				End If
			End If
		Next
		OnExit()
	End Method

	Rem
		bbdoc: Parse command arguments.
		returns: Nothing.
	End Rem
	Method ParseCommandArgs(argimpl:mxArgumentImplementation, args:String[])
		' This will need to be parsed eventually, but for now..
		argimpl.SetArgs(args)
	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


Fixed some memory bugs when an empty array was returned fromm the server

Closes gh-4
Closes gh-5

Rem
	bbdoc: Exception for TXMLRPC_Response_Data
End Rem
Type TXMLRPC_Response_Data_Exception Extends TXMLRPC_Exception
End Type

Rem
	bbdoc: Data returned from XML-RPC server
End Rem
Type TXMLRPC_Response_Data
	Rem
		bbdoc: This field contains all data returned from a response. If a response value was given an ID it can be accessed by its ID. If not it can be accessed with an ID of 0..inf
	End Rem
	Field data:TMap

	Rem
		bbdoc: Create a TXMLRPC_Response_Data object by passing the response XML message and the output options.
	End Rem
	Method Create:TXMLRPC_Response_Data(xmlMessage:String, options:Byte Ptr)
		Local message:Byte Ptr = xmlMessage.ToCString()
		Local request:Byte Ptr = XMLRPC_REQUEST_FromXML(message, 0, options)

		Local el:Byte Ptr = XMLRPC_RequestGetData(request)

		Self.data = TXMLRPC_Response_Data.IterateVector(el)

		MemFree(message)

		XMLRPC_RequestFree(request, 0)
		XMLRPC_Free(el)

		Return Self
	End Method

	Rem
		bbdoc: Iterate over an XMLRPC vector and add it to a TMap. Private function
	End Rem
	Function IterateVector:TMap(el:Byte Ptr)
		Local data:TMap = New TMap

		If el
			If XMLRPC_VectorSize(el) = 0
				Local cStr:Byte Ptr = XMLRPC_GetValueID(el)
				Local id:String = "0"
				If cStr
					id = String.FromCString(cStr)
					XMLRPC_Free(cstr)
				End If

				data.Insert(id, TXMLRPC_Value_Abstract.XMLRPC_To_BlizMax(el))
			Else
				'Rewind vector
				Local itr:Byte Ptr = XMLRPC_VectorRewind(el)
				If itr
					Local dataCounter:Int = 0
					While itr
						Local cStr:Byte Ptr = XMLRPC_GetValueID(itr)
						Local id:String = String.FromCString(cStr)

						If id.Length = 0
							id = String.FromInt(dataCounter)
							dataCounter:+1
						End If

						data.Insert(id, TXMLRPC_Value_Abstract.XMLRPC_To_BlizMax(itr))
						XMLRPC_Free(cStr)

						'Next element
						itr = XMLRPC_VectorNext(el)
					Wend
				End If
			End If
		End If

		Return data
	End Function

	Rem
		bbdoc: Helper function to show what's inside the map
	End Rem
	Function DebugData:String(map:TMap, pad:Int Var)
		Local str:String
		Local prefix:String
		For Local i:Int = 0 To pad
			prefix:+"-"
		Next

		If map
			For Local val:TXMLRPC_Value_Abstract = EachIn map.Values()
				str:+prefix + " " + TTypeId.ForObject(val).Name() + " (" + val.name + ") = " + val.ToString() + "~n"
				If TXMLRPC_Value_Collection(val)
					pad:+1
					str:+TXMLRPC_Response_Data.DebugData(TXMLRPC_Value_Collection(val).data, pad) + "~n"
					pad:-1
				End If
			Next
		End If
		Return str
	End Function
End Type

Rem
	bbdoc: Exception for TXMLRPC_Response_Data
End Rem
Type TXMLRPC_Response_Data_Exception Extends TXMLRPC_Exception
End Type

Rem
	bbdoc: Data returned from XML-RPC server
End Rem
Type TXMLRPC_Response_Data
	Rem
		bbdoc: This field contains all data returned from a response. If a response value was given an ID it can be accessed by its ID. If not it can be accessed with an ID of 0..inf
	End Rem
	Field data:TMap

	Rem
		bbdoc: Create a TXMLRPC_Response_Data object by passing the response XML message and the output options.
	End Rem
	Method Create:TXMLRPC_Response_Data(xmlMessage:String, options:Byte Ptr)
		Local message:Byte Ptr = xmlMessage.ToCString()
		Local request:Byte Ptr = XMLRPC_REQUEST_FromXML(message, 0, options)

		Local el:Byte Ptr = XMLRPC_RequestGetData(request)

		Self.data = TXMLRPC_Response_Data.IterateVector(el)

		MemFree(message)

		Local freeIo:Int = 1
		'Apparently a unhandled memory exception error gets thrown when
		'xmlMessage only contained 1 value and XMLRPC_RequestFree also
		'frees the response data.
		If Not XMLRPC_VectorRewind(el) Then freeIo = 0

		XMLRPC_RequestFree(request, freeIo)

		Return Self
	End Method

	Rem
		bbdoc: Iterate over an XMLRPC vector and add it to a TMap. Private function
	End Rem
	Function IterateVector:TMap(el:Byte Ptr)
		Local data:TMap = New TMap

		If el
			'Rewind vector
			Local itr:Byte Ptr = XMLRPC_VectorRewind(el)
			If Not itr
				Local cStr:Byte Ptr = XMLRPC_GetValueID(el)
				Local id:String = "0"
				If cStr
					id = String.FromCString(cStr)
					XMLRPC_Free(cstr)
				End If

				data.Insert(id, TXMLRPC_Value_Abstract.XMLRPC_To_BlizMax(el))
			Else
				Local dataCounter:Int = 0
				While itr
					Local cStr:Byte Ptr = XMLRPC_GetValueID(itr)
					Local id:String = String.FromCString(cStr)

					If id.Length = 0
						id = String.FromInt(dataCounter)
						dataCounter:+1
					End If

					data.Insert(id, TXMLRPC_Value_Abstract.XMLRPC_To_BlizMax(itr))
					XMLRPC_Free(cStr)

					'Next element
					itr = XMLRPC_VectorNext(el)
				Wend
			End If
		End If

		Return data
	End Function

	Rem
		bbdoc: Helper function to show what's inside the map
	End Rem
	Function DebugData:String(map:TMap, pad:Int Var)
		Local str:String
		Local prefix:String
		For Local i:Int = 0 To pad
			prefix:+"-"
		Next

		If map
			For Local val:TXMLRPC_Value_Abstract = EachIn map.Values()
				str:+prefix + " " + TTypeId.ForObject(val).Name() + " (" + val.name + ") = " + val.ToString() + "~n"
				If TXMLRPC_Value_Collection(val)
					pad:+1
					str:+TXMLRPC_Response_Data.DebugData(TXMLRPC_Value_Collection(val).data, pad) + "~n"
					pad:-1
				End If
			Next
		End If
		Return str
	End Function
End Type


Bumped version to 0.9.0

Bumped version to 0.9.0

SuperStrict

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

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

Import duct.variables
Import duct.objectmap
Import duct.json
Import duct.locale
Import duct.clapp
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/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() ' Skip the first element because it is the program's location
mainapp.SetArgs(AppArgs[1..])
mainapp.Run()

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

	Const c_version:String = "0.9.0"
	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_sourceshandler:mxSourcesHandler

	Field m_updateimpl:mxUpdateImpl
	Field m_sourcesupdated:Int

	Rem
		bbdoc: Create a new mxApp.
		returns: Itself.
	End Rem
	Method Create:mxApp()
		mainapp = Self
		Super.Create()
		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 dArgumentHandler.Create()
		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 Not m_sourceshandler
			' 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:dArgumentImplementation, isopt:Int
		For Local arg:dIdentifier = EachIn m_arguments
			argimpl = m_arghandler.GetArgImplWithAlias(arg.GetName())
			isopt = (arg.GetName()[0] = 45)
			If argimpl
				If isopt
					argimpl.SetCallConvention(dCallConvention.OPTION)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
				Else
					argimpl.SetCallConvention(dCallConvention.COMMAND)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt
					ThrowCommonError(mxOptErrors.UNKNOWN, arg.GetName())
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg.GetName())
				End If
			End If
		Next
		Rem
		' Test version snatching
		Local time:Int = MilliSecs(), ver:String
		For Local modid:String = EachIn mxModUtils.GetModules().KeyEnumerator()
			ver = mxModUtils.GetInstalledVersionFromVerID(modid)
			If ver And Not (ver = "dev")
				DebugLog("versioned-id: ~q" + modid + "/" + ver + "~q")
			Else If Not ver
				DebugLog("Failed to get version from " + modid)
			End If
		Next
		DebugLog("timed: " + mxModUtils.GetModules().Count() + " modules in " + (MilliSecs() - time) + "ms")
		End Rem
		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(dCallConvention.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 Not args
			args = ["--help"]
		End If
		ParseArguments(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 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 Not file
			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:dTextReplacer = New dTextReplacer
	Local ltext:dLocalizedText
	If mainapp.m_locale Then ltext = mainapp.m_locale.TextFromStructureL(iden)
	If ltext Then ltext = mainapp.m_defaultlocale.TextFromStructureL(iden)
	If ltext
		replacer.SetString(ltext.GetValue())
		replacer.AutoReplacements("{", "}")
		If extra
			Local i:Int
			For Local rep:dTextReplacement = 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
	Return Null
End Function

SuperStrict

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

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

Import duct.variables
Import duct.objectmap
Import duct.json
Import duct.locale
Import duct.clapp
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/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() ' Skip the first element because it is the program's location
mainapp.SetArgs(AppArgs[1..])
mainapp.Run()

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

	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_sourceshandler:mxSourcesHandler

	Field m_updateimpl:mxUpdateImpl
	Field m_sourcesupdated:Int

	Rem
		bbdoc: Create a new mxApp.
		returns: Itself.
	End Rem
	Method Create:mxApp()
		mainapp = Self
		Super.Create()
		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 dArgumentHandler.Create()
		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 Not m_sourceshandler
			' 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:dArgumentImplementation, isopt:Int
		For Local arg:dIdentifier = EachIn m_arguments
			argimpl = m_arghandler.GetArgImplWithAlias(arg.GetName())
			isopt = (arg.GetName()[0] = 45)
			If argimpl
				If isopt
					argimpl.SetCallConvention(dCallConvention.OPTION)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
				Else
					argimpl.SetCallConvention(dCallConvention.COMMAND)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt
					ThrowCommonError(mxOptErrors.UNKNOWN, arg.GetName())
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg.GetName())
				End If
			End If
		Next
		Rem
		' Test version snatching
		Local time:Int = MilliSecs(), ver:String
		For Local modid:String = EachIn mxModUtils.GetModules().KeyEnumerator()
			ver = mxModUtils.GetInstalledVersionFromVerID(modid)
			If ver And Not (ver = "dev")
				DebugLog("versioned-id: ~q" + modid + "/" + ver + "~q")
			Else If Not ver
				DebugLog("Failed to get version from " + modid)
			End If
		Next
		DebugLog("timed: " + mxModUtils.GetModules().Count() + " modules in " + (MilliSecs() - time) + "ms")
		End Rem
		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(dCallConvention.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 Not args
			args = ["--help"]
		End If
		ParseArguments(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 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 Not file
			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:dTextReplacer = New dTextReplacer
	Local ltext:dLocalizedText
	If mainapp.m_locale Then ltext = mainapp.m_locale.TextFromStructureL(iden)
	If ltext Then ltext = mainapp.m_defaultlocale.TextFromStructureL(iden)
	If ltext
		replacer.SetString(ltext.GetValue())
		replacer.AutoReplacements("{", "}")
		If extra
			Local i:Int
			For Local rep:dTextReplacement = 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
	Return Null
End Function


Corrected returns for the SetCommonFromVariable method.

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String, m_description:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the base's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		Assert description, "(mxModuleBase.SetDescription) description cannot be Null!"
		m_description = description
	End Method

	Rem
		bbdoc: Get the base's description.
		returns: The base's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "desc"
				SetDescription(variable.ValueAsString())
			Default
				Return False
		End Select
		Return True
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul <> Null
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ValueByKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				Else
					SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

	Rem
		bbdoc: Get the full name of the module (e.g. "modscope.module").
		returns: The full name of the module.
	End Rem
	Method GetFullName:String()
		Return m_parent.m_name + "." + m_name
	End Method

'#end region Field accessors

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version <> Null
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ValueByKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev if available) version for this module.
		returns: The latest version for this module.
		about: The latest version will be returned, or the dev version if it is the only version.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If hver = Null Or hver.Compare(ver) = 1
					hver = ver
				End If
			End If
		Next
		If hver = Null
			hver = GetVersionWithName("dev")
		End If
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable) = True
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable).GetValues()
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
				Default
					Return False
			End Select
			Return True
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

	Rem
		bbdoc: Create a version.
		returns: Itself.
	End Rem
	Method Create:mxModuleVersion(parent:mxModule, name:String, url:String)
		SetParent(parent)
		SetName(name)
		SetUrl(url)
		Return Self
	End Method

	Rem
		bbdoc: Clone the version.
		returns: A clone of the version.
		about: If @withdeps is True (default value), the dependencies will be added as well.
	End Rem
	Method Clone:mxModuleVersion(withdeps:Int = True)
		Local v:mxModuleVersion = New mxModuleVersion.Create(m_parent, m_name, m_url)
		For Local dep:mxModuleDependency = EachIn DependencyEnumerator()
			v.m_dependencies.AddDependency(dep)
		Next
		Return v
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + m_parent.GetFullName() + "-" + m_name + ".zip"
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

	Rem
		bbdoc: Get the version parts for the version.
		returns: True if the version is development, in which case the parameters are both 0; or False, in which case the parameters are set accordingly.
	End Rem
	Method GetVersionParts:Int(vmajor:String Var, vminor:String Var)
		If m_name <> "dev"
			Local i:Int = m_name.Find(".")
			If i > -1
				vmajor = m_name[..i]
				vminor = m_name[i + 1..]
			Else
				vmajor = m_name ' I'm not sure what other version formats would be used, so I'm just playing a random card here
			End If
			Return False
		End If
		Return True
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(variable.ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
			Default
				Return False
		End Select
		Return True
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver <> Null
			If m_name = ver.m_name
				Return 0
			Else
				Local smajor:String, sminor:String, wmajor:String, wminor:String
				Local sdev:Int = GetVersionParts(smajor, sminor), wdev:Int = ver.GetVersionParts(wmajor, wminor)
				If (sdev = True And wdev = True) Or (smajor = wmajor And sminor = wminor)
					Return 0
				Else If smajor > wmajor
					Return 1
				Else If smajor = wmajor
					If sminor > wminor
						Return 1
					Else
						Return -1
					End If
				Else
					Return -1
				End If
			End If
		End If
		Return Super.Compare(with)
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "~t", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream <> Null
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), ["User-Agent: " + mainapp.m_useragent], "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String, m_description:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the base's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		Assert description, "(mxModuleBase.SetDescription) description cannot be Null!"
		m_description = description
	End Method

	Rem
		bbdoc: Get the base's description.
		returns: The base's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "desc"
				SetDescription(variable.ValueAsString())
				Return True
		End Select
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul <> Null
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ValueByKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				Else
					SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

	Rem
		bbdoc: Get the full name of the module (e.g. "modscope.module").
		returns: The full name of the module.
	End Rem
	Method GetFullName:String()
		Return m_parent.m_name + "." + m_name
	End Method

'#end region Field accessors

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version <> Null
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ValueByKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev if available) version for this module.
		returns: The latest version for this module.
		about: The latest version will be returned, or the dev version if it is the only version.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If hver = Null Or hver.Compare(ver) = 1
					hver = ver
				End If
			End If
		Next
		If hver = Null
			hver = GetVersionWithName("dev")
		End If
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable) = True
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable).GetValues()
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
			End Select
			Return False
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + m_parent.GetFullName() + "-" + m_name + ".zip"
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

	Rem
		bbdoc: Get the version parts for the version.
		returns: True if the version is development, in which case the parameters are both 0; or False, in which case the parameters are set accordingly.
	End Rem
	Method GetVersionParts:Int(vmajor:String Var, vminor:String Var)
		If m_name <> "dev"
			Local i:Int = m_name.Find(".")
			If i > -1
				vmajor = m_name[..i]
				vminor = m_name[i + 1..]
			Else
				vmajor = m_name ' I'm not sure what other version formats would be used, so I'm just playing a random card here
			End If
			Return False
		End If
		Return True
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(variable.ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
				Return True
		End Select
		Return False
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver <> Null
			If m_name = ver.m_name
				Return 0
			Else
				Local smajor:String, sminor:String, wmajor:String, wminor:String
				Local sdev:Int = GetVersionParts(smajor, sminor), wdev:Int = ver.GetVersionParts(wmajor, wminor)
				If (sdev = True And wdev = True) Or (smajor = wmajor And sminor = wminor)
					Return 0
				Else If smajor > wmajor
					Return 1
				Else If smajor = wmajor
					If sminor > wminor
						Return 1
					Else
						Return -1
					End If
				Else
					Return -1
				End If
			End If
		End If
		Return 0
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "~t", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream <> Null
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), ["User-Agent: " + mainapp.m_useragent], "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type


Modified driver parent system.

Modified driver parent system.

Strict

Import BRL.Max2D
Import Prime.Max2DEx
Import "light.bmx"
Import "camera.bmx"
Import "mesh.bmx"
Import "flat.bmx"
Import "sprite.bmx"
Import "terrain.bmx"
Import "bspmodel.bmx"

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "core/driver",message
End Function

Public

Type TMaxB3DShaderData
	Field _projection:TMatrix
'	Field _modelview:TMatrix
	Field _modelviewproj:TMatrix
End Type

Global _creategraphicshook=AllocHookId()

Type TCaps
	Field PointSprites
	Field MaxPointSize#

	Method CopyBase(caps:TCaps)
		PointSprites=caps.PointSprites
		MaxPointSize=caps.MaxPointSize
	End Method
End Type

Type TMaxB3DDriver Extends TMax2DExDriver
	Global _configs:TWorldConfig[]

	Field _texture:TTexture[8],_caps:TCaps
	Field _prevwidth,_prevheight
	Field _in_max2d=True

	Method CreateGraphics:TGraphics( width,height,depth,hertz,flags )
		RunHooks _creategraphicshook,Null
		Return Super.CreateGraphics(width,height,depth,hertz,flags)
	End Method

	Method AttachGraphics:TGraphics( widget,flags )
		RunHooks _creategraphicshook,Null
		Return Super.AttachGraphics(widget,flags)
	End Method

	Method SetGraphics(g:TGraphics)
		_parent.SetGraphics(g)
		_current=g
		If _prevwidth<>GraphicsWidth() Or _prevheight<>GraphicsHeight()
			For Local config:TWorldConfig = EachIn _configs
				config.Width=GraphicsWidth()
				config.Height=GraphicsHeight()
			Next
			ScaleViewports
			_prevwidth=GraphicsWidth();_prevheight=GraphicsHeight()
		EndIf
		Global firsttime=True
		If firsttime
			_caps=GetCaps()
			firsttime=False
		EndIf
	End Method

	Method MakeBuffer:TBuffer(src:Object,width,height,flags) Abstract

	Method TextureBuffer:TBuffer(texture:TTexture,frame=0,flags=BUFFER_COLOR)
		Return MakeBuffer(texture._frame[frame],texture._width,texture._height,flags)
	End Method

	Method SetBuffer(buffer:TBuffer)
		For Local config:TWorldConfig = EachIn _configs
			config.Width=buffer._width
			config.Height=buffer._height
		Next
		Return TMax2DExDriver(_parent).SetBuffer(buffer)
	End Method

	Method BackBuffer:TBuffer()
		Return TMax2DExDriver(_parent).BackBuffer()
	End Method

	Method GetCaps:TCaps() Abstract

	Method SetMax2D(enable) Abstract

	Method DoMax2D()
		If Not _in_max2d
			_in_max2d=True
			SetMax2D True
		EndIf
	End Method
	Method EndMax2D()
		If _in_max2d
			_in_max2d=False
			SetMax2D False
		EndIf
	End Method

	Method Abbr$() Abstract
	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig) Abstract
	Method SetCamera(camera:TCamera,config:TWorldConfig) Abstract
	Method SetLight(light:TLight,index) Abstract

	Method BeginEntityRender(entity:TEntity) Abstract
	Method EndEntityRender(entity:TEntity) Abstract

	Method RenderSurface(surface:TSurfaceRes,brush:TBrush) Abstract
	Method RenderFlat(plane:TFlat) Abstract
	Method RenderSprite(sprite:TSprite) Abstract
	Method RenderTerrain(terrain:TTerrain) Abstract
	Method RenderBSPTree(tree:TBSPTree) Abstract

	Method UpdateTextureRes:TTextureRes(frame:TTextureFrame,flags) Abstract
	Method UpdateSurfaceRes:TSurfaceRes(surface:TSurface) Abstract

	Method MergeSurfaceRes:TSurfaceRes(base:TSurface,animation:TSurface,data) Abstract

	Method ScaleViewports()
		For Local config:TWorldConfig = EachIn _configs
			For Local camera:TCamera=EachIn config.List[WORLDLIST_CAMERA]
				Local x,y,width,height
				camera.GetViewport x,y,width,height
				Local sx#=config.Width/Float(_prevwidth),sy#=config.Height/Float(_prevheight)
				If width=0
					width=GraphicsWidth()
				Else
					x:*sx;width:*sx
				EndIf
				If height=0
					height=GraphicsHeight()
				Else
					y:*sy;height:*sy
				EndIf
				camera.SetViewport x,y,width,height
			Next
		Next
	End Method
End Type

Strict

Import BRL.Max2D
Import Prime.Max2DEx
Import "light.bmx"
Import "camera.bmx"
Import "mesh.bmx"
Import "flat.bmx"
Import "sprite.bmx"
Import "terrain.bmx"
Import "bspmodel.bmx"

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "core/driver",message
End Function

Public

Type TMaxB3DShaderData
	Field _projection:TMatrix
'	Field _modelview:TMatrix
	Field _modelviewproj:TMatrix
End Type

Global _creategraphicshook=AllocHookId()

Type TCaps
	Field PointSprites
	Field MaxPointSize#

	Method CopyBase(caps:TCaps)
		PointSprites=caps.PointSprites
		MaxPointSize=caps.MaxPointSize
	End Method
End Type

Type TMaxB3DDriver Extends TMax2DExDriver
	Global _parent:TMax2DExDriver

	Field _texture:TTexture[8],_caps:TCaps
	Field _prevwidth,_prevheight
	Field _in_max2d=True
	Field _configs:TWorldConfig[]

	Method CreateGraphics:TGraphics( width,height,depth,hertz,flags )
		RunHooks _creategraphicshook,Null
		Return Super.CreateGraphics(width,height,depth,hertz,flags)
	End Method

	Method AttachGraphics:TGraphics( widget,flags )
		RunHooks _creategraphicshook,Null
		Return Super.AttachGraphics(widget,flags)
	End Method

	Method SetGraphics(g:TGraphics)
		_parent.SetGraphics(g)
		_current=g
		If _prevwidth<>GraphicsWidth() Or _prevheight<>GraphicsHeight()
			For Local config:TWorldConfig = EachIn _configs
				config.Width=GraphicsWidth()
				config.Height=GraphicsHeight()
			Next
			ScaleViewports
			_prevwidth=GraphicsWidth();_prevheight=GraphicsHeight()
		EndIf
		Global firsttime=True
		If firsttime
			_caps=GetCaps()
			firsttime=False
		EndIf
	End Method

	Method MakeBuffer:TBuffer(src:Object,width,height,flags) Abstract

	Method TextureBuffer:TBuffer(texture:TTexture,frame=0,flags=BUFFER_COLOR)
		Return MakeBuffer(texture._frame[frame],texture._width,texture._height,flags)
	End Method

	Method SetBuffer(buffer:TBuffer)
		For Local config:TWorldConfig = EachIn _configs
			config.Width=buffer._width
			config.Height=buffer._height
		Next
		Return _parent.SetBuffer(buffer)
	End Method

	Method BackBuffer:TBuffer()
		Return _parent.BackBuffer()
	End Method

	Method GetCaps:TCaps() Abstract

	Method SetMax2D(enable) Abstract

	Method DoMax2D()
		If Not _in_max2d
			_in_max2d=True
			SetMax2D True
		EndIf
	End Method
	Method EndMax2D()
		If _in_max2d
			_in_max2d=False
			SetMax2D False
		EndIf
	End Method

	Method Abbr$() Abstract
	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig) Abstract
	Method SetCamera(camera:TCamera,config:TWorldConfig) Abstract
	Method SetLight(light:TLight,index) Abstract

	Method BeginEntityRender(entity:TEntity) Abstract
	Method EndEntityRender(entity:TEntity) Abstract

	Method RenderSurface(surface:TSurfaceRes,brush:TBrush) Abstract
	Method RenderFlat(plane:TFlat) Abstract
	Method RenderSprite(sprite:TSprite) Abstract
	Method RenderTerrain(terrain:TTerrain) Abstract
	Method RenderBSPTree(tree:TBSPTree) Abstract

	Method UpdateTextureRes:TTextureRes(frame:TTextureFrame,flags) Abstract
	Method UpdateSurfaceRes:TSurfaceRes(surface:TSurface) Abstract

	Method MergeSurfaceRes:TSurfaceRes(base:TSurface,animation:TSurface,data) Abstract

	Method ScaleViewports()
		For Local config:TWorldConfig = EachIn _configs
			For Local camera:TCamera=EachIn config.List[WORLDLIST_CAMERA]
				Local x,y,width,height
				camera.GetViewport x,y,width,height
				Local sx#=config.Width/Float(_prevwidth),sy#=config.Height/Float(_prevheight)
				If width=0
					width=GraphicsWidth()
				Else
					x:*sx;width:*sx
				EndIf
				If height=0
					height=GraphicsHeight()
				Else
					y:*sy;height:*sy
				EndIf
				camera.SetViewport x,y,width,height
			Next
		Next
	End Method
End Type


Fixed BlitzMax native code

Fixed BlitzMax native code

Type flixel

	Function isMobile:Int()
		Return False
	End Function

End Type

Function resize_array_array_XYZRecord:bb_xyzrecord__1X_1Y_1Z_1Record[][] (arr:bb_xyzrecord__1X_1Y_1Z_1Record[][], leng:Int)
	Local i:Int = arr.length
	arr = arr[..leng]

	If( leng<=i ) Return arr

	For Local l:Int = 0 Until Len(arr)
		arr[l] = arr[l][..leng]
	Next

	While( i<leng )
		arr[0][i]= Null
		i:+1
	Wend

	Return arr
End Function

Type flixel

	Function systemMillisecs:Int()
		Return Millisecs()
	End Function

	Function hideMouse()
		bmxHideMouse()
	End Function

	Function showMouse()
		bmxShowMouse()
	End Function

	Function isMobile:Int()
		Return False
	End Function

	Function openURL(url:String)
		bmxOpenURL(url)
	End Function

End Type

Function bmxHideMouse()
	HideMouse()
EndFunction

Function bmxShowMouse()
	ShowMouse()
EndFunction

Function bmxOpenURL(url:String)
	OpenURL(url)
End Function

Function resize_array_array_XYZRecord:bb_xyzrecord__1X_1Y_1Z_1Record[][] (arr:bb_xyzrecord__1X_1Y_1Z_1Record[][], leng:Int)
	Local i:Int = arr.length
	arr = arr[..leng]

	If( leng<=i ) Return arr

	For Local l:Int = 0 Until Len(arr)
		arr[l] = arr[l][..leng]
	Next

	While( i<leng )
		arr[0][i]= Null
		i:+1
	Wend

	Return arr
End Function

Function bb_input__1Set_1Input_1Device:Int(device:gxtkInput)
	Return bb_input2__1Set_1Input_1Device(device)
End Function