Mod Tool/Scripting

From OniGalore
< Mod Tool
Revision as of 13:25, 14 March 2015 by Paradox-01 (talk | contribs) (read registry with forced 64/32-bit path)
Jump to navigation Jump to search

General

Variables

Declaration

In VBS, any new variable is of type "variant".

dim MyVar

A variant's subtype (boolean, string, integer, ...) gets declared automatically when the variable is used the first time.

MyVar = 100

Variants can be declared in bulk, separated by comma.

dim MyVar, MyVar2, MyVar3

Variables can be used without declaring them. That's convenient for short scripts.

MyVar4 = 100
MyVar5 = "text"

As longer a script becomes as more likely typos can appear including in variables.

option explicit will force you to declare every variable but that will also make sure no misspelled variable can appear. You would see the error immediately.

option explicit

dim MyVar6
MyVar6 = true


Global variables

Variables inside a function or sub are local. A local variable can only be used of the sub or function where it was declared.

Any variable that gets defined outside a function or subroutine is a global variable. Global variables can be used by any sub or function in the file.


' global var 7
MyVar7 = 3 + 0.3
sub test
	' local var 8
	MyVar8 = 3.3
end sub
function test2
	' local var 9 and 10
	MyVar9  = 3
	MyVar10 = 4 + MyVar7
end function

In Softimage Public variable declaration doesn't work. It means variables can be global inside a script but not between multiple scripts.

At same time Softimage's substitute for these missing "public" variables are two commands: SetGlobal and GetGlobal.

SetGlobal "MyGloVar", "variable_value"
logmessage GetGlobal ("MyGloVar")

Further information are found over HERE.


Theoretically it should be possible to place all code on one script file but that screws the overview.

Other possibilities to pass values from one script to another:

a) a command (containing a function with at least one argument), needs many lines just for the setup
b) user data blob, preferably attached to the scene root, needs annoying checks to see if they already exist, however an advantage is that UDB can be saved inside *.xsi files
c) a Softimage environment item, the information can only be stored as a string
' set value
XSIUtils.Environment.Setitem "MyVar", "true"
' get value
logmessage XSIUtils.Environment("MyVar")

As the information is a string you need to convert it back to what it was meant originally e.g. with cBool and cInt. For more conversion see HERE


Arrays

An array is a variable that can contain multiple values, also named elements. VBS arrays are 0-based. For example MyArr(1) has 2 elements (one at index 0 and one at index 1).

' static array
Dim MyArr(1)
MyArr (0) = true
MyArr (1) = false

To create dynamic arrays (where the amount of elements can by changed) use redim at all times or Array declaration at the beginning. ReDim clears an array. Use preserve to keep the old values.

ReDim MyArr (1)
MyArr(0) = true
MyArr(1) = false

MyArr2   = Array("A","B")


ReDim Preserve MyArr (2)
ReDim Preserve MyArr2(2)


MyArr (2) = true
MyArr2(2) = "C"

[...]

Directories

logmessage XSIUtils.ResolvePath("$XSI_USERHOME/")
' directory to addons and exported resources
logmessage XSIUtils.ResolvePath("$XSI_HOME/")
' directory to a few imported core files that must be modified (Model.vbs, ModStartup.js, ...)

' example:
' INFO : C:\Users\Paradox-01\Autodesk\Softimage_2015\
' INFO : C:\Program Files\Autodesk\Softimage 2015\

' INFO : C:\Users\Paradox-01\Autodesk\Softimage_Mod_Tool_7.5\
' INFO : C:\Softimage\Softimage_Mod_Tool_7.5\
' this can be useful for default locations like when selecting a folder
DesktopPath = CreateObject("WScript.Shell").SpecialFolders("Desktop")
logmessage DesktopPath


Message box

' okay-only
msgbox "message", 0, "title"
' okay and cancel
MyVar = msgbox ("message", 1, "title")

if MyVar = 1 then
	logmessage "OK button clicked"
	logmessage MyVar ' = 1
else
	logmessage "Cancel button clicked"
	logmessage MyVar ' = 2
end if


Input box

MyVar = inputbox ("message", "title" , "pre-entered content")

if MyVar = false then
	logmessage "Cancel button clicked"
else
	logmessage "OK button clicked"
	logmessage MyVar
end if


Check executable version

' taking OniSplit as example
Set objFSO = CreateObject("Scripting.FileSystemObject")
logmessage objFSO.GetFileVersion("F:\Program Files (x86)\Oni\Edition\install\onisplit.exe")

' result looks like this:
' INFO : 0.9.59.0


Build an vbs executable

VbsEdit for scripting and compiling.png

Executable, app(lication), program. Whatever you call it, sometimes it might be necessary to compile the script into an actual program.

Even though vbs is a script language and not a programming language, it can be done.

VbsEdit is an editor to fulfill such task with ease.

Just goto File > Convert into Executable. Choose output path, 32/64-bit version and hit OK.


OS bitness

if GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'").AddressWidth = 64 then
	logmessage "64"
else
	logmessage "32"
end if

faster

Set WshShell = CreateObject("WScript.Shell")
if instr(WshShell.RegRead("HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\Identifier"), "64") > 0 then
	logmessage "64"
else
	logmessage "32"
end if


XSI/Softimage bitness, version and license

There are three possibilities to detect the program's bitness:

logmessage Platform
logmessage XSIUtils.ResolvePath("$XSI_CPU/")
logmessage XSIUtils.Is64BitOS

' output for 32-bit installation
' INFO : Win32
' INFO : nt-x86\
' INFO : False

' output for 64-bit installation
' INFO : Win64
' INFO : nt-x86-64\
' INFO : True


For program's version:

logmessage version
' examples:
' INFO : 13.0.114.0
' INFO : 7.5.203.0

For program's license:

logmessage license
' examples:
' INFO : Softimage
' INFO : Mod Tool

DAE files saved with XSI/Softimage contain license information.


Read registry

This reads the registry with forced 64/32-bit path (RegType). In this example Oni's install location gets revealed.

Set WshShell = CreateObject("WScript.Shell")
if instr(WshShell.RegRead("HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\Identifier"), "64") > 0 then
	OS_bitness = 64
else
	OS_bitness = 32
end if

Const HKEY_LOCAL_MACHINE = &H80000002

sPath = ReadRegStr (HKEY_LOCAL_MACHINE, _
	"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{B67333BB-1CF9-4EFD-A40B-E25B5CB4C8A7}}_is1", _
	"InstallLocation", _
	OS_bitness)
	
logmessage sPath



Function ReadRegStr (RootKey, Key, Value, RegType)
  Dim oCtx, oLocator, oReg, oInParams, oOutParams

  Set oCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
  oCtx.Add "__ProviderArchitecture", RegType

  Set oLocator = CreateObject("Wbemscripting.SWbemLocator")
  Set oReg = oLocator.ConnectServer("", "root\default", "", "", , , , oCtx).Get("StdRegProv")

  Set oInParams = oReg.Methods_("GetStringValue").InParameters
  oInParams.hDefKey = RootKey
  oInParams.sSubKeyName = Key
  oInParams.sValueName = Value

  Set oOutParams = oReg.ExecMethod_("GetStringValue", oInParams, , oCtx)

  ReadRegStr = oOutParams.sValue
End Function


Read file

Binary

scan_AKEV_file_table

sub scan_AKEV_file_table
	' ##############################################
	OniInputFile =  "H:\Oni\AE\GameDataFolder\level1_Final\AKEVEnvWarehouse.oni"
	' ##############################################
    
	Set OniInputFileStream = CreateObject("ADODB.Stream")
	OniInputFileStream.Type = 1
	OniInputFileStream.Open
	OniInputFileStream.LoadFromFile OniInputFile

	' ### read AKEV textures table offset and size
	ByteNum = 4
	' ##############################################
	TOffset = cLng("&H" & "28")
	' ##############################################
	OniInputFileStream.Position = TOffset
	BArr1 = OniInputFileStream.Read(ByteNum)

	ByteNum = 4
	' ##############################################
	TSize = cLng ("&H" & "2C")
	' ##############################################
	OniInputFileStream.Position = TSize
	BArr2 = OniInputFileStream.Read(ByteNum)


	' ### get AKEV textures table offset and size
	TOffsetHex = SimpleBinaryToString(BArr1)
	for i = ubound(TOffsetHex ) - 1 to 0 step -1
		newhex = newhex & hex(Asc(TOffsetHex(i)))
	next
	logmessage newhex
	logmessage "name table offset: " & cLng("&H" & newhex)
	TOffsetInt = cLng("&H" & newhex)
	newhex = ""
	
	TSizeHex = SimpleBinaryToString(BArr2)
	for i = ubound(TSizeHex) - 1 to 0 step -1
		newhex = newhex & hex(Asc(TSizeHex(i)))
	next
	logmessage newhex
	logmessage "name table size: " & cLng("&H" & newhex)
	TSizeInt = cLng("&H" & newhex) 
	
  	logmessage "------------------------------"
  	

	' ### read table content
	ByteNum = TSizeInt
	OniInputFileStream.Position = TOffsetInt
	BArr3 = OniInputFileStream.Read(ByteNum)
	TContent = SimpleBinaryToString(BArr3)
	
	' ### name grapper
	NG = ""
	for each n in TContent
		if not Asc(n) = 0 then
			NG = NG & n
		else
			'if instr(NG, "TXMP") = 1 then
				' write TXMP to array ?
				logmessage NG
			'end if
			NG = ""
		end if
	next
end sub

Function SimpleBinaryToString(Binary)
	ReDim tmpArr(LenB(Binary) - 1)
	For I = 1 To LenB(Binary)
		S = Chr(AscB(MidB(Binary, I, 1)))
		tmpArr(I - 1) = S
	Next
	SimpleBinaryToString = tmpArr
End Function

Output:

' INFO : 0E40
' INFO : name table offset: 3648
' INFO : 0A4A
' INFO : name table size: 2634
' INFO : ------------------------------
' INFO : AKEVEnvWarehouse
' INFO : AGDBEnvWarehouse
' INFO : TXMP_DOOR_FRAME
' INFO : TXMPNONE
' INFO : TXMPSUMI_1
' INFO : TXMPTC_CONTROL_01
' [...]
' INFO : TXMPWH_DCTRBND


Modify file

Binary

Before and after patching.

Change FilePath and in case of binary patching use function "StringToByteArray".

FilePath =  "C:\path_to\AKEVEnvWarehouse.oni"

' ### create stream objects
Set InputStream = CreateObject("ADODB.Stream")
InputStream.Type = 1
InputStream.Open
Set OutputStream = CreateObject("ADODB.Stream")
OutputStream.Type = 1
OutputStream.Open

' ### load input stream from file
InputStream.LoadFromFile FilePath

' ### copy first 16 signs of input stream to output stream
OutputStream.Write InputStream.Read(16)

' ### apply patch
' # ASCII patching
patch_data = "ABCD"
patch_data_length = len(patch_data)
'   patch_data_length = 4
InputStream.Position = InputStream.Position + patch_data_length
OutputStream.Write CreateObject("System.Text.ASCIIEncoding").GetBytes_4(patch_data)
' # binary patching
'OutputStream.Write StringToByteArray("41424344")

' ### re-add data that was cut off
OutputStream.Write InputStream.Read
   
' ### unloader
InputStream.Close
Set InputStream = Nothing
' ### modes: 2 = overwrite; 1 = dontoverwrite
' test: save to new file
' FilePath2 = "C:\path_to\AKEVEnvWarehouseB.oni"
OutputStream.SaveToFile FilePath, 2
OutputStream.Close
Set OutputStream = Nothing


Conversions

String -> byte array

function StringToByteArray(ThisString)
	for i = 1 To Len(ThisString) Step 2
		str = str & Chr("&h" & Mid(ThisString, i, 2))
	next

	Set stream = CreateObject("ADODB.Stream")
	With stream
		.Open
		.CharSet = "Windows-1252"
		.Type = 2
			' ### 2 = text
		.WriteText str
		.Position = 0
		.Type = 1
			' ### 1 = binary
		StringToByteArray = .Read
		.Close
	End With
end function

' ### usage
ByteArray = StringToByteArray(ThisString)


Byte array -> string

Function ByteArrayToString(Binary)
  'Antonin Foller, http://www.motobit.com
  'Optimized version of a simple BinaryToString algorithm.
  
  Dim cl1, cl2, cl3, pl1, pl2, pl3
  Dim L
  cl1 = 1
  cl2 = 1
  cl3 = 1
  L = LenB(Binary)
  
  Do While cl1<=L
    pl3 = pl3 & Chr(AscB(MidB(Binary,cl1,1)))
    cl1 = cl1 + 1
    cl3 = cl3 + 1
    If cl3>300 Then
      pl2 = pl2 & pl3
      pl3 = ""
      cl3 = 1
      cl2 = cl2 + 1
      If cl2>200 Then
        pl1 = pl1 & pl2
        pl2 = ""
        cl2 = 1
      End If
    End If
  Loop
  BinaryToString = pl1 & pl2 & pl3
End Function

' ### usage
MyString = ByteArrayToString(ByteArray)


Math

Euler rotation -> matrix

function cosn (n)
	cosn = cos(XSIMath.DegreesToRadians(n))
end function
function sinn (n)
	sinn = sin(XSIMath.DegreesToRadians(n))
end function

' ################
logmessage "input"
x = 60 : logmessage x
y = 60 : logmessage y
z = 60 : logmessage z

logmessage "##################"
logmessage "converted"

set RotMatX = XSIMath.CreateMatrix3(1, 0, 0, 0, cosn(x), sinn(x), 0, -sinn(x), cosn(x))
set RotMatY = XSIMath.CreateMatrix3(cosn(y), 0, -sinn(y), 0, 1, 0, sinn(y), 0, cosn(y))
set RotMatZ = XSIMath.CreateMatrix3(cosn(z), sinn(z), 0, -sinn(z), cosn(z), 0, 0, 0, 1)

RotMatZ.MulInPlace RotMatY
RotMatZ.MulInPlace RotMatX

for i=0 to 2
	for j=0 to 2
		logmessage RotMatZ (i, j)
	next
next 

' INFO : input
' INFO : 60
' INFO : 60
' INFO : 60
' INFO : ##################
' INFO : converted
' INFO : 0,25
' INFO : 0,808012701892219
' INFO : 0,53349364905389
' INFO : -0,433012701892219
' INFO : -0,399519052838329
' INFO : 0,808012701892219
' INFO : 0,866025403784439
' INFO : -0,433012701892219
' INFO : 0,25


Matrix -> Euler rotation

Function Atan2(y, x)
  If x > 0 Then
    Atan2 = Atn(y / x)
  ElseIf x < 0 Then
    Atan2 = Sgn(y) * (XSIMath.PI - Atn(Abs(y / x)))
  ElseIf y = 0 Then
    Atan2 = 0
  Else
    Atan2 = Sgn(y) * XSIMath.PI / 2
  End If
End Function


function ToEuler(M00, M10, M20, M21, M22)
            a = M00
            b = M10
            dim c, s, r

            if b = 0 then
                c = Sgn(a)
                s = 0
                r = Abs(a)

            elseif a = 0 then
                c = 0
                s = Sgn(b)
                r = Abs(b)
                
            elseif Abs(b) > Abs(a) then
                t = a / b
                u = Sgn(b) * Sqr(1 + t * t)
                s = 1 / u
                c = s * t
                r = b * u

            else
                t = b / a
                u = Sgn(a) * Sqr(1 + t * t)
                c = 1 / u
                s = c * t
                r = a * u

	    end if

            Z = -Atan2(s, c)
            Y = Atan2(M20, r)
            X = -Atan2(M21, M22)

	    X = XSIMath.RadiansToDegrees(X)
	    Y = XSIMath.RadiansToDegrees(Y)
	    Z = XSIMath.RadiansToDegrees(Z)

	    ToEuler = array(X, Y, Z)
end function

' ################################
set RotMat = XSIMath.CreateMatrix3( _
		0.25, 0.808012701892219, 0.53349364905389, _
		-0.433012701892219, -0.399519052838329, 0.808012701892219, _
		0.866025403784439, -0.433012701892219, 0.25 )


' convert matrix to euler rotation and store values to array
ReXYZ = ToEuler(RotMat(0,0), RotMat(1,0), RotMat(2,0), RotMat(2,1), RotMat(2,2))

logmessage "reconverted"
logmessage ReXYZ(0)
logmessage ReXYZ(1)
logmessage ReXYZ(2)

' INFO : 60
' INFO : 60
' INFO : 60


Euler rotation -> quaternion

dim x, y, z, dRotation, qRotation
x = 90
y = 0
z = 0

set dRotation = XSIMath.CreateRotation(XSIMath.DegreesToRadians(x), XSIMath.DegreesToRadians(y), XSIMath.DegreesToRadians(z)) 
set qRotation = XSIMath.CreateQuaternion() 

	dRotation.GetQuaternion (qRotation) 
	LogMessage qRotation.W
	LogMessage qRotation.X
	LogMessage qRotation.Y
	LogMessage qRotation.Z
	' INFO : 0,707106781186548
	' INFO : 0,707106781186547
	' INFO : 0
	' INFO : 0

' to calculate oni quaternions from euler rotations use this setup:
' 	LogMessage qRotation.X
'	LogMessage qRotation.Y
'	LogMessage qRotation.Z
'	LogMessage qRotation.W * -1


Quaternion -> Euler rotation

dim qW, qX, qY, qZ, qRotation, x, y, z

	qW = 0.707106781186548
	qX = 0.707106781186547
	qY = 0
	qZ = 0

set qRotation = XSIMath.CreateQuaternion (qW, qX , qY, qZ)

	qRotation.GetXYZAngleValues x, y, z
	logmessage XSIMath.RadiansToDegrees(x)
	logmessage XSIMath.RadiansToDegrees(y)
	logmessage XSIMath.RadiansToDegrees(z)
	' INFO : 89,9999999999999
	' INFO : 0
	' INFO : 0

' to calculate euler rotations from oni quaternions use this setup:
	'qX = ...
	'qY = ...
	'qZ = ...
	'qW = ... * -1
'set qRotation = XSIMath.CreateQuaternion (qW, qX, qY, qZ)


Softimage-specific stuff

Check selection mode

checkfilter
'use sub to make use of the exit command

sub checkfilter
	Select Case Selection.Filter.Name
	'caution: case-sensitive

	Case "object"
		logmessage "object mode"
	Case "Edge"
		logmessage "edge mode"
	Case "Vertex"
		logmessage "point mode"
	Case "Polygon"
		logmessage "polygon mode" 
	Case Else
		logmessage "unknown mode"
		exit sub
 	End Select
end sub

Property Page

Detect a PPG

' this check is used to decide to either build or open a PPG
set oRoot = ActiveProject.ActiveScene.Root
if typename(oRoot.Properties("my_PPG")) = "Nothing" then
	logmessage "couldn't find my_PPG"
else
	logmessage "found my_PPG"
end if


Disable PPG popups

' let's say a big amount of objects will be created and each object will open a PPG
' in that case for user convenience those PPG popups should be disabled
' disable PPG popup
Preferences.SetPreferenceValue "Interaction.autoinspect", false

' creates the cube mesh but no PPG will show up
CreatePrim "Cube", "MeshSurface"

' enable PPG popup again
Preferences.SetPreferenceValue "Interaction.autoinspect", true


Build a PPG

' general PPG setup
set oPSet = ActiveSceneRoot.AddProperty("CustomProperty", false, "my_PPG")
set oPPGLayout = oPSet.PPGLayout

' PPG content
' [...]

' open PPG
InspectObj oPSet


PPG content

Text input
oPSet.AddParameter3 "ParamName", siString
oPPGLayout.AddItem "ParamName", "Caption"


Integer input
oPSet.AddParameter3 "ParamName", siInt2, , , , false, 0
oPPGLayout.AddItem "ParamName", "Caption"


Droplist
oPSet.AddParameter3 "Team", siString, 0
aListTeams = Array(	"Konoko", 0, _
					"TCTF", 1, _
					"Syndicate", 2, _
					"Neutral", 3, _
					"SecurityGuard", 4, _
					"RogueKonoko", 5, _
					"Switzerland (is melee-immune)", 6, _
					"SyndicateAccessory", 7 )
oPPGLayout.AddEnumControl "Team", aListTeams, "", siControlCombo


Radio options
oPSet.AddParameter3 "Team", siString, 0
aListTeams = Array(	"Konoko", 0, _
					"TCTF", 1, _
					"Syndicate", 2, _
					"Neutral", 3, _
					"SecurityGuard", 4, _
					"RogueKonoko", 5, _
					"Switzerland (is melee-immune)", 6, _
					"SyndicateAccessory", 7 )
oPPGLayout.AddEnumControl "Team", aListTeams, "", siControlRadio


Checkbox
' create checkbox and remove key symboles by setting parameter "Animatable" to false or 0
' CustomProperty.AddParameter3( ScriptName, ValueType, [DefaultValue], [Min], [Max], [Animatable], [ReadOnly] )
oPSet.AddParameter3 "Check1", siBool, , , , 0, 0
oPPGLayout.AddItem "Check1", "Checkbox_caption"


Spacer
' AddSpacer( [Width], [Height] )
oPPGLayout.AddSpacer 25