diff --git a/doc/specs/bioware/2DA_Format.pdf b/doc/specs/bioware/2DA_Format.pdf deleted file mode 100644 index 80e4ec0adc..0000000000 Binary files a/doc/specs/bioware/2DA_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/AreaFile_Format.pdf b/doc/specs/bioware/AreaFile_Format.pdf deleted file mode 100644 index 2b418e86eb..0000000000 Binary files a/doc/specs/bioware/AreaFile_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/CommonGFFStructs.pdf b/doc/specs/bioware/CommonGFFStructs.pdf deleted file mode 100644 index 988650cc53..0000000000 Binary files a/doc/specs/bioware/CommonGFFStructs.pdf and /dev/null differ diff --git a/doc/specs/bioware/Conversation_Format.pdf b/doc/specs/bioware/Conversation_Format.pdf deleted file mode 100644 index 0d8951267d..0000000000 Binary files a/doc/specs/bioware/Conversation_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Creature_Format.pdf b/doc/specs/bioware/Creature_Format.pdf deleted file mode 100644 index 7581e770bf..0000000000 Binary files a/doc/specs/bioware/Creature_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/DoorPlaceableGFF.pdf b/doc/specs/bioware/DoorPlaceableGFF.pdf deleted file mode 100644 index 42a0b7e7da..0000000000 Binary files a/doc/specs/bioware/DoorPlaceableGFF.pdf and /dev/null differ diff --git a/doc/specs/bioware/ERF_Format.pdf b/doc/specs/bioware/ERF_Format.pdf deleted file mode 100644 index 8fcacb9056..0000000000 Binary files a/doc/specs/bioware/ERF_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Encounter_Format.pdf b/doc/specs/bioware/Encounter_Format.pdf deleted file mode 100644 index 66b056bae7..0000000000 Binary files a/doc/specs/bioware/Encounter_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Faction_Format.pdf b/doc/specs/bioware/Faction_Format.pdf deleted file mode 100644 index ff2a2614f0..0000000000 Binary files a/doc/specs/bioware/Faction_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/GFF_Format.pdf b/doc/specs/bioware/GFF_Format.pdf deleted file mode 100644 index 72abcbc0c7..0000000000 Binary files a/doc/specs/bioware/GFF_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/IFO_Format.pdf b/doc/specs/bioware/IFO_Format.pdf deleted file mode 100644 index 2eec39cb5f..0000000000 Binary files a/doc/specs/bioware/IFO_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Item_Format.pdf b/doc/specs/bioware/Item_Format.pdf deleted file mode 100644 index 7150b357d2..0000000000 Binary files a/doc/specs/bioware/Item_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Journal_Format.pdf b/doc/specs/bioware/Journal_Format.pdf deleted file mode 100644 index 1b5a4302cf..0000000000 Binary files a/doc/specs/bioware/Journal_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/KeyBIF_Format.pdf b/doc/specs/bioware/KeyBIF_Format.pdf deleted file mode 100644 index a3f9ffe513..0000000000 Binary files a/doc/specs/bioware/KeyBIF_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/LocalizedStrings_Format.pdf b/doc/specs/bioware/LocalizedStrings_Format.pdf deleted file mode 100644 index b7a1de28f6..0000000000 Binary files a/doc/specs/bioware/LocalizedStrings_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/PaletteITP_Format.pdf b/doc/specs/bioware/PaletteITP_Format.pdf deleted file mode 100644 index 8112ffa1fc..0000000000 Binary files a/doc/specs/bioware/PaletteITP_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/README b/doc/specs/bioware/README deleted file mode 100644 index c58cdcfee6..0000000000 --- a/doc/specs/bioware/README +++ /dev/null @@ -1,3 +0,0 @@ -BioWare's Aurora specs: - -Copyright of these files is held by BioWare corp. diff --git a/doc/specs/bioware/SSF_Format.pdf b/doc/specs/bioware/SSF_Format.pdf deleted file mode 100644 index a0af1b7292..0000000000 Binary files a/doc/specs/bioware/SSF_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/SoundObject_Format.pdf b/doc/specs/bioware/SoundObject_Format.pdf deleted file mode 100644 index 399d3ef759..0000000000 Binary files a/doc/specs/bioware/SoundObject_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Store_Format.pdf b/doc/specs/bioware/Store_Format.pdf deleted file mode 100644 index 310dc4a175..0000000000 Binary files a/doc/specs/bioware/Store_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/TalkTable_Format.pdf b/doc/specs/bioware/TalkTable_Format.pdf deleted file mode 100644 index e7e6d3e7ea..0000000000 Binary files a/doc/specs/bioware/TalkTable_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Trigger_Format.pdf b/doc/specs/bioware/Trigger_Format.pdf deleted file mode 100644 index 04e3527859..0000000000 Binary files a/doc/specs/bioware/Trigger_Format.pdf and /dev/null differ diff --git a/doc/specs/bioware/Waypoint_Format.pdf b/doc/specs/bioware/Waypoint_Format.pdf deleted file mode 100644 index 3ff53711a2..0000000000 Binary files a/doc/specs/bioware/Waypoint_Format.pdf and /dev/null differ diff --git a/doc/specs/gmax_nwn_mdl_0.3b.ms b/doc/specs/gmax_nwn_mdl_0.3b.ms deleted file mode 100644 index 26d7cee5bc..0000000000 --- a/doc/specs/gmax_nwn_mdl_0.3b.ms +++ /dev/null @@ -1,2422 +0,0 @@ --------------------------------------------------------------- --- -- --- NWN mdl Import/Export utility for GMax, v0.3b -- --- by Wayland (wreid@spectrumanalytic.com) -- --- -- --- (c) Wayland Reid -- --- Based on Zaddix's original WOK file importer maxscript -- -- --- Based on code by (c) Copyright Zaddix, 2002 -- --- -- --- Use this code in any way you see fit. -- --- If you use any/all of the code in your own scripts, -- --- just credit me and Zaddix somewhere. -- --- -- --------------------------------------------------------------- - -global lastDanglyName = undefined -global AnimRootNodeName = undefined -global LastAnimNumber = undefined -global LastAnimEventNumber = undefined - -------------- --- Structs -- -------------- -struct Tokenizer -( - tokens = #(), - - fn SetString str = - ( - str = trimright(trimleft(str)) - tokens = filterString str "\t ," - ), - - fn ReadToken = - ( - if tokens.count > 0 then - ( - local tok = tokens[1] - deleteItem tokens 1 - tok - ) - else - ( - undefined - ) - ), - - fn PeekToken = - ( - if tokens.count > 0 then tokens[1] else undefined - ), - - fn ReadPoint3 = - ( - local x = ReadToken() as float; - local y = ReadToken() as float; - local z = ReadToken() as float; - (point3 x y z) - ), - - fn ReadFloat = - ( - (ReadToken() as float) - ), - - fn ReadInteger = - ( - (ReadToken() as integer) - ), - - fn ReadString = - ( - (ReadToken()) - ), - - fn ReadColor = - ( - local r = 255 * (ReadToken() as float); - local g = 255 * (ReadToken() as float); - local b = 255 * (ReadToken() as float); - (color r g b) - ), - - fn ReadRemainingTokens = - ( - local str = "" - while (PeekToken() != undefined) do str += ReadToken() + " " - substring str 1 (str.count-1) - ) -) - -------------- ---- round --- -------------- - -fn round x= -( - local correction = 0 as integer - local tenth = ((mod x 1) * 10) as integer - if (tenth >= 5) then correction = 1 as integer - return (((x as integer) + correction) as integer) -) - ----------------------------- ---- Check if file exists --- ----------------------------- - -fn existFile fname = (getfiles fname).count != 0 - - -------------------------------- ---- Get Next Non-Blank line --- -------------------------------- - -fn getNextNonBlankLine fileHandle= -( - local line = "" - while ((line == "") and (not eof fileHandle)) do - ( - line = trimright (readDelimitedString fileHandle "\n") - ) - return line -) - ------------------------------------ --- UpdateMaterialEditor function -- ------------------------------------ - fn UpdateMaterialEditor MatLib= - ( - if MatLib.count > 0 then - ( - local curLibIndex = 1 - for matIndex in 1 to 24 do - ( - curMat = meditMaterials[matIndex] - if findString curMat.name " - Default" != 0 then - ( - -- Put the model material into the Material Editor - setMeditMaterial matIndex MatLib[curLibIndex] - -- Set the sample object type for this material to a square - setMTLMeditObjType MatLib[curLibIndex] 3 - curLibIndex = curLibIndex + 1 - if curLibIndex > MatLib.count then - ( - exit - ) -- end curLibIndex > MatLib.count check - ) -- end current material slot in editor is a standard material check - ) -- end materials in editor loop - ) -- end MatLib.count > 0 check - ) -- end UpdateMaterialEditor function - ------------------------------------ ---- Create NWN Surface Material --- ------------------------------------ - -fn createNWNWalkMaterial = -( - local SurfaceMatName = #() - local SurfaceMat = #() - local matName = "NWN_WalkMaterial"; - - local newMaterial = sceneMaterials[matName] - if (newMaterial == undefined) then - ( - append SurfaceMatName "Dirt" - append SurfaceMat brown -- Dirt - append SurfaceMatName "Obscuring" - append SurfaceMat white -- Obscuring - append SurfaceMatName "Grass" - append SurfaceMat (color 0 128 0) -- Grass - append SurfaceMatName "Stone" - append SurfaceMat gray -- Stone - append SurfaceMatName "Wood" - append SurfaceMat brown -- Wood - append SurfaceMatName "Water" - append SurfaceMat (color 0 0 192) -- Water - append SurfaceMatName "Nonwalk" - append SurfaceMat (color 180 0 255) -- Nonwalk - append SurfaceMatName "Transparent" - append SurfaceMat black -- Transparent - append SurfaceMatName "Carpet" - append SurfaceMat (color 198 200 0) -- Carpet - append SurfaceMatName "Metal" - append SurfaceMat black -- Metal - append SurfaceMatName "Puddles" - append SurfaceMat (color 0 128 128) -- Puddles - append SurfaceMatName "Swamp" - append SurfaceMat (color 0 64 0) -- Swamp - append SurfaceMatName "Mud" - append SurfaceMat (color 92 52 25) -- Mud - append SurfaceMatName "Leaves" - append SurfaceMat (color 0 192 0) -- Leaves - append SurfaceMatName "Lava" - append SurfaceMat (color 192 0 0) -- Lava - append SurfaceMatName "BottomlessPit" - append SurfaceMat (color 200 99 0) -- BottomlessPit - append SurfaceMatName "DeepWater" - append SurfaceMat (color 0 0 96) -- DeepWater - append SurfaceMatName "Door" - append SurfaceMat yellow -- Door - - newMaterial = multimaterial name:matName numsubs:SurfaceMatName.count - - for i in 1 to SurfaceMatName.count do - ( - newMaterial.names[i] = SurfaceMatName[i] - newMaterial.materialList[i].name = SurfaceMatName[i] - newMaterial.materialList[i].diffuse = SurfaceMat[i] - ) - ) - return newMaterial -) - - -fn addMDLSkin node buf= -( - setCommandPanelTaskMode mode:#modify - modPanel.setCurrentObject node.skin - - local weightStream = buf as stringstream; - local v = 0; - while (not eof weightStream) do - ( - v += 1; -- increment vertex - local tok = Tokenizer() - local weightLine = readDelimitedString weightStream "\n" - tok.SetString(weightLine) - while (tok.PeekToken() != undefined) do - ( - local i; - local b = 0; -- bone index - local boneName = tok.ReadString(); - local weight = tok.ReadFloat(); - for i = 1 to (skinops.GetNumberBones node.skin) do - ( - if ((skinops.GetBoneName node.skin i 0) == boneName) then b = i - ) - if (b == 0) then - ( - local boneNode = getNodeByName boneName - skinops.AddBone node.skin boneNode 0 - b = skinops.GetNumberBones node.skin - ) - local gmax_bug = node.numVerts - skinops.SetVertexWeights (node.skin) v b weight - ) - ) -) - -------------------------- --- ImportFile function -- -------------------------- - -fn ImportNWNmdl pFile importAnims showWarnings= -( - -- Model heirarchy defs - local MDL_DEF = 1 - local MDL_GEOM = 2 - local MDL_ANIM = 3 - local MDL_NOT_IN_MDL = 4 - - local currentPath = getFilenamePath pFile - - -- Open up the file and check to see if its a binary file - local binMdlStream = fopen pFile "rb" - local isNotBinary = ReadByte binMdlStream - fclose binMdlStream - - if (isNotBinary == 0) then - ( - MessageBox("Binary MDL Files not supported. Convert model to ASCII"); - return false - ) - - -- Open up the file - local mdlStream = openFile pFile mode:"r" - - local numAnims = 0 - local animGap = 60 - local lastFrame = 0 - local lineNum = 0 - local mdl_pos = MDL_DEF - local objProps = "" - local quitEarly = false - local in_aabb = false - - try ( - -- Go through the whole mdl file - while not (eof mdlStream) and not quitEarly do - ( - local tok = Tokenizer() - local line = getNextNonBlankLine mdlStream - tok.SetString (line) - lineNum += 1 - - -- Read the data ID tag - local idToken = tok.ReadToken() - if (idToken[1] == "#") then idToken = "#" - if (idToken == "donemodel") then - ( - mdl_pos = MDL_NOT_IN_MDL - quitEarly = true - ) - case mdl_pos of ( - MDL_DEF: - ( - case idToken of - ( - "#": () -- ignore comments - "newmodel": newModelName = tok.ReadString() - "setsupermodel": - ( - tok.ReadToken(); - objProps += "setsupermodel=" + tok.ReadRemainingTokens() + "\r\n" - ) - "beginmodelgeom": mdl_pos = MDL_GEOM - default: objProps += idToken + "=" + tok.ReadRemainingTokens() + "\r\n" - ) - ) - - MDL_GEOM: - ( - if (idToken == "aabb") then - ( - in_aabb = true - idToken = tok.ReadToken() - - ) - if (not (isKindOf idToken float) and - not (isKindOf idToken integer)) then in_aabb = false - if (in_aabb) then - ( - local x = idToken as float - local y = tok.ReadFloat() - local z = tok.ReadFloat() - local bl = point3 x y z - local tr = tok.ReadPoint3() - local leaf = (tok.ReadInteger() != -1) - bl *= 100 - tr *= 100 - local box_pos = (bl+tr)/2 - box_pos.z = bl.z - box_pos += objPos - if (leaf) then - ( - -- local b = box pos:box_pos length:(tr.y-bl.y) width:(tr.x-bl.x) height:(tr.z-bl.z) - ) - ) - case idToken of - ( - "#": () -- ignore comments - "node": -- Node Struct - ( - objType = tok.ReadToken() - objName = tok.ReadToken() - objWireColor = color 255 255 255 - objAmbient = color 0 0 0 - objDiffuse = color 0 0 255 - objSpecular = color 0 0 0 - objSelfIllum = color 0 0 0 - objAlpha = 100 - objShine = 0 - objPos = [0, 0, 0] - objRot = quat 0 0 0 0 - objScale = [1, 1, 1] - objMaterial = undefined - textureName = "" - - numVerts = 0 - verts = #() - numFaces = 0 - faces = #() - numTVerts = 0 - TVerts = #() - TVfaces = #() - smoothGroups = #() - surfacemats = #() - numConstraints = 0 - danglyConstraints = #() - numWeights = 0 - weightBuffer = "" - ) - - "parent": parent = tok.ReadToken() - "position": - ( - objPos = tok.ReadPoint3() - objPos *= 100 - ) - "orientation": - ( - local axis = tok.ReadPoint3() - local angle = RadToDeg (tok.ReadFloat()) - objRot = quat angle axis - ) - "scale": - ( - objScale = [1,1,1] * tok.ReadFloat() - ) - "wirecolor": objWireColor = tok.ReadColor() - "ambient": objAmbient = tok.ReadColor() - "diffuse": objDiffuse = tok.ReadColor() - "specular": objSpecular = tok.ReadColor() - "selfillumcolor": objSelfIllum = tok.ReadColor() - "alpha": objAlpha = 100 * tok.ReadFloat() - - "bitmap": - ( - textureName = tok.ReadToken() - if (textureName == "NULL") then textureName = "" - ) - "verts": -- Verticies Struct - ( - numVerts = tok.ReadInteger() - for i = 1 to numVerts do - ( - tok.SetString (getNextNonBlankLine mdlStream) - lineNum += 1 - local p3 = tok.ReadPoint3() - p3 *= 100 - append verts p3 - ) - ) - "constraints": -- Verticies Struct - ( - numConstraints = tok.ReadInteger() - for i = 1 to numConstraints do - ( - tok.SetString (getNextNonBlankLine mdlStream) - lineNum += 1 - local p1 = tok.ReadFloat() - append danglyConstraints p1 - ) - ) - "weights": -- Weights for skin modifier - ( - numWeights = tok.ReadInteger() - for i = 1 to numWeights do - ( - weightBuffer += (getNextNonBlankLine mdlStream) - weightBuffer += "\n" - ) - ) - "faces": -- Faces Struct - ( - local p1 - local p3 - numFaces = tok.ReadInteger() - for i = 1 to numFaces do - ( - tok.SetString (getNextNonBlankLine mdlStream) - lineNum += 1 - - -- Get the current Face - -- mdl file is zero-based index, max expects - -- one-based index into vert array - p3 = (tok.ReadPoint3() + [1,1,1]) - append faces p3 - - -- Get Smoothing Group - p1 = tok.ReadInteger() - append smoothGroups p1 - - -- Get the current TVface - p3 = (tok.ReadPoint3() + [1,1,1]) - append TVfaces p3 - - -- Get Surface Material - p1 = tok.ReadInteger() - append surfacemats p1 - ) - ) - - "tverts": -- Verticies Struct - ( - numTVerts = tok.ReadInteger() - for i = 1 to numTVerts do - ( - tok.SetString (getNextNonBlankLine mdlStream) - lineNum += 1 - local p3 = tok.ReadPoint3() - append TVerts p3 - ) - ) - - "endnode": -- Done parsing mesh, so create the object - ( - in_aabb = false - local newObj - case of - ( - ((objType == "trimesh") or - (objType == "danglymesh") or - (objType == "skin") or - (objType == "aabb")): - ( - -- Make material, if necessary - local matName = objName - if ((textureName != "") and - (textureName != "NULL") and - (textureName != undefined)) then matName = textureName - objMaterial = sceneMaterials[matName] - if (objType == "aabb") then - ( - objMaterial = createNWNWalkMaterial() - objProps = "" - ) - if (objMaterial == undefined) then - ( - objMaterial = standardmaterial name:matName - objMaterial.shaderType = 1 -- Blinn - objMaterial.ambient = objAmbient - objMaterial.diffuse = objDiffuse - objMaterial.specular = objSpecular - objMaterial.glossiness = objShine - objMaterial.selfillumcolor = objSelfIllum - objMaterial.opacity = objAlpha - if (textureName != "") then - ( - local tgaFilename = currentPath + textureName + ".tga" - local bmpFilename = currentPath + textureName + ".bmp" - if ((existFile tgaFilename) or (existFile bmpFilename)) then - ( - local bmp - if (existFile tgaFilename) then - bmp = openBitMap(tgaFilename) - else - bmp = openBitMap(bmpFilename) - local texMap = BitmapTexture bitmap:bmp name:textureName - objMaterial.mapAmounts[2] = 100 - objMaterial.maps[2] = texmap - objMaterial.mapEnables[2] = true; - showTextureMap objMaterial texmap true - - ) - else - ( - format "Missing texture file: %\r\n" textureName - if (showWarnings) then messagebox ("Missing texture file: " + textureName) - ) - ) - ) - - -- Make an editable mesh - local newObj = mesh name:objName vertices:verts faces:faces wirecolor:objWireColor material:objMaterial materialIDS:surfacemats - - -- Assign smoothing groups - for i = 1 to smoothGroups.count do - setFaceSmoothGroup newObj i smoothGroups[i] - - -- Add texture, if we have them - if numTVerts > 0 do - ( - setNumTVerts newObj numTVerts - for i = 1 to numTVerts do setTVert newObj i TVerts[i] - buildTVFaces newObj - for i = 1 to numFaces do setTVFace newObj i TVfaces[i] - ) - - if numConstraints > 0 then - ( - for i = 1 to numConstraints do - ( - local c = (color danglyConstraints[i] danglyConstraints[i] danglyConstraints[i]) - meshop.SetVertColor newObj 0 i c - ) - ) - - -- Update the mesh - update newObj - - ) --End trimesh case - - (objType == "emitter"): - ( - newObj = Cone_Angle name:objName - objRot += quat 180 (point3 1 0 0) -- NWN Emitters starts in +z, GMax cone manipulator starts in -z - newObj.Angle = 30; - newObj.Distance = 100; - ) - - (objType == "light"): - ( - newObj = omniLight radius:1000 name:objName - objProps = "" - ) - - default: - ( - newObj = dummy name:objName - newObj.boxsize = [5,5,5] - if (newModelName == objName) then - ( - newObj.boxsize = [100,100,5] - objProps += "mdl_root=true\r\n" - ) - ) - ) --End objType case - - local itsParent = for p in objects where p.name == parent collect p - if itsParent != undefined and itsParent.count > 0 then - ( - newObj.parent = itsParent[1] - newObj.scale.controller = bezier_scale() - newObj.position.controller = bezier_position() - newObj.rotation.controller = TCB_rotation() - in coordsys parent - ( - with animate on at time 0 newObj.scale = objScale - with animate on at time 0 newObj.rotation = objRot - with animate on at time 0 newObj.position = objPos - ) - ) - setUserPropBuffer newObj objProps - setUserProp newObj "node_type" objType - objProps = "" - - -- Skin modifier must be created after proper positioning. - if ((objType == "skin") and (numWeights == numVerts)) then - ( - -- Modifier must be applied here, rather than inside - -- the addMDLSkin finction where it should be. - -- I think its a gmax maxscript bug. - addmodifier newObj (skin()) - addMDLSkin newObj weightBuffer - ) - - ) -- End endnode case - - "endmodelgeom": -- End of model geometry - ( - mdl_pos = MDL_ANIM - if (not importAnims) then quitEarly = true; - ) - - default: - ( - objProps += idToken + "=" + tok.ReadRemainingTokens() + "\r\n" - ) - ) -- end iktoken case statement - ) -- End MDL_GEOM case - - MDL_ANIM: - ( - case idToken of - ( - "#": () -- ignore comments - - "newanim": -- New animation - ( - animName = tok.ReadToken() - animRoot = getNodeByName(tok.ReadToken()) - animLength = 0 - firstFrame = lastFrame+animGap - numAnims += 1 - numEvents = 0 - anim_property = "anim_name_" + (numAnims as string) - setUserProp animRoot anim_property animName - anim_property = "anim_firstframe_" + (numAnims as string) - setUserProp animRoot anim_property firstFrame - ) - "length": -- animtion length - ( - animLength = round((tok.ReadToken() as float) * frameRate) as integer - lastFrame = firstFrame + animLength - anim_property = "anim_lastframe_" + (numAnims as string) - setUserProp animRoot anim_property lastFrame - ) - "transtime": -- animtion length - ( - animTrans = (tok.ReadToken() as float) - anim_property = "anim_transtime_" + (numAnims as string) - setUserProp animRoot anim_property animTrans - ) - "event": -- animtion event - ( - numEvents += 1 - local anim_property = "anim_eventframe_" + (numAnims as string) + "_" + (numEvents as string) - local event_frame = round((tok.ReadFloat()*frameRate) + firstFrame) - setUserProp animRoot anim_property event_frame - - anim_property = "anim_eventname_" + (numAnims as string) + "_" + (numEvents as string) - local event_name = tok.ReadString() - setUserProp animRoot anim_property event_name - ) - "node": - ( - objType = tok.ReadString() - objName = tok.ReadString() - - local animObj = getNodeByName(objName) - if (animObj != undefined) then - ( - in coordsys parent - ( - local zeroPos = at time 0 animObj.pos - local zeroRot = at time 0 animObj.rotation - local keytime = 0 - keytime = (firstFrame - 1) - with animate on at time keytime animObj.pos = zeroPos - with animate on at time keytime animObj.rotation = zeroRot - newkey = getKeyIndex animObj.pos.controller keytime - animObj.pos.keys[newkey].inTangentType = #step - animObj.pos.keys[newkey].outTangentType = #step - newkey = getKeyIndex animObj.rotation.controller keytime - animObj.rotation.keys[newkey].continuity = 0 - - keytime = (lastFrame + 1) - with animate on at time keytime animObj.pos = zeroPos - with animate on at time keytime animObj.rotation = zeroRot - newkey = getKeyIndex animObj.pos.controller keytime - animObj.pos.keys[newkey].inTangentType = #step - animObj.pos.keys[newkey].outTangentType = #step - newkey = getKeyIndex animObj.rotation.controller keytime - animObj.rotation.keys[newkey].continuity = 0 - ) - ) - ) - "positionkey": - ( - local newkey - local animObj = getNodeByName(objName) - if (animObj != undefined) then - ( - local keyCount = undefined; - if (tok.PeekToken() != undefined) then keyCount = tok.ReadInteger() - local originalObjPos - in coordsys parent - ( - at time 0 originalObjPos = animObj.pos - tok.SetString (getNextNonBlankLine mdlStream) - while ((tok.PeekToken() != "endlist") and (keyCount != 0)) do - ( - lineNum += 1 - local keytime = round(firstFrame + (frameRate*tok.ReadFloat())) - local p3 = tok.ReadPoint3() - p3 *= 100 - with animate on at time keytime animObj.pos = p3 - newkey = getKeyIndex animObj.pos.controller keytime - animObj.pos.keys[newkey].inTangentType = #linear - animObj.pos.keys[newkey].outTangentType = #linear - tok.SetString (getNextNonBlankLine mdlStream) - if (keyCount != undefined) then keyCount -= 1 - ) - ) - ) - ) - "orientationkey": - ( - local keytime - local axis - local angle - local objRot - local newKey - local originalObjRot - local keyCount = undefined - if (tok.PeekToken() != undefined) then keyCount = tok.ReadInteger() - local animObj = getNodeByName(objName) - if (animObj != undefined) then - ( - in coordsys parent - ( - tok.SetString (getNextNonBlankLine mdlStream) - while ((tok.PeekToken() != "endlist") and (keyCount != 0)) do - ( - lineNum += 1 - keytime = round(firstFrame + (frameRate*tok.ReadFloat())) - axis = tok.ReadPoint3() - angle = RadToDeg (tok.ReadFloat()) - objRot = quat angle axis - local existingPosKey = getKeyIndex animObj.pos.controller keytime - local existingPos - if (existingPosKey > 0) then existingPos = animObj.pos.keys[existingPosKey].value - with animate on at time keytime animObj.rotation = objRot - newkey = getKeyIndex animObj.rotation.controller keytime - animObj.rotation.keys[newkey].continuity = 0 - if (existingPosKey == 0) then - ( - local newPosKey = getKeyIndex animObj.pos.controller keytime - if (newPosKey != 0) then deleteKey animObj.pos.controller newPosKey - ) - else - ( - animObj.pos.keys[existingPosKey].value = existingPos - ) - - tok.SetString (getNextNonBlankLine mdlStream) - if (keyCount != undefined) then keyCount -= 1 - ) - ) - ) - ) - "doneanim": () - default: () - ) -- end idtoken case - ) -- End MDL_ANIM case - ) -- end mdl_pos case statement - ) -- end while not eof - lastFrame += animGap - if (importAnims) then animationRange = (interval 0 lastFrame) - ) - catch - ( - --messageBox ("NWN importer Script Error reading mdl file at line:" + lineNum as string) - close mdlStream - throw - ) - close mdlStream -) - - ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ ---- ---- Export routines ---- ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ - - ------------------------- ---- WriteTrimeshNode --- ------------------------- - -fn WriteTrimeshNode node= -( - local isAABB = ((getUserProp node "node_type" == "aabb") or - (getUserProp node "node_type" == "pwk_aabb")) - - local isSkin = (node.modifiers["skin"] != undefined) - - if (isAABB) then - ( - format " diffuse 0.8000 0.8000 0.8000\r\n" - format " ambient 0.2000 0.2000 0.2000\r\n" - format " specular 0.0000 0.0000 0.0000\r\n" - format " render 0\r\n" - format " bitmap NULL\r\n" - ) - if ((node.material != undefined) and (not isAABB)) then - ( - local r = node.material.diffuse.r / 255.0 - local g = node.material.diffuse.g / 255.0 - local b = node.material.diffuse.b / 255.0 - format " diffuse % % %\r\n" r g b - - r = node.material.ambient.r / 255.0 - g = node.material.ambient.g / 255.0 - b = node.material.ambient.b / 255.0 - format " ambient % % %\r\n" r g b - - r = node.material.specular.r / 255.0 - g = node.material.specular.g / 255.0 - b = node.material.specular.b / 255.0 - format " specular % % %\r\n" r g b - - r = node.material.selfillumcolor.r / 255.0 - g = node.material.selfillumcolor.g / 255.0 - b = node.material.selfillumcolor.b / 255.0 - format " selfillumcolor % % %\r\n" r g b - - local a = node.material.opacity / 100.0 - format " alpha %\r\n" a - - if ((node.material.mapEnables[2]) and - (getUserProp node "nobitmap" != 1)) then - ( - local fullpath = node.material.maps[2].filename - local path_parts = filterString fullpath "\\" - local filename = path_parts[path_parts.count] - if ((findString filename ".") >= 1) then - filename = substring filename 1 ((findString filename ".")-1) - format " bitmap %\r\n" filename - ) - ) - - format " verts %\r\n" node.mesh.numverts - for i = 1 to node.mesh.numverts do - ( - in coordsys parent - ( - v = getvert node.mesh i - v *= 0.01 - ) - format " % % %\r\n" v.x v.y v.z - ) - - format " faces %\r\n" node.mesh.numfaces - for i = 1 to node.mesh.numfaces do - ( - v = getFace node.mesh i - smooth = getFaceSmoothGroup node.mesh i - matid = getFaceMatID node.mesh i - - local x = v.x as Integer - local y = v.y as Integer - local z = v.z as Integer - x -= 1 -- mdl file is zero-based index, max expects one-based index into vert array - y -= 1 -- - z -= 1 -- - format " % % % % " x y z smooth - - if (node.mesh.numtverts > 0) then - ( - tv = getTVFace node.mesh i - x = tv.x as Integer - y = tv.y as Integer - z = tv.z as Integer - x -= 1 -- mdl file is zero-based index, max expects one-based index into vert array - y -= 1 -- - z -= 1 -- - ) - else - ( - x = 0 - y = 0 - z = 0 - ) - format "% % % %\r\n" x y z matid - ) - - if (node.mesh.numtverts > 0) then - ( - format " tverts %\r\n" node.mesh.numtverts - for i = 1 to node.mesh.numtverts do - ( - v = gettvert node.mesh i - format " % % 0\r\n" v.x v.y - ) - ) - - if (getuserprop node "node_type" == "danglymesh") then - ( - format " danglymesh 1\r\n" - format "# Fake period, tightness and displacement parameters\r\n" - format " period 6.000000\r\n" - format " tightness 3.000000\r\n" - format " displacement 0.025000\r\n" - format " constraints %\r\n" node.numverts - for i = 1 to node.numverts do - ( - local c = GetVertColor node i - format " %\r\n" c.r - ) - ) - - if (isSkin) then - ( - setCommandPanelTaskMode mode:#modify - modPanel.setCurrentObject node.skin - - format " weights %\r\n" node.numVerts - local v - - local gmax_bug = node.numVerts - for v = 1 to (skinops.GetNumberVertices node.skin) do - ( - local b - format " " - for b = 1 to (skinops.getVertexWeightCount node.skin v) do - ( - local bone_id = skinops.getVertexWeightBoneID node.skin v b - local bone_name = skinops.getBoneName node.skin bone_id 0 - local weight = skinops.getVertexWeight node.skin v b - format "% % " bone_name weight - ) - if ((skinops.getVertexWeightCount node.skin v) == 0) then - ( - local bone_name = skinops.getBoneName node.skin 1 0 - format "% % " bone_name 0 - ) - format "\r\n" - ) - ) -) - - -------------------------- ---- BuildAABBTreeNode --- -------------------------- - -fn BuildAABBTreeNode node facemids facelist level= -( - if (facelist.count == 0) then return 0 - if (level > 10) then - ( - MessageBox("AABB Generation: Maximum recursion level reached. Check for duplicate verticies and/or faces.") - return 0 - ) - local bot_left = point3 100000.0 100000.0 100000.0 - local top_right = point3 -100000.0 -100000.0 -100000.0 - local midpoint_average = point3 0 0 0 - - for i=1 to facelist.count do - ( - local v3 = getface node.mesh facelist[i] - local p1 = getvert node.mesh v3.x - local p2 = getvert node.mesh v3.y - local p3 = getvert node.mesh v3.z - - if (p1.x < bot_left.x) then bot_left.x = p1.x - if (p1.y < bot_left.y) then bot_left.y = p1.y - if (p1.z < bot_left.z) then bot_left.z = p1.z - - if (p2.x < bot_left.x) then bot_left.x = p2.x - if (p2.y < bot_left.y) then bot_left.y = p2.y - if (p2.z < bot_left.z) then bot_left.z = p2.z - - if (p3.x < bot_left.x) then bot_left.x = p3.x - if (p3.y < bot_left.y) then bot_left.y = p3.y - if (p3.z < bot_left.z) then bot_left.z = p3.z - - if (p1.x > top_right.x) then top_right.x = p1.x - if (p1.y > top_right.y) then top_right.y = p1.y - if (p1.z > top_right.z) then top_right.z = p1.z - - if (p2.x > top_right.x) then top_right.x = p2.x - if (p2.y > top_right.y) then top_right.y = p2.y - if (p2.z > top_right.z) then top_right.z = p2.z - - if (p3.x > top_right.x) then top_right.x = p3.x - if (p3.y > top_right.y) then top_right.y = p3.y - if (p3.z > top_right.z) then top_right.z = p3.z - - midpoint_average += facemids[facelist[i]] - ) - - midpoint_average /= facelist.count - - local box_pos = (bot_left+top_right)/2 - --- format "DEBUG: % % \r\n" bot_left top_right - box_pos.z = bot_left.z - in coordsys parent ( box_pos += node.pos) - - local bl = bot_left / 100 - local tr = top_right / 100 - format " % % % % % %" bl.x bl.y bl.z tr.x tr.y tr.z - if (facelist.count == 1) then - ( --- display box --- local b = box pos:box_pos length:(top_right.y-bot_left.y) width:(top_right.x-bot_left.x) height:(top_right.z-bot_left.z) --- sphere pos:box_pos radius:10 - - format " %\r\n" (facelist[1]-1) - ) - else - ( - format " -1\r\n" - local bb_size = top_right - bot_left --- Axis 1=x 2=y 3=z - local axis = 1 - if (bb_size.y > bb_size.x) then axis = 2 - if (bb_size.z > bb_size.y) then axis = 3 - - -- Check exception case, where all points are coplanar with the axis-aligned split plane - local change_axis = true - for i=1 to facelist.count do - ( - p1 = facemids[facelist[i]] - case axis of - ( - 1: change_axis = change_axis and (p1.x == midpoint_average.x) - 2: change_axis = change_axis and (p1.y == midpoint_average.y) - 3: change_axis = change_axis and (p1.z == midpoint_average.z) - ) --- format "DEBUG: % % \r\n" p1 midpoint_average - ) - if (change_axis) then - ( - axis += 1 - if (axis > 3) then axis = 1 - ) - - local leftside = true - local good_split = false - local leftlist = #() - local rightlist = #() - while (not good_split) do - ( - leftlist = #() - rightlist = #() - for i=1 to facelist.count do - ( - p1 = facemids[facelist[i]] --- format "DEBUG: % % \r\n" p1 axis - case axis of - ( - 1: leftside = (p1.x < midpoint_average.x) - 2: leftside = (p1.y < midpoint_average.y) - 3: leftside = (p1.z < midpoint_average.z) - ) - if (leftside) then - append leftlist facelist[i] - else - append rightlist facelist[i] - ) - if ((leftlist.count > 0) and (rightlist.count >0)) then - ( - good_split = true - ) - else - ( - axis += 1 - if (axis > 3) then axis = 1 - ) - ) - - BuildAABBTreeNode node facemids leftlist (level+1) - BuildAABBTreeNode node facemids rightlist (level+1) - ) -) - - ---------------------- ---- WriteAABBTree --- ---------------------- - -fn WriteAABBTree node= -( - local facemids =#() - local facelist = #() - if ((mod node.mesh.numfaces 2) == 1) then - ( - MessageBox("Odd number of faces in AABB tree. Expect problems."); - ) - for i=1 to node.mesh.numfaces do - ( - v3 = getface node.mesh i - p1 = getvert node.mesh v3.x - p2 = getvert node.mesh v3.y - p3 = getvert node.mesh v3.z - mid = (p1+p2+p3) / 3 - append faceMids mid - append facelist i - ) - - format " aabb " -- no newline here, NWN chokes if the data doesn't start on the same line - BuildAABBTreeNode node faceMids facelist 0 -) - - ------------------------------- ---- WriteNWNRootProperties --- ------------------------------- - -fn WriteNWNRootProperties node= -( - local dep = getUserProp node "filedependancy" - if (dep == undefined) then dep = "Unknown" - format "filedependancy %\r\n" dep - format "newmodel %\r\n" node.name - format "setsupermodel % %\r\n" node.name (getUserProp node "setsupermodel") - format "classification %\r\n" (getUserProp node "classification") - format "setanimationscale %\r\n" (getUserProp node "setanimationscale") - format "beginmodelgeom %\r\n" node.name -) - -------------------------- ---- DumpNWNProperties --- -------------------------- - -fn DumpNWNProperties node= -( - local line - local val - local buffer = (getUserPropBuffer(node) + "\r\n") as stringstream - while (not eof buffer) do - ( - line = trimright (readline buffer) - if (line != "") then - ( - val = filterstring line "=" - val[1] = trimright(trimleft(val[1])) - if ((val[1] != "node_type") and - (val[1] != "mdl_root") and - (val[1] != "number_animations") and - (val[1] != "StartFrame") and - (val[1] != "EndFrame") and - (val[1] != "danglymesh") and - (val[1] != "nobitmap") and - ((findString val[1] "anim") != 1)) then - format " % %\r\n" val[1] val[2] - ) - ) -) - - --------------------- ---- ExportNWNmdl --- --------------------- - -fn ExportNWNmdl node only_nodetype children= -( - local isMesh = false - local isEmitter = false - - if (getUserProp node "node_type" == undefined) then - ( - setUserProp node "node_type" "dummy" - if (canConvertTo node Editable_Mesh) then setUserProp node "node_type" "trimesh" - if (classOf node == Cone_Angle) then setUserProp node "node_type" "emitter" - if (classOf(node.baseobject) == OmniLight) then setUserProp node "node_type" "light" - ) - if (node.modifiers["skin"] != undefined) then setUserProp node "node_type" "skin" - - local node_type = getuserprop node "node_type" - - if ((node_type == "trimesh") or - (node_type == "danglymesh") or - (node_type == "skin") or - (node_type == "aabb") or - (node_type == "pwk_aabb")) then isMesh = true - if (node_type == "emitter") then isEmitter = true - - if ((node_type == only_nodetype) or ((only_nodetype == "") and - (node_type != "pwk_aabb") and - (node_type != "pwk_use_dummy"))) then - ( - local node_name = node.name - local parent_name - if (node_type == "pwk_use_dummy") then - ( - node_type = "dummy" - node_name = (substring node.parent.name 5 30) + "_pwk_" + node.name - parent_name = node.parent.name - parent_name += "_pwk" - ) - if (node_type == "pwk_aabb") then - ( - node_type = "trimesh" - node_name = node.parent.name - node_name += "_wg" - parent_name = node.parent.name - parent_name += "_pwk" - ) - format "node % %\r\n" node_type node_name - if (node.parent != undefined) then - ( - if (parent_name == undefined) then parent_name = node.parent.name - format " parent %\r\n" parent_name - in coordsys parent - ( - local objScale = at time 0 node.scale; - local objPos = at time 0 node.pos; - local objRot = at time 0 node.rotation; - if (isEmitter) then objRot += quat -180 (point3 1 0 0) - local rotVector = normalize (point3 -objRot.x -objRot.y -objRot.z) - local angle = -degToRad(objRot.angle) - objPos *= 0.01 - - format " position % % %\r\n" objPos.x objPos.y objPos.z - format " orientation % % % %\r\n" rotVector.x rotVector.y rotVector.z angle - format " scale %\r\n" objScale.x - ) - DumpNWNProperties node - ) - else - ( - format " parent NULL\r\n" - ) - - if (isMesh) then WriteTrimeshNode(node) - if (node_type == "aabb") then WriteAABBTree node - if (node_type == "light") then - ( - format "# Fake light data\r\n" - format " ambientonly 0\r\n" - format " shadow 0\r\n" - format " isdynamic 0\r\n" - format " affectdynamic 1\r\n" - format " lightpriority 5\r\n" - format " fadingLight 1\r\n" - format " flareradius 0\r\n" - format " radius 14\r\n" - format " multiplier 1\r\n" - format " color 0 0 0 \r\n" - ) - format("endnode\r\n"); - ) - - local skinnodes = #() - if (children) then - ( - for c in node.children do - ( - if (c.modifiers["skin"] == undefined) then - ExportNWNmdl c only_nodetype children - else - append skinnodes c - ) - ) - - for c in skinnodes do ExportNWNmdl c only_nodetype children -) - ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ ---- ---- Animation export routines ---- ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ - -fn ExportAnimKeys node firstframe lastframe= -( - if (getUserProp node "node_type" == undefined) then - ( - setUserProp node "node_type" "dummy" - if (canConvertTo node Editable_Mesh) then setUserProp node "node_type" "trimesh" - if (classOf node == Cone_Angle) then setUserProp node "node_type" "emitter" - if (classOf(node.baseobject) == OmniLight) then setUserProp node "node_type" "light" - ) - local node_type = getuserprop node "node_type" - - format " node % %\r\n" node_type node.name - if (node.parent != undefined) then - format " parent %\r\n" node.parent.name - else - format " parent NULL\r\n" - - local t - local keycount = 0; - for t = firstframe to lastframe do - ( - if ((getKeyIndex node.pos.controller t) > 0) then - ( - if (keycount == 0) then format " positionkey\r\n" - keycount += 1; - at time t in coordsys parent objPos = node.pos * 0.01 - key_time = ((t-firstframe) as float) / (frameRate as float); - format " % % % %\r\n" key_time objPos.x objPos.y objPos.z - ) - ) - if (keycount > 0) then format " endlist\r\n" - - keycount = 0; - for t = firstframe to lastframe do - ( - if ((getKeyIndex node.rotation.controller t) > 0) then - ( - if (keycount == 0) then format " orientationkey\r\n" - keycount += 1; - local objRot - at time t in coordsys parent objRot = node.rotation; - local rotVector = normalize (point3 -objRot.x -objRot.y -objRot.z) - local angle = -degToRad(objRot.angle) - key_time = ((t-firstframe) as float) / (frameRate as float); - format " % % % % %\r\n" key_time rotVector.x rotVector.y rotVector.z angle - ) - ) - if (keycount > 0) then format " endlist\r\n" - - format(" endnode\r\n") - - for c in node.children do ExportAnimKeys c firstframe lastframe -) - -fn ExportAnimEvents node anim_number firstframe= -( - local user_props = getUserPropBuffer node as stringstream - local event_prefix = "anim_eventname_" + (anim_number as string) - while not eof user_props do - ( - local prop = readDelimitedString user_props "\n" - if ((findString prop (event_prefix + "_")) == 1) then - ( - local event_name = trimleft (trimright (substring prop ((findstring prop "=")+1) -1)) - if (event_name != "") then - ( - local event_number = trimright(substring prop ((event_prefix.count)+2) ((findstring prop "=")-(event_prefix.count)-2)) - local event_prop = "anim_eventframe_" + (anim_number as string) + "_" + (event_number as string) - event_time = (getUserProp node event_prop) as float - event_time = ((event_time - firstframe) as float) / (frameRate as float) - format " event % %\r\n" event_time event_name - ) - ) - ) -) - -fn ExportAnimByNumber node anim_number= -( - local name = getUserProp node ("anim_name_" + (anim_number as string)) - local firstframe = getUserProp node ("anim_firstframe_" + (anim_number as string)) - local lastframe = getUserProp node ("anim_lastframe_" + (anim_number as string)) - local transtime = getUserProp node ("anim_transtime_" + (anim_number as string)) - local anim_length = ((lastframe - firstframe) as float) / (frameRate as float); - format "\r\n" - format "newanim % %\r\n" name node.name - format " length %\r\n" anim_length - format " transtime %\r\n" transtime - format " animroot %\r\n" node.name - ExportAnimEvents node anim_number firstframe - ExportAnimKeys node firstframe lastframe - format "doneanim % %\r\n" name node.name - format "\r\n" -) - -fn ExportAnims node export_anim_name= -( - if (node == undefined) then return - local all_anims - all_anims = #() - local user_props = getUserPropBuffer node as stringstream - while not eof user_props do - ( - local prop = readDelimitedString user_props "\n" - if ((findString prop "anim_name") == 1) then - ( - local anim_name = trimleft (trimright (substring prop ((findstring prop "=")+1) -1)) - if (anim_name != "") then - ( - local anim_number = substring prop 11 ((findstring prop "=")-11) - anim_number = (trimright anim_number) as integer - if ((export_anim_name == "") or (export_anim_name == anim_name)) then - append all_anims anim_number - ) - ) - ) - for anim_number in all_anims do - ( - ExportAnimByNumber node anim_number - ) -) - -------------------------- ---- ShowDanglyConstraints --- -------------------------- - -fn ShowDanglyConstraints node onoff = -( - if (node != undefined) then - ( - if ((classof(node) == Editable_Mesh) and ((GetUserProp node "node_type") == "danglymesh")) then - node.showVertexColors = onoff - if (onoff) then - lastDanglyName = node.name - else - lastDanglyName = undefined - ) -) - ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ ---- ---- Rollout definitions ---- ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ - -rollout ImportRollout "Import MDL" width:160 height:192 -( - groupBox igrp4 "Filename" pos:[8,8] width:144 height:72 - editText import_filename_edit "" pos:[16,24] width:128 height:16 - button import_browse_button "Browse" pos:[72,48] width:70 height:24 - groupBox igrp5 "Options" pos:[8,80] width:144 height:64 - checkbox anims_checkbox "Import animations" pos:[16,104] width:128 height:16 - checkbox warn_checkbox "Show warnings" pos:[16,120] width:128 height:16 - button import_button "Import" pos:[32,152] width:104 height:24 - - on import_browse_button pressed do - ( - local filename = getOpenFileName caption:"Import mdl" types:"NWN mdl (*.mdl)|*.mdl|NWN ASCII Model (*.ascii)|*.ascii|All Files (*.*)|*.*|" - if (filename == undefined) then filename = "" - import_filename_edit.text = filename - ) - - on import_button pressed do - ( - --Open up a dialog box - -- local mdlFileName = "c:\\windows\\desktop\\nwn\\" + import_filename_edit.text - local mdlFileName = import_filename_edit.text - - if (not existFile mdlFileName) then - ( - MessageBox ("Model not imported. " + mdlFileName + " does not exist.") title:"File does not exist" - ) - else - ( - clearListener() - format "Importing %\r\n" mdlFileName - - ImportNWNmdl mdlFileName anims_checkbox.checked warn_checkbox.checked - - -- Redraw gmax viewports - clearSelection() - max tool zoomextents all - max views redraw - - format "Finished.\n\r\n" - ) - ) -) - -rollout ExportRollout "Export" width:160 height:104 rolledUp:true -( - button export_mdl_button "MDL" pos:[8,8] width:64 height:24 - button export_node_button "Node" pos:[88,8] width:64 height:24 - button export_wok_button "WOK" pos:[8,40] width:64 height:24 - button export_pwk_button "PWK" pos:[8,72] width:64 height:24 - dropDownList anim_combo "Animations to Export" pos:[8,112] width:144 height:40 - button export_anim_button "Anim" pos:[88,40] width:64 height:24 - -on export_mdl_button pressed do -( -local node = selection[1] -clearListener() -format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime -format "# mdl file\r\n" -format "#\r\n" -if ((getUserProp node "mdl_root") == true) then -( -WriteNWNRootProperties node -) -local anim_name = anim_combo.text -ExportNWNmdl node "" true -if ((getUserProp node "mdl_root") == true) then -( -format "endmodelgeom %\r\n" node.name -if (anim_name != "") then ExportAnims node anim_name -format "donemodel %\r\n" node.name -) -clearSelection() -select node -) -on export_mdl_button pressed do -( -local node = selection[1] -clearListener() -format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime -format "# mdl file\r\n" -format "#\r\n" -if ((getUserProp node "mdl_root") == true) then -( -WriteNWNRootProperties node -) -local anim_name = anim_combo.text -ExportNWNmdl node "" true -if ((getUserProp node "mdl_root") == true) then -( -format "endmodelgeom %\r\n" node.name -if (anim_name != "") then ExportAnims node anim_name -format "donemodel %\r\n" node.name -) -clearSelection() -select node -) -on export_mdl_button pressed do -( -local node = selection[1] -clearListener() -format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime -format "# mdl file\r\n" -format "#\r\n" -if ((getUserProp node "mdl_root") == true) then -( -WriteNWNRootProperties node -) -local anim_name = anim_combo.text -ExportNWNmdl node "" true -if ((getUserProp node "mdl_root") == true) then -( -format "endmodelgeom %\r\n" node.name -if (anim_name != "") then ExportAnims node anim_name -format "donemodel %\r\n" node.name -) -clearSelection() -select node -) - - - on export_node_button pressed do - ( - local node = selection[1] - clearListener() - format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime - format "# mdl file\r\n" - format "#\r\n" - ExportNWNmdl node "" false - clearSelection() - select node - ) - - on export_anim_button pressed do - ( - local node = selection[1] - clearListener() - format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime - format "# mdl file\r\n" - format "#\r\n" - local anim_name = anim_combo.text - if (anim_name != "") then ExportAnims node anim_name - clearSelection() - select node - ) - - on export_wok_button pressed do - ( - clearListener() - format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime - format "# wok file\r\n" - format "#\r\n" - if ((getUserProp selection[1] "mdl_root") == true) then - ( - format "beginwalkmeshgeom %\r\n" selection[1].name - ExportNWNmdl selection[1] "aabb" true - format "endwalkmeshgeom %\r\n" selection[1].name - ) - else - ( - format "beginwalkmeshgeom %\r\n" selection[1].parent.name - ExportNWNmdl selection[1] "aabb" true - format "endwalkmeshgeom %\r\n" selection[1].parent.name - ) - ) - - on export_pwk_button pressed do - ( - local node = selection[1] - if ((node.name.count < 7) or (node.name[4] != "_")) then - MessageBox("You should follow the placeable naming convention of \"xxx_xxx\" or \"xxx_xxxx\"") - clearListener() - format "# Exported from Wayland's NWN Maxscript v0.3b at %\r\n" localtime - format "# pwk file\r\n" - format "#\r\n" - ExportNWNmdl node "pwk_aabb" true - ExportNWNmdl node "pwk_use_dummy" true - ) - - on ExportRollout open do callbacks.addscript #selectionSetChanged "selChanged()" id:#waysNWNtool - on ExportRollout close do callbacks.removeScripts #selectionSetChanged id:#waysNWNtool -) - -rollout RootPropRollout "Model Properties" width:160 height:216 rolledUp:true -( - button make_root_button "Create \"Model Base\"" pos:[8,8] width:144 height:24 - groupBox rgrp3 "Model Properties" pos:[8,40] width:144 height:160 - editText supermodel_edit "" pos:[56,56] width:88 height:16 - label rlbl2 "Super" pos:[16,56] width:32 height:16 - radioButtons classification_radio "" pos:[56,80] width:69 height:96 labels:#("Character", "Tile", "Door", "FX", "Other") columns:1 - label rlbl3 "Class" pos:[16,80] width:32 height:16 - label rlbl4 "Animation Scale" pos:[16,166] width:80 height:15 - spinner anim_scale_spinner "" pos:[96,166] width:48 height:16 range:[0,100,0] scale:0.01 - - on make_root_button pressed do - ( - if (selection.count == 0) then - ( - local new_dummy = dummy name:"unnamed model" boxSize:[100,100,5] pos:[0,0,0] - select new_dummy - ) - selection[1].boxSize = [100,100,5] - setUserProp selection[1] "mdl_root" "true" - setUserProp selection[1] "setsupermodel" "NULL" - setUserProp selection[1] "classification" "Character" - setUserProp selection[1] "setanimationscale" "1.0" - -- - -- Work around until I can figure out how to get around this forward reference restriction - -- UpdateRootPropertiesUI selection[1] - local sm = getuserprop selection[1] "setsupermodel" - local mdl_class = getuserprop selection[1] "classification" - local anim_scale = getuserprop selection[1] "setanimationscale" - if (sm == undefined) then sm = "NULL" - if (mdl_class == undefined) then mdl_class = 1 - if (anim_scale == undefined) then anim_scale = 1 - RootPropRollout.make_root_button.enabled = false - RootPropRollout.supermodel_edit.enabled = true - RootPropRollout.supermodel_edit.text = sm - RootPropRollout.classification_radio.enabled = true - case (mdl_class) of - ( - "Character": RootPropRollout.classification_radio.state = 1 - "Tile": RootPropRollout.classification_radio.state = 2 - "Door": RootPropRollout.classification_radio.state = 3 - "FX": RootPropRollout.classification_radio.state = 4 - "Other": RootPropRollout.classification_radio.state = 5 - ) - RootPropRollout.anim_scale_spinner.enabled = true - RootPropRollout.anim_scale_spinner.value = anim_scale - select selection[1] - ) - on supermodel_edit entered newText do setUserProp selection[1] "setsupermodel" newText - on classification_radio changed newState do - ( - case newState of - ( - 1: setUserProp selection[1] "classification" "Character" - 2: setUserProp selection[1] "classification" "Tile" - 3: setUserProp selection[1] "classification" "Door" - 4: setUserProp selection[1] "classification" "FX" - 5: setUserProp selection[1] "classification" "Other" - ) - ) - on anim_scale_spinner changed newVal do - ( - setUserProp selection[1] "setanimationscale" newVal - ) -) - -rollout GeometryPropRollout "Node Properties" width:160 height:136 rolledUp:true -( - radioButtons geom_type_radio "" pos:[24,32] width:98 height:64 enabled:true labels:#("Trimesh", "Danglymesh", "AABB", "PWK walkmesh") - checkButton show_dangle_checkbutton "Danglymesh Constraints" pos:[8,440] width:144 height:24 - groupBox grp1 "Node Type" pos:[8,8] width:144 height:128 - groupBox grp2 "Render Options" pos:[8,144] width:144 height:96 - checkbox render_checkbox "Render" pos:[24,168] width:96 height:16 - checkbox shadow_checkbox "Shadow" pos:[24,184] width:96 height:16 - checkbox beaming_checkbox "Beaming" pos:[24,200] width:104 height:16 - groupBox grp3 "Tile Fade" pos:[8,248] width:144 height:96 - radioButtons tile_fade_radio "" pos:[24,272] width:109 height:64 labels:#("Don't Fade", "Fade", "Horizontal Blocker", "Vertical Blocker") - groupBox grp4 "Alpha Channel Options" pos:[8,352] width:144 height:80 - radioButtons alpha_radio "" pos:[24,376] width:112 height:48 labels:#("Don't Use", "Transparency Map", "Reflection Map") - radioButtons dummy_type_radio "" pos:[24,96] width:104 height:32 labels:#("Dummy", "PWK use dummy") - checkbox no_bitmap_checkbox "Don't export bitmap" pos:[24,216] width:120 height:16 - - on geom_type_radio changed newState do - ( - local node = selection[1] - case newState of - ( - 1: - ( - setUserProp node "node_type" "trimesh" - node.material = sceneMaterials[node.name] - ) - - 2: - ( - if (getUserProp node "danglymesh" != "1") then - ( - for i = 1 to node.mesh.numverts do - ( - meshop.setVertColor node.mesh 0 i (color 0 0 0) - ) - update node.mesh - ) - setUserProp node "node_type" "danglymesh" - setUserProp node "danglymesh" "1" - node.material = sceneMaterials[node.name] - ) - - 3: - ( - setUserProp node "node_type" "aabb" - node.material = createNWNWalkMaterial() - ) - 4: - ( - setUserProp node "node_type" "pwk_aabb" - node.material = createNWNWalkMaterial() - ) - ) - ) - - on dummy_type_radio changed newState do - ( - local node = selection[1] - node.boxsize = [5, 5, 5] - case newState of - ( - 1: - ( - setUserProp node "node_type" "dummy" - node.material = sceneMaterials[node.name] - ) - - 2: - ( - SetUserProp node "node_type" "pwk_use_dummy" - - local use_count = 1 - for i in objects do - ( - if (((findString i.name "use") == 1) and (i != node)) then use_count += 1 - ) - node.name = "use" - if (use_count < 10) then node.name += "0" - node.name += use_count as string - ) - ) - ) - - on render_checkbox changed curSetting do - ( - local node = selection[1] - if (curSetting) then - setUserProp node "render" "1" - else - setUserProp node "render" "0" - ) - - on shadow_checkbox changed curSetting do - ( - local node = selection[1] - if (curSetting) then - setUserProp node "shadow" "1" - else - setUserProp node "shadow" "0" - ) - - on beaming_checkbox changed curSetting do - ( - local node = selection[1] - if (curSetting) then - setUserProp node "beaming" "1" - else - setUserProp node "beaming" "0" - ) - - on no_bitmap_checkbox changed curSetting do - ( - local node = selection[1] - if (curSetting) then - setUserProp node "nobitmap" "1" - else - setUserProp node "nobitmap" "0" - ) - - on tile_fade_radio changed newState do - ( - local node = selection[1] - case newState of - ( - 1: setUserProp node "tilefade" "0" - 2: setUserProp node "tilefade" "1" - 3: setUserProp node "tilefade" "2" - 4: setUserProp node "tilefade" "4" - ) - ) - - on alpha_radio changed newState do - ( - local node = selection[1] - case newState of - ( - 1: - ( - setUserProp node "shininess" "0" - setUserProp node "transparencyhint" "0" - ) - 2: - ( - setUserProp node "shininess" "0" - setUserProp node "transparencyhint" "1" - ) - 3: - ( - setUserProp node "shininess" "1" - setUserProp node "transparencyhint" "0" - ) - ) - ) - - on show_dangle_checkbutton changed curSetting do - ( - ShowDanglyConstraints selection[1] curSetting - ) -) - -fn FindAnimByName lookup_name= -( - if ((lookup_name == undefined) or (AnimRootNodeName == undefined)) then return undefined - local node = getNodeByName(AnimRootNodeName) - if node == undefined then return undefined - - local user_props = getUserPropBuffer(selection[1]) as stringstream - while not eof user_props do - ( - local prop = readDelimitedString user_props "\n" - if ((findString prop "anim_name") == 1) then - ( - local anim_name = trimleft (trimright (substring prop ((findstring prop "=")+1) -1)) - if (anim_name == lookup_name) then - ( - local anim_number = substring prop 11 ((findstring prop "=")-11) - anim_number = trimright anim_number - return (anim_number as integer) - ) - ) - ) - return undefined -) - -fn FindFreeAnimNumber= -( - local i = 0 - local node = getNodeByName(AnimRootNodeName) - while (true) do - ( - i += 1 - local prop_name = "anim_name_" + (i as string) - local anim_name = getUserProp node prop_name - if ((anim_name == undefined) or (anim_name == "")) then return i - ) -) - -fn FindFreeEventNumber= -( - local i = 0 - local node = getNodeByName(AnimRootNodeName) - while (true) do - ( - i += 1 - local prop_name = "anim_eventname_" + (LastAnimNumber as string) + "_" + (i as string) - local event_name = getUserProp node prop_name - if ((event_name == undefined) or (event_name == "")) then return i - ) -) - -rollout AnimationPropRollout "Animations" width:160 height:488 rolledUp:true -( - listBox anim_list "" pos:[8,8] width:144 height:9 - groupBox grp1 "Properties" pos:[8,160] width:144 height:128 - button add_anim_button "Add" pos:[8,136] width:40 height:20 - button delete_anim_button "Delete" pos:[56,136] width:48 height:20 - button zoom_to_anim_button "Zoom" pos:[112,136] width:40 height:20 - editText anim_name "" pos:[56,184] width:88 height:16 - spinner first_frame "" pos:[80,208] width:64 height:16 range:[0,10000,0] type:#integer scale:1 - spinner last_frame "" pos:[80,232] width:64 height:16 range:[0,10000,0] type:#integer scale:1 - label lbl1 "First Frame:" pos:[16,208] width:56 height:16 - label lbl2 "Last Frame:" pos:[16,232] width:56 height:16 - label lbl3 "Name:" pos:[16,184] width:32 height:16 - spinner transition "" pos:[80,256] width:64 height:16 range:[0,10,0.01] type:#float scale:1 - label lbl4 "Transition" pos:[16,256] width:48 height:16 - groupBox grp2 "Events" pos:[8,296] width:144 height:192 - listBox event_list "" pos:[16,312] width:128 height:6 - editText event_name "" pos:[64,432] width:80 height:16 - spinner event_frame "" pos:[64,456] width:80 height:16 range:[0,10000,0] - label lbl5 "Name:" pos:[16,432] width:40 height:16 - label lbl6 "Frame:" pos:[16,456] width:40 height:16 - button event_add_button "Add" pos:[32,400] width:40 height:20 - button event_delete_button "Delete" pos:[80,400] width:48 height:20 - - fn LoadAnimEvents anim_number= - ( - event_list.items = #() - LastAnimEventNumber = undefined - local node = getNodeByName(AnimRootNodeName) - - local user_props = getUserPropBuffer(node) as stringstream - local event_name_array = #() - while not eof user_props do - ( - local prop = readDelimitedString user_props "\n" - local event_prefix = "anim_eventname_" + (anim_number as string) - if ((findString prop event_prefix) == 1) then - ( - local prop_tokens = filterString prop " =" - local prop_name_tokens = filterString prop_tokens[1] "_" - local event_number = (prop_name_tokens[4] as integer); - local prop_name - prop_name = "anim_eventname_" + (anim_number as string) + "_" + (event_number as string); - local event_name = getUserProp node prop_name - prop_name = "anim_eventframe_" + (anim_number as string) + "_" + (event_number as string); - local event_frame = getUserProp node prop_name - local event_info = (event_number as string) + ". " + event_name - append event_name_array event_info - ) - ) - AnimationPropRollout.event_list.items = event_name_array - AnimationPropRollout.event_list.selection = 0 - ) - - fn LoadSelectedAnimation= - ( - LastAnimNumber = FindAnimByName(AnimationPropRollout.anim_list.selected) - if (LastAnimNumber != undefined) then - ( - local node = getNodeByName(AnimRootNodeName) - local prop_name - prop_name = "anim_name_" + (LastAnimNumber as string) - AnimationPropRollout.anim_name.text = getUserProp node prop_name - prop_name = "anim_firstframe_" + (LastAnimNumber as string) - AnimationPropRollout.first_frame.value = getUserProp node prop_name - prop_name = "anim_lastframe_" + (LastAnimNumber as string) - AnimationPropRollout.last_frame.value = getUserProp node prop_name - prop_name = "anim_transtime_" + (LastAnimNumber as string) - AnimationPropRollout.transition.value = getUserProp node prop_name - LoadAnimEvents LastAnimNumber - ) - else - ( - AnimationPropRollout.anim_name.text = "" - AnimationPropRollout.first_frame.value = 0 - AnimationPropRollout.last_frame.value = 0 - AnimationPropRollout.transition.value = 0 - AnimationPropRollout.event_list.items = #() - AnimationPropRollout.event_name.text = "" - AnimationPropRollout.event_frame.value = 0 - ) - ) - - fn ZoomToAnim= - ( - local firstframe = AnimationPropRollout.first_frame.value - local lastframe = AnimationPropRollout.last_frame.value - if (firstframe < lastframe) then - ( - animationRange = (interval firstframe lastframe) - sliderTime = firstframe - ) - ) - - fn ChangeAnimProperty property new_val= - ( - if ((LastAnimNumber != undefined) and (AnimRootNodeName != undefined)) then - ( - local node = getNodeByName(AnimRootNodeName) - local prop_name = "anim_" + property + "_" + (LastAnimNumber as string) - setUserProp node prop_name new_val - ) - ) - - on anim_list selected list_idx do LoadSelectedAnimation() - on anim_list doubleClicked list_idx do ZoomToAnim() - - on add_anim_button pressed do - ( - local node = getNodeByName(AnimRootNodeName) - if (node != undefined) then - ( - local anim_number = FindFreeAnimNumber() - local anim_name = "untitled" + (anim_number as string) - local prop_name - prop_name = "anim_name_" + (anim_number as string) - setUserProp node prop_name anim_name - prop_name = "anim_firstframe_" + (anim_number as string) - setUserProp node prop_name 0 - prop_name = "anim_lastframe_" + (anim_number as string) - setUserProp node prop_name 1 - prop_name = "anim_transtime_" + (anim_number as string) - setUserProp node prop_name 0.25 - - local newlist = AnimationPropRollout.anim_list.items - append newlist anim_name - AnimationPropRollout.anim_list.items = newlist - AnimationPropRollout.anim_list.selection = newlist.count - LoadSelectedAnimation() - ) - ) - - on delete_anim_button pressed do - ( - local sel = AnimationPropRollout.anim_list.selection - if (sel > 0) then - ( - ChangeAnimProperty "name" "" - AnimationPropRollout.anim_list.selection = 0 - LoadSelectedAnimation() - - local newlist = AnimationPropRollout.anim_list.items - deleteItem newlist sel - AnimationPropRollout.anim_list.items = newlist - ) - ) - - on zoom_to_anim_button pressed do ZoomToAnim() - - on anim_name changed new_val do - ( - ChangeAnimProperty "name" new_val - if (AnimationPropRollout.anim_list.selection > 0) then - AnimationPropRollout.anim_list.selected = new_val - ) - - on first_frame changed new_val do ChangeAnimProperty "firstframe" new_val - on last_frame changed new_val do ChangeAnimProperty "lastframe" new_val - on transition changed new_val do ChangeAnimProperty "transition" new_val - - fn LoadSelectedAnimEvent= - ( - if (AnimationPropRollout.event_list.selection > 0) then - ( - LastAnimEventNumber = readDelimitedString (AnimationPropRollout.event_list.selected as stringstream) "." - local node = getNodeByName(AnimRootNodeName) - prop_name = "anim_eventname_" + (LastAnimNumber as string) + "_" + (LastAnimEventNumber as string) - AnimationPropRollout.event_name.text = getUserProp node prop_name - prop_name = "anim_eventframe_" + (LastAnimNumber as string) + "_" + (LastAnimEventNumber as string) - AnimationPropRollout.event_frame.value = getUserProp node prop_name - ) - else - ( - LastAnimEventNumber = undefined - AnimationPropRollout.event_name.text = "" - AnimationPropRollout.event_frame.value = 0 - ) - ) - - fn ChangeAnimEventProperty property new_val= - ( - if ((LastAnimNumber != undefined) and (LastAnimEventNumber != undefined)) then - ( - local node = getNodeByName(AnimRootNodeName) - local prop_name = "anim_" + property + "_" + (LastAnimNumber as string) + "_" + (LastAnimEventNumber as string) - setUserProp node prop_name new_val - ) - ) - - on add_event_button pressed do - ( - local node = getNodeByName(AnimRootNodeName) - if ((node != undefined) and (LastAnimNumber != undefined)) then - ( - local event_number = FindFreeEventNumber() - local event_info = (event_number as string) + ". untitled" - local prop_name - prop_name = "anim_eventname_" + (LastAnimNumber as string) + "_" + (event_number as string) - setUserProp node prop_name "untitled" - prop_name = "anim_eventframe_" + (LastAnimNumber as string) + "_" + (event_number as string) - setUserProp node prop_name 0 - - local newlist = AnimationPropRollout.event_list.items - append newlist event_info - AnimationPropRollout.event_list.items = newlist - AnimationPropRollout.event_list.selection = newlist.count - LoadSelectedAnimEvent() - ) - ) - - on delete_event_button pressed do - ( - local sel = AnimationPropRollout.event_list.selection - if (sel > 0) then - ( - ChangeAnimProperty "eventname" "" - AnimationPropRollout.event_list.selection = 0 - LoadSelectedAnimEvent() - - local newlist = AnimationPropRollout.event_list.items - deleteItem newlist sel - AnimationPropRollout.event_list.items = newlist - ) - ) - - on event_list selected list_idx do LoadSelectedAnimEvent() - on event_name changed new_val do - ( - ChangeAnimEventProperty "eventname" new_val - if (AnimationPropRollout.event_list.selection > 0) then - ( - local event_info = (LastAnimEventNumber as string) + ". " + new_val - AnimationPropRollout.event_list.selected = event_info - ) - ) - - on event_frame changed new_val do ChangeAnimEventProperty "eventframe" new_val -) - - -rollout AboutRollout "About" width:160 height:248 rolledUp:true -( - label lbl1 "NWN mdl import/export" pos:[8,8] width:144 height:16 - label lbl2 "By Wayland Reid" pos:[8,24] width:136 height:16 - listBox lbx1 "Special thanks to" pos:[8,48] width:144 height:8 items:#("Zaddix", "Mr X", "Hlubocky", "scooterpb", "Polyhedral") - label lbl4 "And anyone else whose posted there problems and solutions to the Bioware NWN forums." pos:[8,184] width:144 height:56 -) - ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ ---- ---- Acutal window creation ---- ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ - --- create the rollout window -if wayland_NWN_mdl_tool != undefined do -( - closerolloutfloater wayland_NWN_mdl_tool -) - -wayland_NWN_mdl_tool = newRolloutFloater "NWN MDL Tool" 189 480 0 100 -addRollout ImportRollout wayland_NWN_mdl_tool -addRollout ExportRollout wayland_NWN_mdl_tool -addRollout RootPropRollout wayland_NWN_mdl_tool -addRollout GeometryPropRollout wayland_NWN_mdl_tool -addRollout AnimationPropRollout wayland_NWN_mdl_tool -addRollout AboutRollout wayland_NWN_mdl_tool rolledUp:true - -if IsSceneRedrawDisabled()==true do enablesceneredraw() - ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ ---- ---- GUI updaters ---- ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ - -fn UpdateRootPropertiesUI node= -( - if ((node != undefined) and (GetUserProp selection[1] "mdl_root" == true)) then - ( - RootPropRollout.make_root_button.enabled = false - local sm = getuserprop node "setsupermodel" - local mdl_class = getuserprop node "classification" - local anim_scale = getuserprop node "setanimationscale" - if (sm == undefined) then sm = "NULL" - if (mdl_class == undefined) then mdl_class = 1 - if (anim_scale == undefined) then anim_scale = 1 - RootPropRollout.supermodel_edit.enabled = true - RootPropRollout.supermodel_edit.text = sm - RootPropRollout.classification_radio.enabled = true - case (mdl_class) of - ( - "Character": RootPropRollout.classification_radio.state = 1 - "Tile": RootPropRollout.classification_radio.state = 2 - "Door": RootPropRollout.classification_radio.state = 3 - "FX": RootPropRollout.classification_radio.state = 4 - "Other": RootPropRollout.classification_radio.state = 5 - ) - RootPropRollout.anim_scale_spinner.enabled = true - RootPropRollout.anim_scale_spinner.value = anim_scale - ) - else - ( - if ((node == undefined) or ((node.parent == undefined) and (IsKindOf node Dummy))) then - RootPropRollout.make_root_button.enabled = true - else - RootPropRollout.make_root_button.enabled = false - RootPropRollout.supermodel_edit.enabled = false - RootPropRollout.classification_radio.enabled = false - RootPropRollout.anim_scale_spinner.enabled = false - ) - -) - -fn UpdateGeomUI node= -( - if (lastDanglyName != undefined) then ShowDanglyConstraints (getNodeByName(lastDanglyName)) false - if ((node != undefined) and (canConvertTo node Editable_Mesh)) then - ( - ShowDanglyConstraints node GeometryPropRollout.show_dangle_checkbutton.checked - GeometryPropRollout.geom_type_radio.enabled = true - GeometryPropRollout.render_checkbox.enabled = true - GeometryPropRollout.shadow_checkbox.enabled = true - GeometryPropRollout.beaming_checkbox.enabled = true - GeometryPropRollout.no_bitmap_checkbox.enabled = true - GeometryPropRollout.tile_fade_radio.enabled = true - GeometryPropRollout.alpha_radio.enabled = true - case (getuserprop node "node_type") of - ( - "danglymesh": GeometryPropRollout.geom_type_radio.state = 2 - "aabb": GeometryPropRollout.geom_type_radio.state = 3 - "pwk_aabb": GeometryPropRollout.geom_type_radio.state = 4 - default: GeometryPropRollout.geom_type_radio.state = 1 - ) - GeometryPropRollout.render_checkbox.checked = (getuserprop node "render" == 1) - GeometryPropRollout.shadow_checkbox.checked = (getuserprop node "shadow" == 1) - GeometryPropRollout.beaming_checkbox.checked = (getuserprop node "beaming" == 1) - GeometryPropRollout.no_bitmap_checkbox.checked = (getuserprop node "nobitmap" == 1) - case (getuserprop node "tilefade") of - ( - 1: GeometryPropRollout.tile_fade_radio.state = 2 - 2: GeometryPropRollout.tile_fade_radio.state = 3 - 4: GeometryPropRollout.tile_fade_radio.state = 4 - default: GeometryPropRollout.tile_fade_radio.state = 1 - ) - case (getuserprop node "transparencyhint") of - ( - 1: GeometryPropRollout.alpha_radio.state = 2 - 0: - ( - case (getuserprop node "shininess") of - ( - 1: GeometryPropRollout.alpha_radio.state = 3 - default: GeometryPropRollout.alpha_radio.state = 1 - ) - ) - default: GeometryPropRollout.alpha_radio.state = 1 - ) - ) - else - ( - GeometryPropRollout.geom_type_radio.enabled = false - GeometryPropRollout.render_checkbox.enabled = false - GeometryPropRollout.shadow_checkbox.enabled = false - GeometryPropRollout.beaming_checkbox.enabled = false - GeometryPropRollout.no_bitmap_checkbox.enabled = false - GeometryPropRollout.tile_fade_radio.enabled = false - GeometryPropRollout.alpha_radio.enabled = false - ) - - if ((node != undefined) and (isKindOf node Dummy) and (getUserProp node "mdl_root" != true)) then - ( - GeometryPropRollout.dummy_type_radio.enabled = true - case (getuserprop node "node_type") of - ( - "pwk_use_dummy": GeometryPropRollout.geom_type_radio.state = 2 - default: GeometryPropRollout.geom_type_radio.state = 1 - ) - ) - else - ( - GeometryPropRollout.dummy_type_radio.enabled = false - ) - - -) - -fn UpdateExportUI node = -( - - ExportRollout.anim_combo.items = #() - if (node != undefined) then - ( - ExportRollout.export_node_button.enabled = true - if (getUserProp node "mdl_root" == true) then - ( - ExportRollout.export_mdl_button.enabled = true - ExportRollout.export_wok_button.enabled = true - ExportRollout.export_pwk_button.enabled = true - ExportRollout.export_anim_button.enabled = true - ExportRollout.anim_combo.enabled = true - - local user_props = getUserPropBuffer(selection[1]) as stringstream - local anim_name_array = #("", "") - while not eof user_props do - ( - local prop = readDelimitedString user_props "\n" - if ((findString prop "anim_name") == 1) then - ( - local anim_name = trimleft (trimright (substring prop ((findstring prop "=")+1) -1)) - if (anim_name != "") then append anim_name_array anim_name - ) - ) - ExportRollout.anim_combo.items = anim_name_array - ExportRollout.anim_combo.selection = 1 - ) - else - ( - ExportRollout.export_mdl_button.enabled = false - ExportRollout.export_wok_button.enabled = false - ExportRollout.export_pwk_button.enabled = false - ExportRollout.export_anim_button.enabled = false - ExportRollout.anim_combo.enabled = false - ) - ) - else - ( - ExportRollout.export_mdl_button.enabled = false - ExportRollout.export_wok_button.enabled = false - ExportRollout.export_pwk_button.enabled = false - ExportRollout.export_node_button.enabled = false - ExportRollout.export_anim_button.enabled = false - ExportRollout.anim_combo.enabled = false - ) -) - -fn UpdateAnimUI node= -( - if ((node != undefined) and (getUserProp node "mdl_root" == true)) then - ( - AnimRootNodeName = selection[1].name - AnimationPropRollout.anim_list.items = #() - AnimationPropRollout.event_list.items = #() - LastAnimNumber = undefined - LastAnimEventNumber = undefined - local user_props = getUserPropBuffer(selection[1]) as stringstream - local anim_name_array = #() - while not eof user_props do - ( - local prop = readDelimitedString user_props "\n" - if ((findString prop "anim_name") == 1) then - ( - local anim_name = trimleft (trimright (substring prop ((findstring prop "=")+1) -1)) - if (anim_name != "") then append anim_name_array anim_name - ) - ) - AnimationPropRollout.anim_list.items = anim_name_array - AnimationPropRollout.anim_list.selection = 0 - - AnimationPropRollout.anim_list.enabled = true - AnimationPropRollout.add_anim_button.enabled = true - AnimationPropRollout.delete_anim_button.enabled = true - AnimationPropRollout.zoom_to_anim_button.enabled = true - AnimationPropRollout.anim_name.enabled = true - AnimationPropRollout.first_frame.enabled = true - AnimationPropRollout.last_frame.enabled = true - AnimationPropRollout.transition.enabled = true - AnimationPropRollout.event_list.enabled = true - AnimationPropRollout.event_name.enabled = true - AnimationPropRollout.event_frame.enabled = true - AnimationPropRollout.event_add_button.enabled = true - AnimationPropRollout.event_delete_button.enabled = true - ) - else - ( - AnimRootNodeName = undefined - AnimationPropRollout.anim_list.items = #() - AnimationPropRollout.event_list.items = #() - - AnimationPropRollout.anim_list.enabled = false - AnimationPropRollout.add_anim_button.enabled = false - AnimationPropRollout.delete_anim_button.enabled = false - AnimationPropRollout.zoom_to_anim_button.enabled = false - AnimationPropRollout.anim_name.enabled = false - AnimationPropRollout.first_frame.enabled = false - AnimationPropRollout.last_frame.enabled = false - AnimationPropRollout.transition.enabled = false - AnimationPropRollout.event_list.enabled = false - AnimationPropRollout.event_name.enabled = false - AnimationPropRollout.event_frame.enabled = false - AnimationPropRollout.event_add_button.enabled = false - AnimationPropRollout.event_delete_button.enabled = false - ) -) - ------------------- ---- selChanged --- ------------------- - -fn selChanged = -( - UpdateRootPropertiesUI selection[1] - UpdateGeomUI selection[1] - UpdateExportUI selection[1] - UpdateAnimUI selection[1] -) - --- Call this function once to set all the UI elements properly -selChanged() diff --git a/doc/specs/kotor_mdl.html b/doc/specs/kotor_mdl.html deleted file mode 100644 index 896570dd1b..0000000000 --- a/doc/specs/kotor_mdl.html +++ /dev/null @@ -1,2174 +0,0 @@ - - - - - mdl info - - -Geometry header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
unknown0
-
4
-
long?
-
2
-
unknown
-
4
-
4
-
long?
-
3
-
model name
-
8
-
32
-
null terminated string
-
4
-
location of root node
-
40
-
4
-
long
-
5
-
number of nodes
-
44
-
4
-
long
-
6
-
unknown bytes
-
48
-
28
-

-
7
-
geometry type?
-
76
-
1
-
char
-
8
-
padding?
-
77
-
3
-
char
-
-Total length of geometry -header: 80 bytes
-
-Model header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
unknown
-
0
-
1
-
char
-
2
-
unknown bytes
-
1
-
2
-
char
-
3
-
unknown
-
3
-
1
-
char
-
4
-
unknown
-
4
-
4
-
long?
-
5
-
location of the start of -animation data
-
8
-
4
-
long
-
6
-
number of animations
-
12
-
4
-
long
-
7
-
duplicate of item 6
-
16
-
4
-
long
-
8
-
unknown
-
20
-
4
-
long?
-
9
-
bounding box min. x
-
24
-
4
-
float
-
10
-
bounding box min. y
-
28
-
4
-
float
-
11
-
bounding box min. z
-
32
-
4
-
float
-
12
-
bounding box max. x
-
36
-
4
-
float
-
13
-
bounding box max. y
-
40
-
4
-
float
-
14
-
bounding box max. z
-
44
-
4
-
float
-
15
-
radius
-
48
-
4
-
float
-
16
-
unknown
-
52
-
4
-
float
-
17
-
super model
-
56
-
32
-
null terminated string
-
-Total length of model -header: 88 bytes
-
-Names array header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
location of root node
-
0
-
4
-
long
-
2
-
unknown
-
4
-
4
-
long?
-
3
-
size of mdx data?
-
8
-
4
-
long
-
4
-
unknown
-
12
-
4
-
long?
-
5
-
location of names
-
16
-
4
-
long
-
6
-
number of names
-
20
-
4
-
long
-
7
-
duplicate of item 6
-
24
-
4
-
long
-
-Total length of names array -header: 28 bytes
-
-The names are stored in the -array one after the other seperated only by the null values.
-
-Animation header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
animation length
-
0
-
4
-
float
-
2
-
trans time
-
4
-
4
-
float
-
3
-
model name
-
8
-
32
-
null terminated string
-
4
-
location of events
-
40
-
4
-
long
-
5
-
number of events
-
44
-
4
-
long
-
6
-
duplicate of item 5
-
48
-
4
-
long
-
7
-
unknown bytes
-
52
-
4
-

-
-Total length of animation -header: 56 bytes
-
-Events structure:
-
- - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
activation time?
-
0
-
4
-
float
-
2
-
event
-
4
-
32
-
null terminated string
-
-Total length of 1 structure: 36 -bytes
-

-Node header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
node type
-
0
-
2
-
short
-
2
-
supernode
-
2
-
2
-
short
-
3
-
node number
-
4
-
2
-
short
-
4
-
unknown
-
6
-
2
-
short
-
5
-
unknown
-
8
-
4
-

-
6
-
location of parent node
-
12
-
4
-
long
-
7
-
position X (same value as -position controller)
-
16
-
4
-
float
-
8
-
position Y (same value as -position controller)20
-
4
-
float
9
-
position Z (same value as -position controller)24
-
4
-
float
10
-
rotation W (same value as -rotation controller)28
-
4
-
float
11
-
rotation X (same value as -rotation controller)32
-
4
-
float
12
-
rotation Y (same value as -rotation controller)36
-
4
-
float
13
-
rotation Z (same value as -rotation controller)40
-
4
-
float
14
-
location of the array of child -node locations
-
44
-
4
-
long
-
15
-
number of items in array in item -8
-
48
-
4
-
long
-
16
-
duplicate of item 9
-
52
-
4
-
long
-
17
-
location of the array of -controllers
-
56
-
4
-
long
-
18
-
number of items in array in item -11
-
60
-
4
-
long
-
19
-
duplicate of item 12
-
64
-
4
-
long
-
20
-
location of the array of -controller data
-
68
-
4
-
long
-
21
-
number of items in array in item -14
-
72
-
4
-
long
-
22
-
duplicate of item 15
-
76
-
4
-
long
-
-Total length of node header: -80 bytes
-
-The array of child node -locations is simply a list of long integers.
-
-controller structure:
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
controller type
-
0
-
4
-
long
-
2
-
unknown
-
4
-
2
-
short
-
3
-
number of rows of controller data
-
6
-
2
-
short
-
4
-
offset of first time key
-
8
-
2
-
short
-
5
-
offset of first data byte
-
10
-
2
-
short
-
6
-
columns of data
-
12
-
1
-
char
-
7
-
unknown
-
13
-
3
-
char
-
-Total length of controller -structure: 16 bytes
-

-controller data structure:
-Ok, the controller data structure is not fixed.  It has to be -figured out from the controller itself.
-
-Example:
-controller:    1) 8 -1 2 0 2 3
-                -   2) 20 -1 2 8 10 4
-
-data: 0 1 0 0 0 1 2 3 0 1 0 0 0 1 0 0 0 1
-
-dark green =  -controller 1 time keys, light -green = controller 1 data, dark -red = controller 2 time keys, light red = controller 2 data
-
-So, controller 1 is a position controller (8 = position).  It has -2 rows of data, first time key is at offset 0, first data byte is at -offset 2, the data is 3 values long.
-
Controller 2 is an -orientation -controller (20 = orientation).  It has 2 rows of data, first time -key is at -offset 8, first data byte is at offset 10, the data is 4 values long.
-
-Taking the data string and -formatting it according to the controller info gives:
-controller 1 data:
-
- - - - - - - - - - - - - - - - - - - - - -
time
-
x position
-
y position
-
z position
-
0
-
0
-
0
-
0
-
1
-
1
-
2
-
3
-
-
-controller 2 data:
-
- - - - - - - - - - - - - - - - - - - - - - - - -
time
-
x
-
y
-
z
-
w
-
0
-
0
-
0
-
0
-
1
-
1
-
0
-
0
-
0
-
1
-
-
-What the heck is that x,y,z,w for orientation?  That is a -quaternion.  For some models oreintation (i.e. rotation) is stored -as quaternions.  Too much for me to explain, Google it.
-
-
common mesh header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
unknown
-
0
-
4
-
long
-
2
-
unknown
-
4
-
4
-
long
-
3
-
location of the face array
-
8
-
4
-
long
-
4
-
number of faces in the face array
-
12
-
4
-
long
-
5
-
duplicate of item 4
-
16
-
4
-
long
-
6
-
bounding box min X
-
20
-
4
-
float
-
7
-
bounding box min Y24
-
4
-
float
8
-
bounding box min Z28
-
4
-
float
9
-
bounding box max X32
-
4
-
float
10
-
bounding box max Y
-
36
-
4
-
float
11
-
bounding box max Z
-
40
-
4
-
float
12
-
mesh radius
-
44
-
4
-
float
13
-
mesh points average X
-
48
-
4
-
float
14
-
mesh points average Y
-
52
-
4
-
float
15
-
mesh points average Z
-
56
-
4
-
float
16
-
diffuse red
-
60
-
4
-
float
17
-
diffuse green
-
64
-
4
-
float
18
-
diffuse blue
-
68
-
4
-
float
19
-
ambient red
-
72
-
4
-
float
20
-
ambient green
-
76
-
4
-
float
21
-
ambient blue
-
80
-
4
-
float
22
-
unknown
-
84
-
4
-
float?
23
-
name for texture map 1
-
88
-
32
-
null terminated string
-
24
-
name for texture map 2
-
120
-
32
-
null terminated string
25
-
unknown
-
152
-
24
-
???
-
26
-
location of number of verts
-
176
-
4
-
long
-
27
-
number of items in array 10 -(always 1)
-
180
-
4
-
long
-
28
-
duplicate of item 11
-
184
-
4
-
long
-
29
-
location of location of verts
-
188
-
4
-
long
-
30
-
number of items in array 13 -(always 1)
-
192
-
4
-
long
-
31
-
duplicate of item 14
-
196
-
4
-
long
-
32
-
location of unknown array
-
200
-
4
-
long
-
33
-
number of items in array 16 -(always 1)
-
204
-
4
-
long
-
34
-
duplicate of item 17
-
208
-
4
-
long
-
35
-
unknown (always -1)
-
212
-
4
-
long
-
36
-
unknown (always -1)
-
216
-
4
-
long
-
37
-
unknown (always 0)
-
220
-
4
-
long
-
38
-
unknown (always 3)
-
224
-
4
-
long
-
39
-
unknown (always 0)228
-
4
-
long
40
-
unknown (always 0)232
-
4
-
long
41
-
unknown
-
236
-
16
-
long?
-
42
-
size of 1 MDX structure
-
252
-
4
-
long
-
43
-
unknown (has something to do -with textures)
-
256
-
4
-
long
44
-
unknown (always 0)
-
260
-
4
-

-
45
-
offset to vertex normals in MDX -data in bytes (always 12)
-
264
-
4
-
long
46
-
unknown (always -1)
-
268
-
4
-
long
47
-
offset to UV coordinates in MDX -data in bytes (24 if present)
-
272
-
4
-
long
48
-
unknown (each value always -1)
-
276
-
28
-

-
49
-
number of vertices
-
304
-
2
-
short
-
50
-
number of textures
-
306
-
2
-
short
-
51
-
unknown
-
308
-
2
-
short
-
52
-
shadow flag (value of 256 = cast -shadow)
-
310
-
2
-
short
53
-
render flag (value of 256 = -render this node)312
-
2
-
short
54
-
unknown314
-
2
-
short
55
-
unknown316
-
8
-
long?
-
56
-
location of this nodes data in -mdx
-
324
-
4
-
long
-
57
-
location of vertex coordinates -array
-
328
-
4
-
long
-
-Total length of common mesh header: -332 bytes
-
-skin mesh header
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size (bytes)
-
data type
-
1
-
unknown
-
0
-
20
-
long?
-
2
-
location of bone map array
-
20
-
4
-
long
-
3
-
number of items in array 2
-
24
-
4
-
long
-
4
-
location of an unknown array
-
28
-
4
-
long
-
5
-
number of items in array 4
-
32
-
4
-
long
-
6
-
duplicate of item 5
-
36
-
4
-
long
-
7
-
location of an unknown array
-
40
-
4
-
long
-
8
-
number of items in array 7
-
44
-
4
-
long
-
9
-
duplicate of item 8
-
48
-
4
-
long
-
10
-
location of an unknown array
-
52
-
4
-
long
-
11
-
number of items in array 10
-
56
-
4
-
long
-
12
-
duplicate of item 11
-
60
-
4
-
long
-
13
-
list of nodes that can affect -verticies of this node (i.e. bones)
-
64
-
30
-
short
-
14
-
unknown
-
94
-
6
-
short?
-
-Total length of skin mesh header: -100 bytes
-
-Bone maps work like this:
--For each vertex in the MDX -there are 4 bone indexes and the corresponding bone weights
--You take the bone index from -the MDX and match it to an entry in the bone map array
--The entry number that matches -is the node number that affects the vertex
-
-Example:
-MDX data:  0.1 0.2 0.3 -0.4 0.5 0.6 0.7 0.8 0.5 -0.5 0 0 1 2 -1 -1
-
-Bone map array:
-0 => 1
-1 => -1
-2 => -1
-3 => 2
-
-The yellow numbers in the MDX -data are the weights.  The red numbers are the bone indicies. The -white numbers are coordinates, uv map coordinates, etc.
-
-We take bone index 1 from the -MDX and look for it in the bone map array.  We see that it is at -position 0 in the list.  This means that node 0 has a bone weight -of 0.5 when affecting the vertex described in the MDX data.  -Taking bone index 2 we see that it is at position 3 in the list.  -So, node 3 has a bone weight of 0.5 when affecting the vertex described -in the MDX data.  The remaining bone indexs in the MDX are -1, -meaning no other nodes affect this vertex.  The total of  the -bone weights for a vertex must equal 1.
-
-dangly mesh header
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
-
item
-
offset
-
size -(bytes)
-
data -type
-
1
-
location -of constraints
-
0
-
4
-
long
-
2
-
number -of items in array 1
-
4
-
4
-
long
-
3
-
duplicate -of item 2
-
8
-
4
-
long
-
4
-
displacement
-
12
-
4
-
float
-
5
-
tightness
-
16
-
4
-
float
-
6
-
period
-
20
-
4
-
float
-
7
-
unknown
-
24
-
4
-
long?
-
-Total length of dangly mesh header: -28 bytes - - diff --git a/doc/specs/nds_sdat.html b/doc/specs/nds_sdat.html deleted file mode 100644 index e3267e899e..0000000000 --- a/doc/specs/nds_sdat.html +++ /dev/null @@ -1,941 +0,0 @@ - - - -

Nitro Composer File (*.sdat) Specification

- -

Current status

-

This spec is far from completion. And it may contains error. Use it at your own risk. -

23 June 2007 SBNK update -

20 June 2007 SSEQ Events -

6 June 2007 SBNK + general update -

23 May 2007 First published -

For enquiries please contact me at "kiwi.ds AT gmail.com" - -

Acknowledgement

-Many thanks to the following persons whose works I have studied: -

Crystal - the author of CrystalTile2.exe

-

loveemu - the author of sseq2mid.exe, swave2wave.exe & strm2wave.exe

-

Nintendon - the author of ndssndext.exe

-

DJ Bouche - the author of sdattool.exe

-

VGMTrans - the author of VGMTrans.exe

- - - -
-

Tables of Contents

-
-

0. Introduction -

    -
  • 0.1 Terminology
  • -
  • 0.2 NDS Standard File Header
  • -

-

1. SDAT File Format -

    -
  • 1.1 Header
  • -
  • 1.2 Symbol Block
  • -
  • 1.3 Info Block
  • -
  • 1.4 FAT
  • -
  • 1.5 File Block
  • -

-

2. SSEQ File Format

-

3. SSAR File Format

-

4. SBNK File Format

-

5. SWAV File Format

-

6. SWAR File Format

-

7. STRM File Format

- - - - - -
-

0. Introduction

-
- -

"The DS SDK has all the tools in it to convert MIDI files to the DS format, and has text file templates to define the soundbanks." CptPiard from VGMix

- -

The Nitro Composer packs various types of sound files in a single file (*.sdat) for use in DS games. Not all games involve the Nitro Composer. But it seems that it is very popular for creation of DS game music.

- -

Inside the SDAT you will find: SSEQ (Sequence), SSAR (Sequence Archive), SBNK (Sound Bank), SWAR (Wave Archive), STRM (Stream).

- -

SSAR is a collection of SSEQ, while SWAR is a collection of SWAV.

- -

0.1 Terminology

-File format is explained in C-style struct declaration. -The following types of variable are used: -
-s8	1 byte	// signed char
-u8 	1 byte	// unsigned char
-s16	2 byte	// signed short
-u16	2 byte	// unsigned short
-s32	4 byte	// signed long
-u32	4 byte	// unsigned long
-
- -

0.2 NDS Standard File Header

-Many files (besides sound-related) found in DS game rom share this header structure: - -
-typedef struct tagNdsStdFile {
-	s8  type[4];	// i.e. 'SDAT' or 'SBNK' etc...
-	u32 magic;	// 0x0100feff or 0x0100fffe
-	u32 nFileSize;	// Size of this file ( include this structure )
-	u16 nSize;	// Size of this structure ( always 16 )
-	u16 nBlock;	// Number of Blocks
-} NDSSTDF;
-
- -

The magic value can be 0x0002feff or 0x0001feff for non sound-related files.

- - -
-

1. SDAT File Format

-
- -The file has the following structure: - -
-	--------------------------------
-	|            Header            |
-	--------------------------------
-	|         Symbol Block         |
-	--------------------------------
-	|          Info Block          |
-	--------------------------------
-	|  File Allocation Table (FAT) |
-	--------------------------------
-	|          File Block          |
-	--------------------------------
-
- -

1.1 Header

-The Header appears at offset 0 in the SDAT file. All offsets in this structure are absolute offsets. - -
-typedef struct tagSDATHeader
-{
-	struct tagNdsStdFile {
-		s8  type[4];   // 'SDAT'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize;
-		u16 nSize;
-		u16 nBlock;    // usually 4, but some have 3 only ( Symbol Block omitted )
-	} file;
-	u32 nSymbOffset;  	// offset of Symbol Block = 0x40
-	u32 nSymbSize;    	// size of Symbol Block
-	u32 nInfoOffset; 	// offset of Info Block
-	u32 nInfoSize;    	// size of Info Block
-	u32 nFatOffset;   	// offset of FAT
-	u32 nFatSize;     	// size of FAT
-	u32 nFileOffset; 	// offset of File Block
-	u32 nFileSize;   	// size of File Block
-	u8  reserved[16]; 	// unused, 0s
-} SDATHEADER;
-
- -

1.2 Symbol Block

-It appears at offset 0x40, right after the Header. It may be omitted. It contains the symbols (or "filenames") of each sound file in the SDAT file. All offsets are relative to this block's starting address (i.e. 0x40).
-NB. Some files doesn't have Symbol Block.
-NB. The value of nSize of Symbol Block may not be 32-bit aligned. However, the value of nSymbSize in Header is. - -
-typedef struct tagSDATSymbol
-{
-	char type[4];		// 'SYMB'
-	u32 nSize;		// size of this Symbol Block
-	u32 nRecOffset[8];	// offset of Records (note below)
-	u8  reserved[24];	// unused, 0s
-} SDATSYMB;
-
- - - -

1.2.1 Symbol Block - Record

-There are a total of 8 records in the Symbol Block. They are: - - - - - - - - - - - - - - -
Record No.Record NameDescription
0SEQSequence (for music)
1SEQARCSequence Archive (for sound effect)
2BANKSound Bank
3WAVEARCWave Archive
4PLAYER*Player (Group-related)
5GROUPGroup of SEQ/SEQARC/BANK/WAVEARC
6PLAYER2*Player2 (Stream-related)
7STRMStream
* Records 4 and 5 do not appear in SMAP file. A SMAP File is generated by the Nitro Composer listing all sound files in the SDAT file. An example can be found from <<Zoids Saga DS - Legend of Arcadia>>
-
-All offsets are relative to Symbol block's starting address (i.e. 0x40). Each record (except Record 1 "SEQARC") has the following structure: - -
-typedef struct tagSDATSymbolRec
-{
-	u32 nCount;		// No of entries in this record
-	u32 nEntryOffset[1];	// Array of offsets of each entry
-} SDATSYMBREC;
-
- -For Record 1 (SEQARC), it is a group which contains sub-records. The sub-record is of the same structure as SDATSYMBREC (above). Record 1 has the following structure: - -
-typedef struct tagSDATSymbolRec2
-{
-	u32 nCount;			// No of entries in this record
-	struct {
-		u32 nEntryOffset;	// Offset of this Group's symbol
-		u32 nSubRecOffset;	// Offset of the sub-record
-	} Group[1];			// Array of offsets of each entry
-} SDATSYMBREC2;
-
- -Below is an example to access these records: - -
-SDATSYMB    *symb;
-int         i, j;
-char        *szSymbol;
-...
-// access record 0 'SSEQ'
-SDATSYMBREC *symb_rec = (SDATSYMBREC *) ( (u8 *)symb + symb->RecOffset[0] );
-
-for ( i = 0; i < symb_rec->nCount; i++ )
-{
-	// print out the symbol
-	szSymbol = (char *) ( (u8 *)symb + symb_rec->nEntryOffset[i] );
-	printf( "%s\n", szSymbol );
-}
-...
-
-// access record 1 'SSAR'
-SDATSYMBREC2 symb_rec2 = (SDATSYMBREC *)( (u8  *)symb + symb->RecOffset[1] );
-
-for ( i = 0; i < symb_rec2->nCount; i++ )
-{
-	szSymbol = (char *) ( (u8 *)symb + symb_rec2->Group[ i ].nEntryOffset );
-	printf( "%s\n", szSymbol );
-
-	SDATSYMBREC *symb_subrec = (SDATSYMBREC *) ( (u8 *)symb + symb_rec2->Group[i].nSubRecOffset );
-	for ( j = 0; j < symb_subrec->nCount; j++ )
-	{
-		// print out sub record's symbols
-		szSymbol = (char *) ( (u8 *)symb + symb_subrec->nEntryOffset[i] );
-		printf( "%s\n", szSymbol );
-	}
-}
-
- -

1.2.2 Symbol Block - Entry

-EXCEPT for Record 1 "SEQARC", an Entry in the record is a null terminated string. This corresponds to the "filename" of a sound file in the SDAT file. -

-For Record 1 "SEQARC", since a SEQARC file is a collection of Sequence files, therefore this record contains a sub-record. And this sub-record contains the symbols ("filenames") of each of the archived SEQ files. - - -

1.3 Info Block

-The Info Block appears just after the Symbol Block. It contains some information of each sound file in the SDAT file. All offsets are relative to this block's starting address. - -
-typedef struct tagSDATInfo
-{
-	char type[4];           // 'INFO'
-	u32 nSize;             // size of this Info Block
-	u32 nRecOffset[8];     // offset of a Record
-	u8  reserved[24];       // unused, 0s
-} SDATINFO;
-
- -

1.3.1 Info Block - Record

-There are a total of 8 records in the Info Block. The Record Names in 1.2.1 above applies here as well. All offsets are relative to Info block's starting address. With modifications, the code example above could be used to access the Info records and entries. - -
-typedef struct tagSDATInfoRec
-{
-	u32 nCount;            // No of entries in this record
-	u32 nEntryOffset[1];   // array of offsets of each entry
-} SDATINFOREC;
-
- - -

1.3.2 Info Block - Entry

- -

Record 0 "SEQ" - The Info Entry for SEQ contains playback information.

-
-typedef struct tagSDATInfoSseq
-{
-	u16 fileID;	// for accessing this file
-	u16 unknown;
-	u16 bnk;	// Associated BANK
-	u8  vol;	// Volume
-	u8  cpr;
-	u8  ppr;
-	u8  ply;
-	u8  unknown[2];
-} SDATINFOSSEQ;
-
- -

-Record 1 "SEQARC" - -
-typedef struct tagSDATInfoSsar
-{
-	u16 fileID;
-	u16 unknown;
-} SDATINFOSSAR;
-
- -Remarks: no info is available for SEQARC files. The info of each archived SEQ is stored in that SEQARC file. - -

-Record 2 "BANK" - -
-typdef struct tagSDATInfoBank
-{
-	u16 fileID;
-	u16 unknown;
-	u16 wa[4];      // Associated WAVEARC. 0xffff if not in use
-}
-
- -Remarks: Each bank can links to up to 4 WAVEARC files. The wa[4] stores the WAVEARC entry number. - -

-Record 3 "WAVEARC" - -
-typedef struct tagSDATInfoSwar
-{
-	u16 fileID;
-	u16 unknown;
-} SDATINFOSwar;
-
- -Remarks: This is not a new structure. It is the same as SDATINFOSSAR above for Record 1. - -

-Record 4 "PLAYER" - -
-typedef struct tagSDATInfoPlayer
-{
-	u8  unknown;
-	u8  padding[3];
-	u32 unknown2;
-} SDATINFOPlayer;
-
- -Remarks: None - -

-Record 5 "GROUP" - -
-typedef struct tagSDATInfoPlayer
-{
-	u32 nCount;		// number of sub-records
-        struct {		// array of Group
-		u32 type;
-		u32 nEntry;
-	} Group[1];
-} SDATINFOPlayer;
-
- -

Remarks: SDATINFOPlayer::Group::type can be one of the following values. nEntry is the entry number in the relevant Record (SEQ/SEQARC/BANK/WAVEARC).

- - - - - - - - - -
ValueType
0x0700SEQ
0x0803SEQARC
0x0601BANK
0x0402WAVEARC
- - -

-Record 6 "PLAYER2" - -
-typedef struct SDATInfoPlayer2
-{
-	u8  nCount;
-	u8  v[16];		// 0xff if not in use
-	u8  reserved[7];	// padding, 0s
-} SDATINFOPLAYER2;
-
- -Remarks: The use is unknown. The first byte states how many of the v[16] is used (non 0xff). - -

-Record 7 "STRM" - -
-typedef struct SDATInfoStrm
-{
-	u16 fileID;		// for accessing the file
-	u16 unknown;
-	u8  vol;		// volume
-	u8  pri;
-	u8  ply;
-	u8  reserved[5];
-} SDATINFOSTRM;
-
- -Remarks: 'ply' means play?, 'pri' means priority? - -

1.4 FAT

-The FAT appears just after the Info Block. It contains the records of offset and size of each sound file in the SDAT file. - -
-typedef struct tagSDATFAT
-{
-	char type[4];		// 'FAT '
-	u32 nSize;		// size of the FAT
-	u32 nCount;		// Number of FAT records
-	SDATFATREC Rec[1];	// Arrays of FAT records
-} SDATFAT;
-
- -

1.4.1 FAT - Record

-It contains the offset and size of the sound file. All the offsets are relative to the SDAT Header structure's beginning address. - -
-typedef struct tagSDATFATREC
-{
-	u32 nOffset;		// offset of the sound file
-	u32 nSize;		// size of the Sound file
-	u32 reserved[2];	// always 0s, for storing data in runtime.
-} SDATFATREC;
-
- -

1.5 File Block

-The File Block is the last block and appears just after the FAT. It has a small header (the structure below) which contains the total size and number of sound of files. All the sound files are stored after this structure. - -
-typedef struct tagSDATFILE
-{
-	char type[4];  // 'FILE'
-	u32 nSize;    // size of this block
-	u32 nCount;   // Mumber of sound files
-	u32 reserved; // always 0
-} SDATFILE;
-
- - - - - -
-

2. SSEQ File Format

-
- -

SSEQ stands for "Sound Sequence". It is a converted MIDI sequence. Linked to a BANK for instruments.

- - -
-typedef struct tagSseq
-{
-	struct tagNdsStdFile {
-		char type[4];	// 'SSEQ'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize;	// Size of this SSEQ file
-		u16 nSize;	// Size of this structure = 16
-		u16 nBlock;	// Number of Blocks = 1
-	} file;
-	struct {
-		char type[4];		// 'DATA'
-		u32 nSize;		// Size of this structure = nFileSize - 16
-		u32 nDataOffset;	// Offset of the sequence data = 0x1c
-		u8  data[1];		// Arrays of sequence data
-	} data;
-} SSEQ;
-
- -

NB. For the details of the SSEQ file, please refer to loveemu's sseq2mid

- -

2.1 Description

-

The design of SSEQ is more programming-oriented while MIDI is hardware-oriented. In MIDI, to produce a sound, a Note-On event is sent to the midi-instrument and then after a certain time, a Note-Off is sent to stop the sound (though it is also acceptable to send a Note-On message with 0 velocity). -In SSEQ, a sound is produced by one event only which carries with data such as note, velocity and duration. So the SSEQ-sequencer knows exactly what and how to play and when to stop.

- -

A SSEQ can have at maximum 16 tracks, notes in the range of 0..127 (middle C is 60). Each quartet note has a fixed tick length of 48. Tempo in the range of 1..240 BPM (Default is 120). The SSEQ will not be played correctly if tempo higher than 240.

-

The SEQ player uses Arm7's Timer1 for timing. The Arm7's 4 Timers runs at 33MHz (approximately 2^25). The SEQ player sets Timer1 reload value to 2728, prescaler to F/64. So on about every 0.0052 sec (64 * 2728 / 33MHz) the SEQ Player will be notified ( 1 cycle ). As a quartet note has fixed tick value of 48, the highest tempo that SEQ Player can handle is 240 BPM ( 60 / (0.0052 * 48) ).

-

During each cycle, the SEQ player adds the tempo value to a variable. Then it checks if the value exceeds 240. If it does, the SEQ player subtracts 240 from the variable, and process the SSEQ file. Using this method, the playback is not very precise but the difference is too small to be noticed.

-

Take an example with tempo = 160 BPM, the SSEQ file is processed twice in 3 notifications.

- - - - - - - - - - -
cyclevariableaction
10Add 160
2160Add 160
3320Subtract 240, process once, add 160
4240Subtract 240, process once, add 160
5160Add 160
6320Subtract 240, process once, add 160
7240Subtract 240, process once, add 160
8160Add 160
- -

2.2 Events

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Status ByteParameterDescription
0xFE2 bytes
It indicates which tracks are used. Bit 0 for track 0, ... Bit 15 for track 15. If the bit is set, the corresponding track is used.
Indication begin of multitrack. Must be in the beginning of the first track to work. A series of event 0x93 follows.
0x934 bytes
1st byte is track number [0..15]
The other 3 bytes are the relative adress of track data. Add nDataOffset (usually 0x1C) to find out the absolute address.
SSEQ is similar to MIDI in that track data are stored one after one track. Unlike mod music.
0x00 .. 0x7fVelocity: 1 byte [0..127]
Duration: Variable Length
NOTE-ON. Duration is expressed in tick. 48 for quartet note. Usually it is NOT a multiple of 3.
0x80Duration: Variable LengthREST. It tells the SSEQ-sequencer to wait for a certain tick. Usually it is a multiple of 3.
0x81Bank & Program Number: Variable Lengthbits[0..7] is the program number, bits[8..14] is the bank number. Bank change is seldomly found, so usually bank 0 is used.
0x94Destination Address: 3 bytes (Add nDataOffset (usually 0x1C) to find out the absolute address.)JUMP. A jump must be backward. So that the song will loop forever. -
0x95Call Address: 3 bytes (Add nDataOffset (usually 0x1C) to find out the absolute address.)CALL. It's like a function call. The SSEQ-sequncer jumps to the address and starts playing at there, until it sees a RETURN event.
0xFDNONERETURN. The SSEQ will return to the caller's address + 4 (a Call event is 4 bytes in size).
0xA0 .. 0xBfSee loveemu's sseq2mid for more details.Some arithmetic operations / comparions. Affect how SSEQ is to be played.
0xC0Pan Value: 1 byte [0..127], middle is 64PAN
0xC1Volume Value: 1 byte [0..127]VOLUME
0xC2Master Volume Value: 1 byte [0..127]MASTER VOLUME
0xC3Value: 1 byte [0..64] (Add 64 to make it a MIDI value)TRANSPOSE (Channel Coarse Tuning)
0xC4Value: 1 bytePITCH BEND
0xC5Value: 1 bytePITCH BEND RANGE
0xC6Value: 1 byteTRACK PRIORITY
0xC7Value: 1 byte [0: Poly, 1: Mono]MONO/POLY
0xC8Value: 1 byte [0: Off, 1: On]TIE (unknown)
0xC9Value: 1 bytePORTAMENTO CONTROL
0xCAValue: 1 byte [0: Off, 1: On]MODULATION DEPTH
0xCBValue: 1 byteMODULATION SPEED
0xCCValue: 1 byte [0: Pitch, 1: Volume, 2: Pan]MODULATION TYPE
0xCDValue: 1 byteMODULATION RANGE
0xCEValue: 1 bytePORTAMENTO ON/OFF
0xCFTime: 1 bytePORTAMENTO TIME
0xD0Value: 1 byteATTACK RATE
0xD1Value: 1 byteDECAY RATE
0xD2Value: 1 byteSUSTAIN RATE
0xD3Value: 1 byteRELEASE RATE
0xD4Count: 1 byte (how many times to be looped)LOOP START MARKER
0xFCNONELOOP END MARKER
0xD5Value: 1 byteEXPRESSION
0xD6Value: 1 bytePRINT VARIABLE (unknown)
0xE0Value: 2 byteMODULATION DELAY
0xE1BPM: 2 byteTEMPO
0xE3Value: 2 byteSWEEP PITCH
0xFFNONEEOT: End Of Track
- -

- - - -
-

3. SSAR File Format

-
- - -SSAR stands for "(Sound) Sequence Archive". It is a collection of sequences (used mainly for sound effect). Therefore, each archived SSEQ is usually short, with one or two notes. - - -
-typedef struct tagSsarRec {
-	u32 nOffset;		// relative offset of the archived SEQ file, absolute offset = nOffset + SSAR::nDataOffset
-	u16 bnk;		// bank
-	u8  vol;		// volume
-	u8  cpr;		// channel pressure 
-	u8  ppr;		// polyphonic pressure
-	u8  ply;		// play
-	u8  reserved[2];
-} SSARREC;
-
-typedef struct tagSsar
-{
-	struct tagNdsStdFile {
-		char type[4];   // 'SSAR'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize; // Size of this SSAR file
-		u16 nSize;     // Size of this structure = 16
-		u16 nBlock;    // Number of Blocks = 1
-	} file;
-	struct {
-		char type[4];		// 'DATA'
-		u32 nSize;		// Size of this structure
-		u32 nDataOffset;	// Offset of data
-		u32 nCount;		// nCount * 12 + 32 = nDataOffset
-		SSARREC Rec[1];		// nCount of SSARREC
-	} data;
-} SSAR;
-
- -

NB. Archived SSEQ files are not stored in sequence (order). So Rec[0].nOffset may point to 0x100 but Rec[1].nOffset points to 0x40.

-

NB. Archived SSEQ files cannot be readily extracted from SSAR file because data in one SSEQ may 'call' data in other SSEQ.

- - - - -
-

4. SBNK File Format

-
- -

SBNK stands for "Sound Bank". A bank is linked to up to 4 SWAR files which contain the samples. It define the instruments by which a SSEQ sequence can use. You may imagine SSEQ + SBNK + SWAR are similar to module music created by trackers.

- -
-typedef struct tagSbnkInstrument
-{
-	u8  fRecord;	// can be either 0, 1..4, 16 or 17
-	u16 nOffset;	// absolute offset of the data in file
-	u8  reserved;	// must be zero
-} SBNKINS;
-
-typedef struct tagSbnk
-{
-	struct tagNdsStdFile {
-		char type[4];   // 'SBNK'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize;	// Size of this SBNK file
-		u16 nSize;	// Size of this structure = 16
-		u16 nBlock;	// Number of Blocks = 1
-	} file;
-	struct {
-		char type[4];		// 'DATA'
-		u32 nSize;		// Size of this structure
-		u32 reserved[8];	// reserved 0s, for use in runtime
-		u32 nCount;		// number of instrument
-		SBNKINS Ins[1];	
-	} data;
-} SBNK;
-
- -

So, after SBNK::data, there come SBNK::data::nCount of SBNKINS. After the last SBNKINS, there will be SBNK::data::nCount of instrument records. In each instrument records, we can find one or more wave/note definitions. - - - - -

4.1 Instrument Record

- -

If SBNKINS::fRecord = 0, it is empty. SBNKINS::nOffset will also = 0.

-

If SBNKINS::fRecord < 16, the record is a note/wave definition. I have seen values 1, 2 and 3. But it seems the value does not affect the wave/note definition that follows. Instrument record size is 16 bytes.

-
-	swav number 	2 bytes	// the swav used
-	swar number	2 bytes	// the swar used. NB. cross-reference to "1.3.2 Info Block - Entry, Record 2 BANK" 
-	note number	1 byte 	// 0..127
-	Attack Rate	1 byte	// 0..127
-	Decay Rate	1 byte	// 0..127
-	Sustain Level	1 byte	// 0..127
-	Release Rate	1 byte	// 0..127
-	Pan		1 byte	// 0..127, 64 = middle
-
-

If SBNKINS::fRecord = 16, the record is a range of note/wave definitions. The number of definitions = 'upper note' - 'lower note' + 1. The Instrument Record size is 2 + no. of definitions * 12 bytes.

-
-	lower note	1 byte 	// 0..127
-	upper note	1 byte 	// 0..127
-
-	unknown		2 bytes	// usually == 01 00
-	swav number 	2 bytes	// the swav used
-	swar number	2 bytes	// the swar used. 
-	note number	1 byte
-	Attack Rate	1 byte
-	Decay Rate	1 byte
-	Sustain Level	1 byte
-	Release Rate	1 byte
-	Pan		1 byte
-
-	...
-	...
-	...
-
-	unknown		2 bytes	// usually == 01 00
-	swav number 	2 bytes	// the swav used
-	swar number	2 bytes	// the swar used. 
-	note number	1 byte
-	Attack Rate	1 byte
-	Decay Rate	1 byte
-	Sustain Level	1 byte
-	Release Rate	1 byte
-	Pan		1 byte
-
-

For example, lower note = 30, upper note = 40, there will be 40 - 30 + 1 = 11 wave/note definitions.
-The first wave/note definition applies to note 30.
-The second wave/note definition applies to note 31.
-The third wave/note definition applies to note 32.
-...
-The eleventh wave/note definition applies to note 40.

- -

If SBNKINS::fRecord = 17, the record is a regional wave/note definition.

-
-	The first 8 bytes defines the regions. They divide the full note range [0..127] into several regions (max. is 8)
-	An example is:
-	25  35  45  55  65  127 0   0 (So there are 6 regions: 0..25, 26..35, 36..45, 46..55, 56..65, 66..127)
-	Another example:
-	50  59  66  83  127 0   0   0 (5 regions: 0..50, 51..59, 60..66, 67..84, 85..127)
-
-	Depending on the number of regions defined, the corresponding number of wave/note definitions follow:
-
-	unknown		2 bytes	// usually == 01 00
-	swav number 	2 bytes	// the swav used
-	swar number	2 bytes	// the swar used. 
-	note number	1 byte	
-	Attack Rate	1 byte
-	Decay Rate	1 byte
-	Sustain Level	1 byte
-	Release Rate	1 byte
-	Pan		1 byte
-	...
-	...
-
-	In the first example, for region 0..25, the first wave/note definition applies.
-	For region 26..35, the 2nc wave/note definition applies.
-	For region 36..45, the 3rd wave/note definition applies.
-	... 
-	For region 66..127, the 6th wave/note definition applies.
-
- - -

REMARKS: Unknown bytes before wave/defnition definition = 5, not 1 in -stage_04_bank.sbnk, stage_04.sdat, Rom No.1156

- - -

4.2 Articulation Data

-

The articulation data affects the playback of the SSEQ file. They are 'Attack Rate', 'Decay Rate', 'Sustain Level' and 'Release Rate' (all have a value in range [0..127])

- -
-amplitude (%)
-
-100% |    /\
-     |   /  \__________
-     |  /              \
-     | /                \
-0%   |/__________________\___ time (sec)
-
-
-

Imagine how the amplitude of a note varies from begin to the end.

-

The graph above shows the amplitude envelope when a note is sound. The y-axis is Amplitude, x-asix is time.

- -

Attack rate determines how fast the note reaches 100% amplitude. (See the first upward curve). Thus the highest value 127 means the sound reaches 100% amplitude in the shortest time; 0 means the longest time.

-

Decay rate determines how fast the amplitude decays to 0% amplitude. Of course the sound will not drop to 0% but stops at sustain level. (See the first downward curve). Thus the highest value 127 means the sound reachs the sustain level in the shortest time; 0 means the longest time.

-

Sustain level determines the amplitude at which the sound sustains. (See the horizonal part). Thus the highest value 127 means the sound sustains at 100% amplitude (no decay), while 0 means 0% (full decay).

-

Release rate determines how fast the amplitude drops from 100% to 0%. Not from sustain level to 0%. (See the second downward curve). The value has the same meaning as Decay rate.

- -

See this file for more details on how to interpret the articulation data. The raw data column is the transformed value used for calculation.

-

The SEQ Player treats 0 as the 100% amplitude value and -92544 (723*128) as the 0% amplitude value. The starting ampltitude is 0% (-92544).

- -

During the attack phase, in each cycle, the SSEQ Player calculates the new amplitude value: amplitude value = attack rate * amplitude value / 255. The attack phase stops when amplitude reaches 0.

-

The times column shows how many cycles are needed to reach 100% amplitude value.

-

The sec column shows the corresponding time needed to reach 100% amplitude value.

-

The scale column is the corresponding value to feed in DLS Bank.

- -

During the decay phase, in each cycle, the SSEQ Player calculates the new amplitude value: amplitude value = amplitude value - decay rate. Note the starting amplitude value is 0. The decay phase stops when amplitude reaches sustain level.

-

The other columns are self-explanatory.

- -
-

5. SWAV File Format

-
- - -

SWAV doesn't appear in SDAT. They may be found in the ROM elsewhere. They can also be readily extracted from a SWAR file (see below).

- - -
-// info about the sample
-typedef struct tagSwavInfo
-{
-	u8  nWaveType;		// 0 = PCM8, 1 = PCM16, 2 = (IMA-)ADPCM
-	u8  bLoop;		// Loop flag = TRUE|FALSE
-	u16 nSampleRate;	// Sampling Rate
-	u16 nTime;		// (ARM7_CLOCK / nSampleRate) [ARM7_CLOCK: 33.513982MHz / 2 = 1.6756991 E +7]
-	u16 nLoopOffset;	// Loop Offset (expressed in words (32-bits))
-	u32 nNonLoopLen;	// Non Loop Length (expressed in words (32-bits))
-} SWAVINFO;
-
-// Swav file format
-typedef struct tagSwav
-{
-	struct tagNdsStdFile {
-		char type[4];   // 'SWAV'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize;	// Size of this SWAV file
-		u16 nSize;	// Size of this structure = 16
-		u16 nBlock;	// Number of Blocks = 1
-	} file;
-	struct {
-		char type[4];	// 'DATA'
-		u32 nSize;	// Size of this structure
-		SWAVINFO info;	// info about the sample
-		u8  data[1];	// array of binary data
-	} data;
-} SWAV;
-
- - - -
-

6. SWAR File Format

-
- -SWAR stands for "(Sound) Wave Archive". It is a collection of mono wave (SWAV) samples only (which can be in either PCM8, PCM16 or ADPCM compression). - -
-typedef struct tagSwar
-{
-	struct tagNdsStdFile {
-		char type[4];   // 'SWAR'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize;	// Size of this SWAR file
-		u16 nSize;	// Size of this structure = 16
-		u16 nBlock;	// Number of Blocks = 1
-	} file;
-	struct {
-		char type[4];		// 'DATA'
-		u32 nSize;		// Size of this structure
-		u32 reserved[8];	// reserved 0s, for use in runtime
-		u32 nSample;		// Number of Samples 
-	} data;
-	u32 nOffset[1];	// array of offsets of samples
-} SWAR;
-
- -

NB. After the array of offsets, the binary samples follow. Each sample has a SWAVINFO structure before the sample data. Therefore, it is easy to make a SWAV from the samples in SWAR.

- - - - -
-

7. STRM File Format

-
- -STRM stands for "Stream". It is an individual mono/stereo wave file (PCM8, PCM16 or ADPCM). - -
-typedef struct tagSTRM
-{
-	struct tagNdsStdFile {
-		char type[4];   // 'STRM'
-		u32 magic;	// 0x0100feff
-		u32 nFileSize;	// Size of this STRM file
-		u16 nSize;	// Size of this structure = 16
-		u16 nBlock;	// Number of Blocks = 2
-	} file;
-	struct {
-		char type[4];		// 'HEAD'
-		u32 nSize;		// Size of this structure
-		u8  nWaveType;		// 0 = PCM8, 1 = PCM16, 2 = (IMA-)ADPCM
-		u8  bLoop;		// Loop flag = TRUE|FALSE
-		u8  nChannel;		// Channels
-		u8  unknown;		// always 0
-		u16 nSampleRate;	// Sampling Rate (perhaps resampled from the original) 
-		u16 nTime;		// (1.0 / rate * ARM7_CLOCK / 32) [ARM7_CLOCK: 33.513982MHz / 2 = 1.6756991e7]
-		u32 nLoopOffset;	// Loop Offset (samples) 
-		u32 nSample;		// Number of Samples 
-		u32 nDataOffset;	// Data Offset (always 68h)
-		u32 nBlock;		// Number of Blocks 
-		u32 nBlockLen;		// Block Length (Per Channel) 
-		u32 nBlockSample;	// Samples Per Block (Per Channel)
-		u32 nLastBlockLen;	// Last Block Length (Per Channel)
-		u32 nLastBlockSample;	// Samples Per Last Block (Per Channel)
-		u8  reserved[32];	// always 0
-	} head;
-	struct {
-		char type[4];		// 'DATA'
-		u32 nSize;		// Size of this structure
-		u8  data[1];		// Arrays of wave data
-	} data;
-} SDATSTRM;
-
- -

7.1 Wave Data

- -

A Block is the same as SWAV Wave Data.

- -

Mono (SWAV)

-Block 1
-Block 2
-...
-Block N (Last Block)
- - -

Stereo (STRM)

-Block 1 L
-Block 1 R
-Block 2 L
-Block 2 R
-...
-Block N L (Last Block)
- -Block N R (Last Block) - - - - \ No newline at end of file diff --git a/doc/specs/torlack/README b/doc/specs/torlack/README deleted file mode 100644 index 27552ee453..0000000000 --- a/doc/specs/torlack/README +++ /dev/null @@ -1 +0,0 @@ -Torlack's reversed specs, used to be available at diff --git a/doc/specs/torlack/basics.html b/doc/specs/torlack/basics.html deleted file mode 100644 index 9c39ccd939..0000000000 --- a/doc/specs/torlack/basics.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - -NWN Data File Basics - - - - -

NWN Data File Basics

- -

It isn't too shocking that a game with the complexity of Neverwinter Nights -(NWN) requires a large assortment of different data files. These files -define everything from the basic mechanics of the game all the way to the videos -played during the introduction.

-

Assuming that you installed the game in the default directories, the data -files are spread out in the following directories.

-
    -
  • C:\NeverwinterNights\NWN - Game executables and key data files
  • -
  • C:\NeverwinterNights\NWN\ambient - Ambient sounds
  • -
  • C:\NeverwinterNights\NWN\data - Primary data files
  • -
  • C:\NeverwinterNights\NWN\dmvault - Dungeon master characters
  • -
  • C:\NeverwinterNights\NWN\hak - Hak packs to augment game data
  • -
  • C:\NeverwinterNights\NWN\localvault - Your local characters
  • -
  • C:\NeverwinterNights\NWN\modules - User created modules
  • -
  • C:\NeverwinterNights\NWN\movies - Cut scene and introduction movies
  • -
  • C:\NeverwinterNights\NWN\music - Game music files
  • -
  • C:\NeverwinterNights\NWN\nwm - NWN game modules
  • -
  • C:\NeverwinterNights\NWN\override - Collection of resources that replace - resources contained in the "data" directory.
  • -
  • C:\NeverwinterNights\NWN\saves - Saved games
  • -
  • C:\NeverwinterNights\NWN\servervault - Server characters for multiplayer - games run on your computer
  • -
  • C:\NeverwinterNights\NWN\texturepacks - Texture packs
  • -
-

When NWN starts, it has available several methods to locate the data -files. Depending on the working directory or more probably the directory -of the executable, NWN can locate three key files, "chitin.key", -"dialog.tlk", and "nwn.ini".

-

Chitin.key contains a listing of all the resources available to NWN at -runtime. Given a resource name, chitin.key can be used to locate the name -of the master data file (.BIF) containing the resource.

-

Dialog.tlk is textual resource file. Given an number, dialog.tlk can -return the text associated with that number. This method has at least two benefits. -First, common strings can be stored in a central location instead of spread out amongst -many script files. This makes it possible to change the text without -having to modify or recompile the scripts that reference the string. The -second benefit is that depending on the language of the user, a different -dialog.tlk can be installed. For the French, the French dialog.tlk is -installed. (Note: I haven't actually seen exactly how Bioware does -language support. It is an assumption on my part that they just use different -dialog.tlk files. They could have dialog.tlk reserved as the common English -and then a different file name for each of the other languages.)

-

Nwn.ini is a standard format Windows ini file. It contains much of the -game configuration information. The section of most interest to us however -is the "[Alias]" section. This section provides a mapping -between logical directory names and their physical counterparts. This -allowed the developers to not have to worry about the how the NWN installation -would look at release time. As long as the group producing the -installation system properly set the different keys in the "[Alias]" -section, NWN will file the data files without modification.

-

For add on programs such as my NWN Explorer, Bioware stored NWN's -installation directory in the registry. The registry key -"HKEY_LOCAL_MACHINE\SOFTWARE\BioWare\NWN\Neverwinter\Location" -contains the installation directory. However, I would like to add that -anyone who wishes to develop 3rd party application for NWN should also allow the -user to specify the location of NWN. Even though I don't foresee Bioware -removing this registry key, there is always the chance that it might be missing -or invalid. Thus, it would be a shame if a user couldn't use your 3rd -party application because you were too lazy to add this simple feature.

- -

Resource Types

- -

NWN has a wide range of resource types.  The following table lists most -of these types.  (Many of these resource types might not be used by NWN but -were present in earlier Bioware games.)

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Resource NameResource Type
RES0x0000
BMP0x0001
MVE0x0002
TGA0x0003
WAV0x0004
PLT0x0006
INI0x0007
BMU0x0008
MPG0x0009
TXT0x000A
PLH0x07D0
TEX0x07D1
MDL0x07D2
THG0x07D3
FNT0x07D5
LUA0x07D7
SLT0x07D8
NSS0x07D9
NCS0x07DA
MOD0x07DB
ARE0x07DC
SET0x07DD
IFO0x07DE
BIC0x07DF
WOK0x07E0
2DA0x07E1
TLK0x07E2
TXI0x07E6
GIT0x07E7
BTI0x07E8
UTI0x07E9
BTC0x07EA
UTC0x07EB
DLG0x07ED
ITP0x07EE
BTT0x07EF
UTT0x07F0
DDS0x07F1
UTS0x07F3
LTR0x07F4
GFF0x07F5
FAC0x07F6
BTE0x07F7
UTE0x07F8
BTD0x07F9
UTD0x07FA
BTP0x07FB
UTP0x07FC
DTF0x07FD
GIC0x07FE
GUI0x07FF
CSS0x0800
CCS0x0801
BTM0x0802
UTM0x0803
DWK0x0804
PWK0x0805
BTG0x0806
UTG0x0807
JRL0x0808
SAV0x0809
UTW0x080A
4PC0x080B
SSF0x080C
HAK0x080D
NWM0x080E
BIK0x080F
PTM0x0811
PTT0x0812
ERF0x270D
BIF0x270E
KEY0x270F
-
-
-

Languages

-

NWN has built-in support for different languages.  Each of these -languages also has a male and female version.  These languages are commonly -used in names, descriptions, and dialogs.

-
-
- - - - - - - - - - - - - - -
LanguageID
English0
French, Male2
French, Female3
German, Male4
German, Female5
Italian, Male6
Italian, Female7
Spanish, Male8
Spanish, Female9
-
-
-

 

- - - \ No newline at end of file diff --git a/doc/specs/torlack/bif.html b/doc/specs/torlack/bif.html deleted file mode 100644 index f655e0e5ad..0000000000 --- a/doc/specs/torlack/bif.html +++ /dev/null @@ -1,103 +0,0 @@ - - - What are Module File - - - - - - -

What are BIF Files?

-

BIF files contain the actual data files referenced in KEY files.

-

BIF File Header

-

- The BIF file begins with a header.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000CHAR [4]File type signature (usually "BIFF")
0x0004CHAR [4]Version number (usually "V1  ")
0x0008UINT32Number of resources in the file
0x000CUINT32 - Unknown value
0x0010UINT32Offset from the start of file to the first resource structure
0x0014Total size of the structure
-
-
-

Resource Structures

-

For each resource contained in a BIF, there is a corresponding entry in the - resource list.  This list begins in the file at the offset specified in - the header.  The resource structures are stored sequentially in the BIF - file.  The BIF header specifies the number of resources.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000UINT32Key file ID of the resource
0x0004UINT32Offset from the start of the file to the given resource
0x0008UINT32Length of the resource in bytes
0x000CUINT32Type of the resource
0x0010Total size of the structure
-
-
- - diff --git a/doc/specs/torlack/binmdl.html b/doc/specs/torlack/binmdl.html deleted file mode 100644 index 3f254254e5..0000000000 --- a/doc/specs/torlack/binmdl.html +++ /dev/null @@ -1,1979 +0,0 @@ - - - - - - - -NWN Binary Model Files Basics - - - - -

NWN Binary Model Files Basics

-

The binary format of the model files contains 3 main sections, the header, -the model data and the vertex or raw data.

-

The header is a only twelve bytes long.  It provides us with the offset -and size of the raw data, and a 32 bit value of 0.  The value of 0 is -important since it can be used to tell the difference between an ASCII model -file and a binary one.  The chances of an ASCII model file starting with 4 -bytes of 0 is somewhere between zero and none.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000UINT32Value of 0 for binary models
0x0004UINT32Offset to the raw data/Size of the model data
0x0008UINT32Size of the raw data
0x000CTotal length of the structure
-
-
-

The model data follows the header in the file.  Immediately following -the model data is the raw data.  Thus, the second value in the header can -be either the model size or the offset from the start of the model data to the -raw data.  Either interpretation works since they are the exact same value.

-

Arrays and Pointers in a Model File

-

The structure of the model is reasonably complex.  Not only does it -contain structures of data, but those structures reference other -structures.  This is accomplished using arrays and pointers.  

-

There are two types of pointers inside of a model file, model data pointer -and raw data pointer.  Each of these pointers are stored as a 32 bit -value.  In the case of model data pointers, the pointer will contain an -offset from the start of the model data to the data in question.  A value -of zero represents a "NULL" pointer or a pointer that doesn't -reference anything.  For raw data pointers, the pointer will contain an -offset from the start of the raw data to the data in question.  A value of -0xFFFFFFFF (unsigned) or -1 (signed) represents a "NULL" pointer or a -pointer that doesn't reference anything.  The reason the raw data uses a -value of -1 is that an offset of zero is a valid pointer into the raw -data.  Excluding one special case, this isn't true for model data -pointers.  (Only one element in a binary model file points to the data at -offset zero in the model data.  However, this value is transient and isn't -actually stored in the model file.)

-

Some of you might be wondering why I am referring to these offsets as -pointers.  After all, in the binary model format, they are always offsets -and never actually a real pointer.  That is very true for the disk image of -a model.  However, on 32 bit address processors (or processors such as the -Alpha that can run in 32 bit address mode with sign extend), after the model is -loaded from disk, all the offsets can be converted to pointers.  This -improves greatly the run time performance.

-

Arrays in models are a slightly more complicated beast.  They -consist of the following three elements.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000UINT32Pointer/Offset to the first elements of the array
0x0004UINT32Number of used entries in the array
0x0008UINT32Number of allocated entries in the array
0x000CTotal length of the structure
-
-
-

For binary model files, the number of used entries and number of allocated -entries will always be the same.  During run time or complication time, -these value will usually differ since these arrays by nature will grow as more -elements are added.

- -

Arrays can contain most anything as long as the elements are all the same -type.  Examples of common arrays in model files would be arrays of vertices, -faces, and even pointers.  In the case of pointers, these arrays are -commonly used to represent a model hierarchy.

- -

Controllers

- -

Each node (to be defined later), in a model can contain zero or more -controllers.  Controllers can be considered as attributes of a node.  -They defined certain aspects of the node.  One of the most common -controller is the position controller.  This controller dictates the -position of the node relative to the parent node.

- -

One of the first obvious questions about controllers is why isn't this -information included as part of the standard node structures.  There are -two reasons for this.  The first reason is that controllers are -optional.  In the case of an emitter node, there are around 50 different -controllers that can be specified.  Having to dedicate storage space to all -of these controllers, used or not, would be a waste of space.  The second -and more important reason is that controllers can be animated.

- -

To animate a model, the model must change over time.  The way to do this -is with time keyed controllers.  All animations take a given amount of time -from start to finish.  For example, the swing of a sword might take 1 -second of total animation time.  In order to draw the model properly, -controllers have different that are time keyed.  For example, normally, the -position controller would be specified as follows.

- -
- -
position 0.2 1.0 0.124
- -
- -

However, in the case of a time keyed position, it would be specified as -follows.

- -
- -
positionkey 3
-  0.0 0.2 1.0 0.124
-  0.4 0.4 1.0 0.510
-  0.8 0.6 1.0 0.516
- -
- -

When specifying a time keyed controller, the first value is the start time -for that value.  Following the start time is the actual value.  In -this case, during animation time 0.0 through 0.4, the first position will be -used.  Between 0.4 and 0.8, the second position will be used.  Between -0.8 and the animation time for the given animation, the 3rd value will be used.

- -

To provide smoother animations, the values will be interpolated.  -For example, given our previous data, if the current animation time was 0.2, -then that would be half way between the first and second positions.  Thus -each position will contribute equally to the actual position used.  If the -animation time was earlier, then the first position would be given more -weight.  The opposite is true if the animation time was later. 

- -

To provide an even smoother animation, a second style of interpolation is -available.  It is know as "bezier" interpolation.  Bezier -interpolation is used when you specify "bezierkey" as the suffix to -the controller name.  (Note: At this time, I have not verified that any -Bioware model uses bezier interpolation.  I don't even know if the -rendering engine supports this option.)

- -

Inside a binary model file, controllers are stored as two arrays in the model -data.  The first array is an array of a controller structure.  The -second array is a simple array of floating point numbers.  The second array -contains the actual controller data while the first tells us about all the -controllers in the array.  The controller structure is as follows.

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000INT32Type of controller
0x0004INT16Number of rows of controller data
0x0006INT16Index into the float array of the first time key
0x0008INT16Index into the float array of the first controller data value
0x000AINT8Number of columns excluding the time key column
0x000BINT8Pad, not used
0x000CTotal length of the structure
-
-
-

There are a few important notes about the controller structure.  The -first thing of note is that for controllers that aren't time keyed, they are -still stored as if they are time keyed but with a single row and a time key -value of zero.  Thus, it is impossible to tell the difference between a -controller that isn't time keyed, and a time keyed controller with a single row -and a time key value of zero.  The second important note is that if the -controller is actually bezier keyed, then the value of 0x10 is ORed in with the -number of columns.  This is how you can tell the difference between a -normal keyed controller and a bezier keyed controller.  Finally, all key -values are stored continuously and all the data values are stored -contiguously.  Thus, if a keyed controller had 3 rows with the time keys -starting at floating point value 5, then the time keys for the other two rows -would be value 6 and value 7.  Also, it appears that for some models, the -controller "detonate" when used as a key controller doesn't even list -a time key.  Thus, the number of columns listed is -1.

-

Following is a list of all controllers:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameValueUsed in nodes
Position8All
Orientation20All
Scale36All
Color76Light
Radius88Light
ShadowRadius96Light
VerticalDisplacement100Light
Multiplier140Light
AlphaEnd80Emitter
AlphaStart84Emitter
BirthRate88Emitter
Bounce_Co92Emitter
ColorEnd96Emitter
ColorStart108Emitter
CombineTime120Emitter
Drag124Emitter
FPS128Emitter
FrameEnd132Emitter
FrameStart136Emitter
Grav140Emitter
LifeExp144Emitter
Mass148Emitter
P2P_Bezier2152Emitter
P2P_Bezier3156Emitter
ParticleRot160Emitter
RandVel 164Emitter
SizeStart168Emitter
SizeEnd172Emitter
SizeStart_Y176Emitter
SizeEnd_Y180Emitter
Spread184Emitter
Threshold188Emitter
Velocity192Emitter
XSize196Emitter
YSize200Emitter
BlurLength204Emitter
LightningDelay208Emitter
LightningRadius212Emitter
LightningScale216Emitter
Detonate228Emitter
AlphaMid464Emitter
ColorMid468Emitter
PercentStart480Emitter
PercentMid481Emitter
PercentEnd482Emitter
SizeMid484Emitter
SizeMid_Y488Emitter
SelfIllumColor100All Meshes
Alpha128All Meshes
-
-
-

Model Routines and the Node types

-

All nodes in a model begin with six values which have also been called -"tokens".  During the early process of decoding the binary -models, many people including myself made the mistake that these tokens were -used to identify the different types of nodes.  As silly as it sounds, this -was the best we could do at the time.  These six tokens did uniquely -identify each of the nodes.  However, I and I would imagine most others -knew there had to be a deeper meaning to these tokens.  Nobody uses 6 -4-byte values to uniquely identify a handful of different nodes.  All of -these tokens were values in the range of 0x00400000 and 0x00500000.  There -was no apparent bit mask to the values.  However, the difference between -the token values was interesting since it was usually either 0x10 or 0x20.  -Now, if someone was really had everything on the ball and had a reasonable -knowledge of the Win32 image loader, they would have realized something that -eluded everyone, including myself for the longest time.  These tokens were -not tokens, they were routine addresses.  You see, the Win32/NT image -loader loads images at 0x0041000.  Thus, the funny start to all the -numbers.  

-

So, as of now, these "tokens" should be consider unreliable as a -method of identifying model node types.  What if Bioware releases new -models using a new build of their model compiler and these routine addresses -change.  All the existing software that utilizes these "tokens" -would fail.

-

Luckily, the proper way to identify the node type has been located.  -Every node contains a 32 bit bit mask that identifies which structures make up -the node.  Following is a list of all the flags.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameValue
HasHeader0x00000001
HasLight 0x00000002
HasEmitter0x00000004
HasReference0x00000010
HasMesh0x00000020
HasSkin0x00000040
HasAnim0x00000080
HasDangly0x00000100
HasAABB0x00000200
-
-
-

Every node contains a node header.  In the case of a dummy node, only a -node header is required. All mesh nodes also contain a mesh structure.  In -the case of a trimesh node, only the node header and the mesh header is -required.  By looking at a combination of flags, not only do we know what -structures make up the node, but we know what the node type is.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameValue
Dummy0x00000001
Light0x00000003
Emitter0x00000005
Reference0x00000011
TriMesh0x00000021
SkinMesh0x00000061
AnimMesh0x000000A1
DanglyMesh0x00000121
AABBMesh0x00000221
-
-
-

As you can see, each of the different node types contains one or more flags.

- -

Part Numbers

- -

Part numbers are values assigned to nodes as the model compiler creates -them.  However, after the complete geometry has been compiled, these values -are adjusted.

- -

If a model has a super model, then the geometry for the model is compared -against the geometry for the super model.  Any node that matches the name -of a node in the super model will be given the part number assigned to the node -in the super model.  If a node in the model isn't found in the super model, -then it receives a part number of -1.  If a model doesn't have a super -model, then the part numbers are left as is.

- -

After the geometry for an animation has been compiled, the same process is -used to match the nodes in the animation with the nodes in the main model -geometry.  It is important to note that the animation geometry is compare -against the model's geometry and not the model's super model geometry.

- -

Layout of the Binary Model File

-

The basic layout of the binary model file is as follows:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
File Header
Model
- Data
Model Geometry Header
Node 1
Node 2
...
Node N
Animation Geometry Header 1
Node 1
Node 2
...
Node N
Animation Geometry Header 2
Node 1
Node 2
...
Node N
Animation Geometry Header N
Raw Data
-
-
- -

Not all models contain animation headers.  Also, in many models, there -might be no raw data.  It is also important to remember that this diagram -is a simplification of the real model layout.  For example, to fully -defined a node, it might take 100 bytes of data or 10,000 bytes of data.  -This information might be scattered in sections throughout the model data.

-

The Geometry Header

-

All models contain at least one geometry header.  This header is part of -the larger model geometry header.  If the model contains animations, then -there will be a geometry header in each of the animation geometry headers.  -

-

The format of the geometry header is as follows:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Function PointerPointer to a function
0x0004Function PointerPointer to the function to parse a ASCII model line
0x0008CHAR [64]Geometry Name (model or animation name)
0x0048Node Header PointerPointer/Offset to the root node of the geometry
0x004CUINT32Number of nodes in the geometry.  In the case of the - model geometry, if the model has a super mode defined, then this value - also includes the number of nodes in the super model plus one.
0x0050Pointer ArrayArray of unknown data (probably runtime only)
0x005CPointer ArrayArray of unknown data (probably runtime only)
0x0068UINT32Reference count, initialized to 0.  When another - model references this model, then this value is incremented.  When - the referencing model dereferences this model the count is - decremented.  When this count goes to zero, the model can be - deleted since it is no longer needed.
0x006CUINT8Geometry Type
- 0x01 = Basic geometry header (not in models)
- 0x02 = Model geometry header
- 0x05 = Animation geometry header
- 0x80 = If bit is set, then model is a compiled binary model loaded from - disk and converted to absolute addresses.
0x006DUINT8 [3]Padding
0x0070Total length of the structure
-
-
- -

As noted, the two arrays at 0x50 and 0x5C, and the data at 0x68 is unknown at -this time.  

-

The Model Header

-

There is only one model header per model file.  This header always -starts at offset 0 in the model data section or offset 12 from the start of the -file.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Geometry HeaderGeometry Header
0x0070UINT8Unknown value initialized to 0
0x0071UINT8Unknown value initialized to 1
0x0072UINT8Model classification:
- 0x01 = Effect
- 0x02 = Tile
- 0x04 = Character
- 0x08 = Door
0x0073UINT8If non-zero, model should be fogged
0x0074UINT32Unknown value initialized to 0
0x0078Animation Header Pointer ArrayArray of pointers to all the animation geometries
0x0084Pointer to parent modelPointer to the parent model, always 0
0x0088FLOAT [3]Bounding box min for the model, defaults to (-5, -5, -1)
0x0094FLOAT [3]Bounding box max for the model., defaults to (5, 5, 10)
0x00A0FLOATRadius of the model, defaults to 7.0
0x00A4FLOATAnimation scale, defaults to 1.0
0x00A8CHAR [64]Super model name, defaults to ""
0x00E8Total length of the structure
-
-
-

The Animation Header

-

There are zero or more animation headers per model, one header for each -animation.  All animations contain their own geometry information.  -However, this usually consists of dummy nodes containing controller information.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Geometry HeaderGeometry Header
0x0070FLOATAnimation length, defaults to 1.0
0x0074FLOATTrans time, defaults to 0.25
0x0078CHAR [64]Animation root, defaults to ""
0x00B8Animation Event ArrayArray of all the events associated with this animation
0x00C4Total length of the structure
-
-
-

Each animation can have zero or more events.  Unlike other arrays that -contain pointers to the structures, the animation event array is an array of the -actual structure.  The structure is as follows:

-
-
- - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000FLOATAfter
0x0004CHAR [32]Event name
0x0024Total length of the structure
-
-
-

Nodes

-

Currently, there are nine different nodes types that make up a model's -geometry.  These nodes specify such elements as lighting, animated graphics -emitters, and different types of meshes.  Each node type share a common -header that supplies us with enough information to tell what type of node it is -and information about controllers and children.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Function PointerUnknown function
0x0004Function PointerFunction to parse a line of an ASCII model file
0x0008Function PointerFunction to perform post node processing
0x000CFunction PointerUnknown function
0x0010Function PointerUnknown function
0x0014Function PointerUnknown function
0x0018UINT32Inherit color flag
0x001CUINT32Part number/Node number
0x0020CHAR [32]Node name
0x0040Geometry PointerPointer to the parent geometry, always zero
0x0044Parent Node PointerPointer to the parent node, always zero
0x0048Node Header Pointer ArrayArray of pointer to the children nodes
0x0054Controller Key ArrayArray of controller key structures
0x0060FLOAT ArrayArray of controller data values
0x006CUINT32Node flags/type
0x0070Total length of the structure
-
-
-

Mesh Nodes

-

All nodes that contain mesh information share a common header.  The -header is as follows:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Node HeaderCommon node header
0x0070Function PointerFunction to prepare the mesh information
0x0074Function PointerFunction to cleanup after mesh has been built
0x0078Face ArrayArray of face structures
0x0084FLOAT [3]Bounding box min, defaults to (0, 0, 0), computed
0x0090FLOAT [3]Bounding box max, defaults to (0, 0, 0), computed
0x009CFLOATMesh radius, defaults to 0, computed
0x00A0FLOAT [3]Average of all points in the mesh, defaults to (0, 0, 0), computed
0x00ACFLOAT [3]Diffuse color, defaults to (0.8, 0.8, 0.8)
0x00B8FLOAT [3]Ambient color, defaults to (0.2, 0.2, 0.2)
0x00C4FLOAT [3]Specular color, defaults to (0, 0, 0)
0x00D0FLOATShininess, defaults to 1
0x00D4UINT32Shadow flag, defaults to 1
0x00D8UINT32Beaming flag, defaults to 0
0x00DCUINT32Render flag, defaults to 1 excluding AABB mesh where it defaults to 0
0x00E0UINT32Transparency hint, defaults to 0 excluding AABB mesh where it defaults - to 1
0x00E4UINT32Unknown value, defaults to 0.  Known values probably - 0, 1, 2, and 4.
0x00E8CHAR [64]Texture0/Bitmap
0x0128CHAR [64]Texture1
0x0168CHAR [64]Texture2
0x01A8CHAR [64]Texture3
0x01E8UINT32Tile fade, defaults to 0
0x01ECUINT32 Pointer ArrayVertex Indices, compile only, not stored in binary
0x01F8UINT32 ArrayLeft over faces, compile only, not stored in binary??
0x0204UINT32 ArrayVertex Indices count array
0x0210Raw UINT16 Pointer ArrayVertex Indices offset array (The pointers exist in the - model data, however, the list of UINT16 that they point to exist in the - raw data.
0x021CRaw Data PointerUnknown, probably used with triangle strips, initialized to -1
0x0220UINT32Unknown, probably used with triangle strips, initialized to 0
0x0224UINT8Triangle mode
- 0x03 = Triangle
- 0x04 = Triangle Strips
0x0225UINT8 [3]Padding
0x0228PointerPointer to a compile only structure, always zero
0x022CRaw FLOAT [3] PointerPointer to the vertex data, stored in the raw data region, -1 if not - present
0x0230UINT16Vertex count
0x0232UINT16Texture count, usually 1
0x0234Raw FLOAT [2] PointerPointer to the texture 0 vertex data, stored in the raw data region, - -1 if not present
0x0238Raw FLOAT [2] PointerPointer to the texture 1 vertex data, stored in the raw data region, - -1 if not present
0x023CRaw FLOAT [2] PointerPointer to the texture 2 vertex data, stored in the raw data region, - -1 if not present
0x0240Raw FLOAT [2] PointerPointer to the texture 3 vertex data, stored in the raw data region, - -1 if not present
0x0244Raw FLOAT [3] PointerPointer to the vertex normals, stored in the raw data region, -1 if - not present
0x0248Raw UINT32 PointerPointer to the vertex RGBA colors, stored in the raw data region, -1 - is not present
0x024CRaw FLOAT [3] PointerPointer to texture animation data, stored in the raw data region, -1 - if not present
0x0250Raw FLOAT [3] PointerPointer to texture animation data, stored in the raw data region, -1 - if not present
0x0254Raw FLOAT [3] PointerPointer to texture animation data, stored in the raw data region, -1 - if not present
0x0258Raw FLOAT [3] PointerPointer to texture animation data, stored in the raw data region, -1 - if not present
0x025CRaw FLOAT [3] PointerPointer to texture animation data, stored in the raw data region, -1 - if not present
0x0260Raw FLOAT? PointerPointer to texture animation data, stored in the raw data region, -1 - if not present
0x0264UINT8Light mapped flag, defaults to 0
0x0265UINT8Rotate texture flag, defaults to 0
0x0266UINT16Padding
0x0268FLOATVertex normal sum divided by 2, initialized to 0
0x026CUINT32/FLOATUnknown, initialized to 0
0x0270Total length of structure
-
-
- -

The two arrays at 0x0210 and 0x0204 are interrelated. The array at 0x0210 -isn't an array of UINT16s, it is an array of pointers to lists of UINT16.  -The number of UINT16s in each of the lists is specified by the elements of the -array at 0x0204.  For the second entry in both 0x0210 and 0x0204, the array -0x0204 might tell us the there are 14 vertices in the list while the 0x0210 -array gives us the pointer to the list.

-

All the pointers 0x022C through 0x0248 point to lists of data.  The -number of elements in these list is specified by the UINT16 at 0x0230 which is -the vertex count.

-

The face structure is as follows:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000FLOAT [3]Plane normal
0x000CFLOATPlane distance
0x0010INT32Surface ID
0x0014INT16 [3]Adjacent face number or -1
0x001AINT16 [3]Vertex indices
0x0020Total length of structure
-
-
-

The Dummy Node 

-

The dummy node is a default node in a geometry that only contains children -nodes and controller information.  It has no other data associated with it.

-
-
- - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Node HeaderCommon node header
0x0070Total length of structure
-
-
-

The Light Node

-

The light node specifies light sources in the geometry of the model.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Node HeaderCommon node header
0x0070FLOATFlare radius
0x0074UINT32 ArrayArray of unknown information
0x0080FLOAT ArrayFlare sizes
0x008CFLOAT ArrayFlare positions
0x0098FLOAT [3] ArrayFlare color shifts
0x00A4CHAR Pointer ArrayArray of pointers to the flare texture names
0x00B0UINT32Light priority, defaults to 5
0x00B4UINT32Ambient only flag, defaults to 0
0x00B8UINT32Dynamic type, defaults to 1
0x00BCUINT32Affect dynamic flag, defaults to 1
0x00C0UINT32Shadow flag, defaults to 1
0x00C4UINT32Generate flare flag, defaults to 0
0x00C8UINT32Fading light flag, defaults to 1
0x00CCTotal length of structure
-
-
-

The Emitter Node

-

The emitter node specifies dynamic graphical elements that are emitted from -the model such as smoke or sparkles.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Node HeaderCommon node header
0x0070FLOATDead space
0x0074FLOATBlast radius, defaults to 0
0x0078FLOATBlast length, defaults to 0
0x007CUINT32X grid
0x0080UINT32Y grid
0x0084UINT32Space type, defaults to 0
0x0088CHAR [32]Update
0x00A8CHAR [32]Render
0x00C8CHAR [32]Blend
0x00E8CHAR [64]Texture
0x0128CHAR [16]Chunk name
0x0138UINT32Two sided texture flag, defaults to 0
0x013CUINT32Loop flag, defaults to 0
0x0140UINT16Render order, defaults to 0
0x0142UINT16Padding
0x0144UINT32Emitter flags
- 0x0001 = P2P
- 0x0002 = P2P Sel
- 0x0004 = Affected by Wind
- 0x0008 = Is Tinted
- 0x0010 = Bounce
- 0x0020 = Random
- 0x0040 = Inherit
- 0x0080 = Inherit Vel
- 0x0100 = Inherit Local
- 0x0200 = Splat
- 0x0400 = Inherit Part
0x0148Total length of structure
-
-
- -

The Reference Node

-

The reference node... TBD

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Node HeaderCommon node header
0x0070CHAR [64]Ref model
0x00B0UINT32Reattachable flag
0x00B4Total Length of structure
-
-
-

The TriMesh Node

-

The trimesh node provides the basic drawing mesh used to render elements of -the game.

-
-
- - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Mesh HeaderCommon mesh header
0x0270Total Length of structure
-
-
-

The Skin mesh Node

-

The skin mesh node provides a specialized mesh where the texture is stretched -and contorted as the model moves to provide a more realistic look to skin.  -It is mostly used just for things such as dragon wings.  

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Mesh HeaderCommon mesh header
0x0270Weight ArrayUsed during compile time to store weight information
0x027CRaw FLOAT [4] PointerCompiled weight information for each vertex
0x0280Raw UINT16 [4] PointerReference index for bones
0x0284Raw UINT16 PointerBone reference mapping array
0x0288UINT32Number of entries in the mapping array
0x028CQuaternion ArrayQBone ref inv
0x0298FLOAT [3] ArrayTBome ref inv
0x02A4UINT32 ArrayBone constant indices
0x02B0UINT16 [17]Bone part numbers
0x02D2UINT16Spare
0x02D4Total length of structure
-
-
- -

Most all skin data is computed from the weights array.

-

The Animmesh node

-

The animmesh nodes are used for the game's GUI

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Mesh HeaderCommon mesh header
0x0270FLOATSample Period
0x0274FLOAT [3] ArrayAnimation vertices, not stored in binary
0x0280FLOAT [3] ArrayAnimation texture vertices, not stored in binary
0x028CFLOAT [3] ArrayAnimation vertex normals, not stored in binary
0x0298FLOAT [3] PointerPointer to the stored animation vertex information
0x029CFLOAT [2] PointerPointer to the stored animation texture vertex - information
0x02A0UINT32Number of vertex sets
0x02A4UINT32Number of texture vertex sets
0x02A8Total length of structure
-
-
-

The stored arrays are much like the other stored vertex arrays.  There -size is the number of sets times the number of mesh vertices.  There is -also a difference between how this information is parsed from the ASCII version -and stored in the binary.  In the ASCII version, each vertex is listed sequentially -for each vertex or texture vertex  sets.  However, in the binary -version, each vertex is store with the different sets store sequentially.

-

The Danglymesh node

-

The danglymesh node provides a model with the look of movement by allowing -faces to move due to momentum even after the whole model has stopped.  Thus -is much like how a car passenger jerks forward in a car when it stops suddenly.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Mesh HeaderCommon mesh header
0x0270FLOAT ArrayVertex constraints
0x027CFLOATDisplacement value
0x0280FLOATTightness value
0x0284FLOATPeriod value
0x0288Total length of structure
-
-
-

Much like the vertex information for the mesh, the constrains are expanded to -match the vertex list stored in the model.

-

The AABB node

-

The aabb node provides the game with the ability to test for collisions.

-
-
- - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000Mesh HeaderCommon mesh header
0x0270AABB Entry PointerAABB table root pointer
0x0274Total length of structure
-
-
-

The following is the layout of the AABB entry structure.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000FLOAT [3]Min bounding box
0x000CFLOAT [3]Max bounding box
0x0018AABB Entry PointerLeft node
0x001CAABB Entry PointerRight node
0x0020INT32Leaf face part number or -1 if not a leaf
0x0020UINT32Most significant plane???
- 0x01 = Positive X
- 0x02 = Positive Y
- 0x04 = Positive Z
- 0x08 = Negative X
- 0x10 = Negative Y
- 0x20 = Negative Z
0x0024Total length of structure
-
-
- -

Update - 09/29/2002

-
    -
  1. Cleaned up the definition of the AABB entry slighly.
  2. -
  3. Updated the skin node.
  4. -
-

Update - 08/22/2002

-
    -
  1. Corrected assorted typos and minor errors
  2. -
  3. "Triangle fan" changed to "Triangle strip".
  4. -
  5. Corrected the type on the offset in the mesh header at 0x0210.  The - array exists in model data, but the actual data that the array points to - exists in the raw data.
  6. -
  7. Added a missing unknown value in the mesh header at 0x00E4.  - Corrected the offset of texture0/bitmap.
  8. -
  9. Updated information on the part numbers and the node counts.
  10. -
  11. The value at 0x0068 in the geometry header is now known.
  12. -
  13. Another flag in the geometry header discovered.
  14. -
- -

Credits

-

I wanted to thank Zaddix and Revinor.  Their information about the -binary model formats provided an excellent starting point for my work.

- - - - diff --git a/doc/specs/torlack/itp.html b/doc/specs/torlack/itp.html deleted file mode 100644 index fbdf00324b..0000000000 --- a/doc/specs/torlack/itp.html +++ /dev/null @@ -1,618 +0,0 @@ - - - What is an ITP File - - - - - - -

What is an ITP File?

-

(Note: ITP files are more correctly known as the GFF and BioWare's - documentation can be found here.)

-

An ITP file can be thought of as a hierarchical collection variables.

-

That cleared it up, didn't it.

-

In an ITP file, you have a series of what can be considered variable assignments - such as 'PlayerName = "Francis"'. There can practically be an infinite - number of variable assignments in one file.  Also, ITP files allow you to - nest variable assignments much the way a programmer might nest structures.

-

ITP files are very common in NWN.  The format is used for a wide collection - of files such as; ITP, BIC, DLG, GIT, GFF, FAC, UTI, UTC, UTT, UTS, UTE, UTD, - UTP, UTM, and UTW.  All of these different files share the same common - file format.  So in reality calling the file format ITP is inaccurate, but - it is the name we all know best.

-

Data Stored in an ITP File

-

ITP files contain seven sections of data.  The sections are as follows:

-
    -
  1. - File Header - - Gives us enough information to begin decoding the remaining sections. -
  2. - Entry Table - - Provides us with our collections of variables and their nesting. -
  3. - Element Table - - Provides the information about all the variables contained within an entry. -
  4. - Variable Names Table - - List of all the different variables names. -
  5. - Variable Data Section - -  Depending on the element in question, this data block can contain a - assortment of data. -
  6. - Multiple Element Map (MultiMap) - - Used to list which elements are contained within an entry when that entry - contains more than one element. -
  7. - List Section - Provides a method for an element to have one or more - entries as children.
  8. -
-

The File Header

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000char [4]4 byte signature.
0x0004char [4]4 byte version.
0x0008UINT32Offset from start of the file to the first entry
0x000CUINT32Number of entries in the file
0x0010UINT32Offset from start of the file to the first element
0x0014UINT32Number of elements in the file
0x0018UINT32Offset from start of the file to the first variable name
0x001CUINT32Number of names in the variable name block
0x0020UINT32Offset from start of the file to the first variable data
0x0024UINT32Number of bytes in the variable data block
0x0028UINT32Offset from start of the file to the first multimap
0x002CUINT32Number of bytes in the multimap table
0x0030UINT32Offset from start of the file to the first list
0x0034UINT32Number of bytes in the list table
0x0038Total length of the structure
-
-
-

All offsets in the file header are from the start of the file.  The file - header is always at offset zero.  It seems that the offset to the first - entry is always 0x0038.  But please note that for entries and elements, we - are given the number of entries or elements.  For the four remaining data - sections, the total section length is given in bytes.

-

The Entity Table

-

The entity table is an array of structures of the following form:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000UINT32Entity code (Use unknown)
0x0004UINT32Element index or MultiMap offset
0x0008UINT32Number of elements in this entry
0x000CTotal length of the structure
-
-
-

The first entry in the entry table is the root of whole hierarchy.  From - that one entry, all other entries and elements can be accessed in a logical - form.

-

Entries only serve one function, to contain one or more elements.  - Depending on the number of elements contained in an entry, accessing the - elements can either be easy or require indirect referencing.  When an - entry contains only one element, and thus the value at offset 0x0008 is one, - then the value at offset 0x0004 is the index in the element table of the only - child.  When an entry contains more than one element, then the value at - offset 0x0004 contains a byte offset into the multimap table.  This offset - points to an array of element numbers stored as UINT32 values.  -

-

Here is an example of how you might iterate through the list of elements for an - entity.  This code assumes that the whole file is resident in memory in a - contiguous buffer.

-
-
char *pFileData = AddressOfTheITPDataInMemory;
-Header *pHeader = (Header *) pFileData;
-Entry *pEntryTable = (Entry *) &pFileData [pHeader ->EntryOffset];
-Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];
-
-Entry *pEntry = &pEntryTable [0]; // For this example, use root entry
-
-if (pEntry ->Count == 1) // We have one element
-{
-    Element *pElement = &pElementTable [pEntry ->Offset];
-    // Do something with the element
-}
-else //more than one
-{
-    UINT32 *pMMap = (UINT32 *) &pFileData [pHeader ->MultiMapOffset + pEntry ->Offset];
-    for (int i = 0; i < pEntry ->Count; i++)
-    {
-        Element *pElement = &pElementTable [pMMap [i]];
-        // Do something with the element
-    }
-}
-
-

There is one trick you can use to reduce duplicated code.  I use this trick - in my software.

-
-
char *pFileData = AddressOfTheITPDataInMemory;
-Header *pHeader = (Header *) pFileData;
-Entry *pEntryTable = (Entry *) &pFileData [pHeader ->EntryOffset];
-Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];
-
-Entry *pEntry = &pEntryTable [0]; // For this example, use root entry
-
-UINT32 *pMMap;
-if (pEntry ->Count == 1)
-    pMMap = &pEntry ->Offset;
-else
-    pMMap = (UINT32 *) &pFileData [pHeader ->MultiMapOffset + pEntry ->Offset];
-
for (int i = 0; i < pEntry ->Count; i++)
-{
-    Element *pElement = &pElementTable [pMMap [i]];
-    // Do something with the element
-}
-
-

The Element Table -

-

The element table is an array of structures of the following form: -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000UINT32Variable type (0-15)
0x0004UINT32Variable name index
0x0008VARIESData for the variable
0x000CTotal length of the structure
-
-
-

As you can see, the element structure is very basic.  From the structure we - know the name of the variable, the type of the variable, and the assigned value - of the variable.  The only difficult part is extracting the value from the - structure. -

-

There are 16 know variable types supported by the ITP format.  They are as - follows: -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Value - Type - Access - Data
0UINT8DirectUnsigned byte
1INT8DirectSigned byte
2UINT16DirectUnsigned word
3INT16DirectSigned word 
4UINT32DirectUnsigned longword 
5INT32DirectSigned longword 
6UINT64IndirectUnsigned quadword 
7INT64IndirectSigned quadword
8FLOATIndirectFour byte floating point value
9DOUBLEIndirectEight byte floating point value
10STRINGIndirectCounted string
11RESREFIndirectCounted resource name
12STRREFComplexMultilingual capable string
13DATREFComplexCounted binary data 
14CAPREFComplexList of child elements
15LISTComplexList of child entries
-
-
-

For the simple data types, UINT8, INT8, UINT16, INT16, UINT32, INT32 and FLOAT, - the value is just stored directly into offset 0x0008.  Since NWN is for - Intel systems, all the data values are stored in little endian format.  - Thus, for UINT8 and INT8, the value is a single byte stored literally at offset - 0x0008 in the element structure.  A UINT16 or INT16 takes up the first two - bytes.  The remaining three basic data types take up all four bytes.  - Access to the different values of the element can be done with this structure. -

-
-
struct Element
-{
-    UINT32 Type;
-    UINT32 NameIndex;
-    union 
-    {
-        UINT8 ui8;
-        INT8 si8;
-        UINT16 ui16;
-        INT16 si16;
-        UINT32 ui32;
-        INT32 si32;
-        FLOAT flt;
-        UINT32 Offset;
-    };
-};
-

Note: The "Offset" member can be used to access the indirect and complex - data stored in the variable data section.. -

-
-

- In the case of UINT64, INT64 and DOUBLE, the value stored at offset 0x0008 is a - byte offset into the variable data region.  Starting at that address would - be stored the value in question. -

-
-
char *pFileData = AddressOfTheITPDataInMemory;
-Header *pHeader = (Header *) pFileData;
-Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];
-
-Element *pElement = &pElementTable [0]; // For this example, use first element
-
if (pElement ->Type == 6)
-{
-    UINT64 ul64 = *((UINT64 *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset]);
-}
-else if (pElement ->Type == 7)
-{
-    INT64 l64 = *((INT64 *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset]);
-}
-else if (pElement ->Type == 9)
-{
-    double d = *((double *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset]);
-}
-
-

The STRING and RESREF types are two more indirect types but require a bit more - work to access.  For both the STRING and RESREF the actual data is stored - in the variable data section starting at the given offset.  They both - being with the length of string.  Following the length, are the actual - bytes of the string.  The string is not NULL terminated.  However, - STRING and RESREF do differ.  In the case of STRING, the string length is - stored as a UINT32 value.  In the case of RESREF, the string length is - stored as a UINT8 value.

-
-
char *pFileData = AddressOfTheITPDataInMemory;
-Header *pHeader = (Header *) pFileData;
-Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];
-
-Element *pElement = &pElementTable [0]; // For this example, use first element
-
if (pElement ->Type == 10) //STRING
-{
-    UINT32 Length = *((UINT32 *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset]);
-    char *String = (char *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset + 
-        sizeof (UINT32)]
-}
-else if (pElement ->Type == 11) //RESREF
-{
-    UINT8 Length = *((UINT8 *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset]);
-    char *String = (char *) &pFileData [
-        pHeader ->VarDataOffset + pElement ->Offset + 
-        sizeof (UINT8)]
-}
-
-

The STRREF has two uses.  First, it references a string in the dialog.tlk - file.  Second, it provides localized (human language specific) version of - the string.  The offset in the element points to a variable length - structure that begins with the following 12 bytes of data. -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000UINT32Number of bytes included in the STRREF not including this count.
0x0004INT32ID of the string in the in the dialog.tlk file.  If none, then -1.
0x0008UINT32Number of language specific strings.  This value is usually zero.
0x000CTotal length of the structure
-
-
-

If the number of language specific strings is zero, then the length of the total - STRREF is only 12 bytes.   The text of the string can be retrieved - from dialog.tlk.  The value stored as the size of the STRREF will be 8 - since the 4 bytes in the count aren't included.  However, if the number of - language specific strings is not zero, then there will be the given number of - the following structure following the initial STRREF structure. -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- Offset - Type - Description
0x0000UINT32Language of the string (values unknown)
0x0004UINT32Number of bytes in the following string
0x0008CHAR []Variable length string
VARIESTotal length of the structure will always be 8 plus the length of - the string
-
-
-

The DATREF associates a block of arbitrary binary data with the element in - question.  At the offset in the variable data section given in the element - is a UINT32 that has the number of bytes in the DATREF.  Following the - UINT32 is the binary data. -

-

The CAPREF element type allows for an element to contain an array of other - elements by referencing a single entry.  The offset in the element - structure specifies the entry number.  All the elements that entry - references can be considered as the children of the element. -

-

The LIST element type allows for an element to contain an array of other - entries.  For a LIST type, the offset in the element structure specifies - an offset into the list table.  This is a byte offset even though the - table itself is just a series of UINT32 values.  At the first UINT32 - pointed to by the offset will be the number of entries referenced by the - element.  Following this count will be the actual entry - numbers.   -

-
-
char *pFileData = AddressOfTheITPDataInMemory;
-Header *pHeader = (Header *) pFileData;
-Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];
-
-Element *pElement = &pElementTable [0]; // For this example, use first element
-
if (pElement ->Type == 15) //LIST
-{
-    UINT32 *List = *((UINT32 *) &pFileData [
-        pHeader ->ListDataOffset + pElement ->Offset]);
-    UINT32 EntryCount = List [0];
-    for (int i = 0; i < (int) EntryCount; i++)
-    {
-        UINT32 EntryIndex = List [i + 1];
-        // do something with Entry
-    }
-}
-
-

The Variable Name Table

-

The variable name table is a series of variable names stored in 16 byte long - character strings.  Care should be taken when processing the variable name - strings.  If the string is less than 16 characters in length, it will be - NULL terminated.  However, if the variable name is 16 characters long, - then it will not.  The easiest thing to do is copy the string into a 17 - byte character string and then set the 17th character to NULL.  Thus, if - the string is 16 characters long, the 17th character will force a NULL - termination.

-

The Remaining Tables

-

All other data tables are purely subservient to the entry and element - tables. See the section on the elements to see how this information is - interpreted.

-

Credits

-

Even though I was able to decode much of the ITP format soon after the game was - released, I want to thank Logxen, Seg Falt and Chanteur - for the information they published about the file format.  Their - information filled in some very important missing elements such as CAPREF, - DATREF and elements of STRREF.

- - diff --git a/doc/specs/torlack/key.html b/doc/specs/torlack/key.html deleted file mode 100644 index a7ab63cc00..0000000000 --- a/doc/specs/torlack/key.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - -The KEY file in Neverwinter Nights - - - - -

What is a KEY File?

- -

The KEY file in Neverwinter Nights (NWN) is used to provide the game with a -centralized location for locating resources contained in the BIF files. To the -best of my knowledge, only one file, "chitin.key" uses this file -format.

-

The KEY file format is very basic.  It consists a header and two large -tables.  At the start of the file is the header.  It provides -information about the file format, the file format version, offset and size -information about the two tables.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000char [4]4 byte signature ("KEY ")
0x0004char [4]4 byte version ("V1  ")
0x0008UINT32Number of BIF files referenced in the key file
0x000CUINT32Number of resources (RES) contained in the key file
0x0010UINT32Offset from the start of the file to the first BIF entry
0x0014UINT32Offset from the start of the file to the first RES entry
0x0018UINT32 [10]Unknown
0x0040Total Structure Size
-
-
-

One of the two tables of interest to us is the BIF table.  This table -lists the all of the BIF files contained in the NWN data directory.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000UINT32Length of the BIF file in bytes
0x0004UINT32Offset from the start of the KEY file to the BIF file name
0x0008UINT16Length of the BIF file name in bytes.  Not including the - terminating 0.
0x000AUINT16Unknown
0x000CTotal Structure Size
-
-
-

The final table lists all of the resources contained within the BIF -files. 

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000char [16]Name of the resource
0x0010UINT16Type of the resource
0x0012UINT32BIF id of the resource
0x0016Total Structure Size
-
-
-

NOTE: It is very important that the structure packing for this structure is -set to BYTE or WORD alignment.  Failure to do so will cause the UINT32 at -offset 0x0012 to be placed at 0x0014 and thus yield buggy functionality.  -On non-Intel platforms, this alignment will cause alignment faults unless the -compiler has a "unaligned" type or special care is made when accessing -the UINT32.

-

When NWN needs to locate a resource, it can be done just using the resource -name.  If the name is used by more than one resource type, then the type -would also be required to locate the resource.  To locate the resource, the -RES table is be searched for a match.  Once a match has been located, the -BIF id of the resource gives the exact location of the resource.

-

A BIF id is actually two numbers combined into one UINT32.  The first 20 -bits starting from bit 0 specify the resource index inside the bif.  The -final 12 bits specify the BIF in the BIF table contained within the KEY -file. 

-

Example: Suppose NWN needed to locate the resource -"IT_GOLD001".  It would start by searching the RES table for the -entry "IT_GOLD001".  Once located in the RES table, the BIF id is -examined.  Suppose with BIF id is 0x00400029.  The lower 20 bits of -the BIF id tell NWN that the "IT_GOLD001" resource is the 0x29th or -41st resource in the BIF file.  From the upper 12 bits, NWN knows that the -4th BIF file contains the resource.  So, the 4th record is examined in the -BIF table.  This yields the file name for the BIF.  The BIF can then -be opened and the 41st resource read from the file.  (Information on how -the 41st resource in the BIF file is located will be covered in the BIF file -paper.)

- -

For more information on the resource types, see NWN -Data File Basics.

- - - - diff --git a/doc/specs/torlack/mod.html b/doc/specs/torlack/mod.html deleted file mode 100644 index ff2fbe66a8..0000000000 --- a/doc/specs/torlack/mod.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - -What are Module File - - - - -

Official BioWare Documentation

-

ERF Format Document: http://nwn.bioware.com/developers/erf.html
-
BioWare's "For Developers" site: http://nwn.bioware.com/developers/

- -

What are ERF Files?

-

First off, the ERF file format is a file format used by many different -files in NWN.  This include files such as saved games (SAV), HAK packs (HAK), -exported resource files (ERF), Neverwinter Nights modules (NWM), and of course, -module files (MOD).  ERF stands for Encapsulated Resource File.

-

ERF files are relatively simple files that contain a collection of other -game resources.  They also provide space for a textual description of the -contents of the file.  In the case of modules, this is the module -description.

-

ERF File Header

-

Like almost all data files in NWN, the ERF file begins with a header.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000CHAR [4]File type signature.  Depending on the file type, - this value varies.
0x0004CHAR [4]Version number
0x0008UINT32Number of strings in the description
0x000CUINT32Total length of the strings in bytes
0x0010UINT32Number of resources in the file
0x0014UINT32Offset from the start of file to the first string
0x0018UINT32Offset from the start of file to the first resource - structure
0x001CUINT32Offset from the start of file to the first position - structure
0x0020UINT32Year the file was created with 1900 being 0
0x0024UINT32Day of the year the file was created with Jan 1st being 0
0x0028UINT32String reference for the description.
- Varies depending on the file type:
- MOD = -1, SAV = 0, NWM = -1
- For ERF and HAK, this value is unpredictable
0x002CUINT32 [29]Spare values all initialized to 0
0x00A0Total size of the structure
-
-
-

Strings in the ERF Files

- -

Utilities such as the module editing tool and HAK pack editor from Bioware -allow the user to specify strings that describe the contents of the module or -HAK pack.  These utilities also allow the user to supply this information -in multiple languages.  Each string is stored sequentially starting at the -offset specified in the ERF file header.

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000UINT32Language Identifier (See the document on data file - basics)
0x0004UINT32Length of the string in characters
0x0008CHAR [*]String data
0x0008+LengthTotal size of the structure
-
-
- -

Resource Structures

-

For each resource contained in an ERF, there is a corresponding entry in -the resource list.  This list begins in the file at the offset specified in -the header.  The resource structures are stored sequentially in the ERF file.  The -ERF header specifies the number of resources.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000CHAR [16]Name of the resource
0x0010UINT32Index of the resource
0x0014UINT16Resource type (See the document on data file basics)
0x0016UINT16Reserved and zero
0x0018Total size of the structure
-
-
-

The name of the resource is padded with NULLs however they are not -necessarily NULL terminated.  In the case of a resource name that is 16 -characters long, no NULL will exist.  It can be mixed case, but most all resources -names are lowercase.

-

Position Structures

-

For each resource, there is a corresponding position structure that defines -where the resource is located in the ERF.  This list begins in the file -at the offset specified in the header.  It is also sequentially stored in -the ERF file.

-
-
- - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000UINT32Offset from the start of the file to the given resource
0x0004UINT32Length of the resource in bytes
0x0008Total size of the structure
-
-
- -

Strange Blank Data

-

The people at the OpenKnights -project noticed that for MOD and NWM files only, there exists a strange block of data -between the resource structures and the position structures.  The size of -the block seems to be eight bytes per resource.  In my tests, all the data -seemed to be NULL.  What is strange about this data is that there doesn't -exist an offset in the ERF file header.  Having a block of data that -contains no offset in the file header is very uncharacteristic for Bioware.

- - - - \ No newline at end of file diff --git a/doc/specs/torlack/ncs.html b/doc/specs/torlack/ncs.html deleted file mode 100644 index a4721ba08e..0000000000 --- a/doc/specs/torlack/ncs.html +++ /dev/null @@ -1,1845 +0,0 @@ - - - - - - - -NWN Data Files - NCS File - - - - -

NWScript Basics

-

Before we can really being discussing the actual contents of the NWScript -Compiled Script (NCS) file, we first must go over some of the basic concepts of -the NWScript engine.

-

NWScript is a small instruction set byte code engine.  This means that -instead of compiling the script into x86 machine instructions, the compiler -generates a series of platform independent commands.  Languages such as -Forth or Java use similar techniques to store their compiled source.  When -a script needs to be executed, current byte code is fetched from the compiled -script and then depending on the value of the byte code, the script engine -executes some predefined operation.

-

NWScript Stack Basics

-

In a real machine code program, local variables can be stored in specific memory -locations, memory relative to a stack pointer, or even in the CPU registers.  -Byte code engines don't have the luxury of using CPU registers.  Thus they -are limited to variables being stored in specific memory locations or relative -to a stack pointer.  In the case of NWScript, all variables are accessed -relative to a stack pointer.  Global variables don't exist in the -traditional sense.

-

Since variables are all accessed off the stack without CPU registers, then -operators such as addition must operate differently.  In machine code, if -you wished to add two values, the values would be loaded into registers and then -the operator is executed.  (Note: That is a drastic simplification)  With NWScript, operators always use the top most variable or variables on the stack.  -Once the operation is complete, the variables are removed and the result is -placed on the stack.  Thus, if you have a variable called "nValue" that you -wished to negate but not lose "nValue" on the stack, you would first have make a -copy of the variable onto the top of the stack and then invoke the operator.

-

Let us look at an example program:

-
-
void main ()
-{
-    int i = 12;
-    int j = 1;
-    i = i + j;
-}
-
-

The first two lines of the program declare two variables "i" and "j".  -Once these two statements are complete, the stack looks as follows:

-
-
- - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4j: 1
-8i: 12
Bottom of Stack
-
-
-

It is important to note that the NWScript stack builds up in increments of 4.  -Thus when accessing values on the stack, they must be referenced using negative -offsets where "-4" points to the top most element on the stack.

-

The next step is to make a copy of "i" so that we can operate on it.

-
-
- - - - - - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4i: 12
-8j: 1
-12i: 12
Bottom of Stack
-
-
-

Next, we need to make a copy of "j".

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4j: 1
-8i: 12
-12j: 1
-16i: 12
Bottom of Stack
-
-
-

Now that we have the two values on the top of the stack, we can invoke the -operator to compute the results.

-
-
- - - - - - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4results: 13
-8j: 1
-12i: 12
Bottom of Stack
-
-
-

The final step is the assignment.  To do this, we copy the top of the -stack down to the variable and then remove the top of the stack.

-
-
- - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4j: 1
-8i: 13
Bottom of Stack
-
-
-

The current top of stack is also known as the stack pointer (SP).

-

NWScript Global Variables

-

As stated previously, NWScript does not have global variables in the -tradition sense where the values a stored in a known region of memory.  In -NWScript, global variables are placed onto the stack by a dummy shell routine.  -This routine wraps the "main" or "StartingConditional" routine.  So when a -script is executed with global variables, "main" and "StartingConditional" are -not the first routines to be invoked.  The "#globals" routine is invoked to -place the globals onto the stack and then it invokes "main" or "StartingConditional".

-

However, placing global variables on the stack is only half the problem.  -Routines inside the script must be able to know how to reference the variables.  -For a routine such as "main" that is invoked directly from "#globals", it knows -how deep down in the stack the global variables would be.  If it had two -local variables and needed to access the top global variable, it could use an -offset of -12 (3 variables down time -4).  However, subroutines called by -"main" would have no idea how deep down the stack the global variables exist.  -

-

To solve this problem, Bioware created a second stack pointer called "BP" -which is traditionally called base pointer for Intel processors.  Inside -the "#globals" routine just prior to invoking "main" or "StartingConditional", -the current stack pointer (SP) is saved and becomes the new value of BP.  -Then when "main" or and subroutine needs to access a global variable, it just -needs to access them relative to BP.

-

For simplicity, there are not many operations that can be done to a variable -relative to BP.  A copy of a variable can be placed on the top of the -stack.  The current top of stack can be assigned to a variable relative to -BP.  And a variable relative to BP can be incremented or decremented.

-

Calling Subroutines and Engine Routines (ACTIONS)

-

Invoking subroutines or engine routines is done basically in the same manner.  -Arguments are placed on the stack in reverse order.  The call is then made -and the callee removes all the arguments from the stack prior to returning.

-

However, return values are handled differently.  In the case of a script -subroutine that returns a value, space for the return value is reserved, then -the arguments are placed on the stack and finally the subroutine is invoked.  -In the case of an engine routine, it is the job of the engine routine to place -the return value on the stack after the calling arguments are removed.

-

Here is an example of a call to a subroutine:

-
-
int j = DoSomeScriptSubroutine (12, 14); 
-
-

Prior to the call, the stack looks as follows:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4Arg1: 12
-8Arg2: 14
-12Return: ??
-16j: ??
Bottom of Stack
-
-
-

After the call, the stack looks as follows:

-
-
- - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4Return: ??
-8j: ??
Bottom of Stack
-
-
-

Here is an example of a call to an engine routine:

-
-
int j = DoSomeEngineRoutine (12, 14); 
-
-

Prior to the call, the stack looks as follows:

-
-
- - - - - - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4Arg1: 12
-8Arg2: 14
-12j: ??
Bottom of Stack
-
-
-

After the call, the stack looks as follows:

-
-
- - - - - - - - - - - - - - - - - - - -
Top of Stack (SP)
OffsetContents
-4Return: ??
-8j: ??
Bottom of Stack
-
-
-

Byte Code Basics

-

All NWScript byte codes start with two bytes.  The first byte is the -instruction such as "RETN" or "JSR".  The second byte is the type of the -instruction such as an integer or floating point operation.

-

Following is a list of all the different types:

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Unary Types
ValueType
3 (0x03)Integer (I)
4 (0x04)Float (F)
5 (0x05)String (S)
6 (0x06)Object (O)
16-31
- (0x10-0x1F)
Engine Types
- 16 = Effect
- 17 = Event
- 18 = Location
- 19 = Talent
Binary Types
ValueType
32 (0x20)Integer, Integer (II)
33 (0x21)Float, Float (FF)
34 (0x22)Object, Object (OO)
35 (0x23)String, String (SS)
36 (0x24)Structure, Structure (TT)
37 (0x25)Integer, Float (IF)
38 (0x26)Float, Integer (FI)
48-57
- (0x30-0x39)
Engine Types
- 48 = Effect, Effect
- 49 = Event, Event
- 50 = Location, Location
- 51 = Talent, Talent
58 (0x3A)Vector, Vector (VV)
59 (0x3B)Vector, Float (VF)
60 (0x3C)Float, Vector (FV)
-
-
-

The value listed in parenthesis next to the type name is the short hand name -used to identify different byte codes.  For example ADDII would add two -integer values.

-

The TT opcode type is used to compare ranges of elements on the stack.  -More specifically, it is used for structures and vectors.

-

Byte Codes

-

Following is a list and description of all the known byte codes. 

-

NOTE: All multi-byte values are stored in big endian order.

-

CPDOWNSP - Copy Down Stack Pointer

-

Copy the given number of bytes from the top of the stack down to the location -specified.

-

The value of SP remains unchanged.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x01Byte Code
10x01Type
2-5Offset Destination of the copy relative to the top of the stack.
6-7SizeNumber of bytes to copy
-
-

RSADDx- Reserve Space on Stack
-RSADDI- Reserve Integer Space on Stack
-RSADDF- Reserve Float Space on Stack
-RSADDS- Reserve String Space on Stack
-RSADDO- Reserve Object Space on Stack

-

Reserve space on the stack for the given variable type.

-

The value of SP is increased by the size of the type reserved.  (Always -4)

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x02Byte Code
10x03
- 0x04
- 0x05
- 0x06
RSADDI type
- RSADDF type
- RSADDS type
- RSADDO type
-

CPTOPSP - Copy Top Stack Pointer

-

Add the given number of bytes from the location specified in the stack to the -top of the stack.

-

The value of SP is increased by the number of copied bytes.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x03Byte Code
10x01Type
2-5Offset Source of the copy relative to the top of the stack.
6-7SizeNumber of bytes to copy
-
-

CONSTI - Place Constant Integer Onto the Stack

-

Place the constant integer onto the top of the stack.

-

The value of SP is increased by the size of the type reserved.  (Always -4)

-
- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x04Byte Code
10x03Type
2-5IntegerInteger value of the constant
-
-

CONSTF - Place Constant Float Onto the Stack

-

Place the constant float onto the top of the stack.

-

The value of SP is increased by the size of the type reserved.  (Always -4)

-
- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x04Byte Code
10x04Type
2-5FloatFloat value of the constant
-
-

CONSTS - Place Constant String Onto the Stack

-

Place the constant string onto the top of the stack.

-

The value of SP is increased by the size of the type reserved.  (Always -4)

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x04Byte Code
10x05Type
2-3String LengthLength of the string
4-nString DataText of the string
-
-

CONSTO - Place Constant Object ID Onto the Stack

-

Place the constant object ID onto the top of the stack.

-

The value of SP is increased by the size of the type reserved.  (Always -4)

-
- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x04Byte Code
10x06Type
2-5ObjectWhen the type is an object, these bytes contain the OID
-
-

ACTION - Call an Engine Routine

-

Invoke the engine routine specified.  All arguments must be placed on -the stack in reverse order prior to this byte code.  The arguments will be -removed by the engine routine and any return value then placed on the stack.

-

The value of SP is increased by the size of the return value and decreased by -the total size of the arguments.  It is important to note that the total -size of the arguments might be different than the number of arguments.  -Structures and vectors are take up more space than normal types.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x05Byte Code
10x00Type
2-3Routine #Number of the action routine.  NWSCIPT.NSS lists engine routines - in order starting at 0
4Arg CountNumber of arguments
-
-

LOGANDII - Logical AND Two Integers

-

Compute the logical AND of two integer values.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x06Byte Code
10x20Type
-

LOGORII - Logical OR Two Integers

-

Compute the logical OR of two integer values.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x07Byte Code
10x20Type
-

INCORII - Bitwise Inclusive OR Two Integers

-

Compute the inclusive OR of two integer values.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x08Byte Code
10x20Type
-

EXCORII - Bitwise Exclusive OR Two Integers

-

Compute the exclusive OR of two integers.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x09Byte Code
10x20Type
-

BOOLANDII - Boolean or Bitwise AND Two Integers

-

Compute the boolean AND of two integers.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x0AByte Code
10x20Type
-

EQUALxx - Test for Logical Equality
-EQUALII - Test for Logical Equality Two Integers
-EQUALFF - Test for Logical Equality Two Floats
-EQUALSS - Test for Logical Equality Two Strings
-EQUALOO - Test for Logical Equality Two Object IDs

-

Test the two operand for logical equality.  This operator supports the -comparison or all the basic types and then engine types as long as both operands -have the same type.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x0BByte Code
10x20
- 0x21
- 0x22
- 0x23
- 0x30-0x39
EQUALII Type
- EQUALFF Type
- EQUALOO Type
- EQUALSS Type
- For engine types
-

EQUALTT - Test for Logical Equality Two Structures

-

Test the two operand for logical equality.  This operator supports the -comparison or all the basic types and then engine types as long as both operands -have the same type.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x0BByte Code
10x24Type
2-3SizeSize of structure
-

NEQUALxx - Test for Logical Inequality
-NEQUALII - Test for Logical Inequality Two Integers
-NEQUALFF - Test for Logical Inequality Two Floats
-NEQUALSS - Test for Logical Inequality Two Strings
-NEQUALOO - Test for Logical Inequality Two Object IDs

-

Test the two operand for logical inequality.  This operator supports the -comparison or all the basic types and then engine types as long as both operands -have the same type.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x0CByte Code
10x20
- 0x21
- 0x22
- 0x23
- 0x30-0x39
EQUALII Type
- EQUALFF Type
- EQUALOO Type
- EQUALSS Type
- For engine types
-

NEQUALTT - Test for Logical Inequality Two Structures

-

Test the two operand for logical inequality.  This operator supports the -comparison or all the basic types and then engine types as long as both operands -have the same type.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x0CByte Code
10x24Type
2-3SizeSize of the structure
-

GEQxx - Test for Greater Than or Equal
-GEQII - Test for Greater Than or Equal Two Integers
-GEQFF - Test for Greater Than or Equal Two Floats

-

Test the two operand for logically greater than or equal.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x0DByte Code
10x20
- 0x21
GEQII Type
- GEQFF Type
-

GTxx - Test for Greater Than
-GTII - Test for Greater Than Two Integers
-GTFF - Test for Greater Than Two Floats

-

Test the two operand for logically greater than.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x0EByte Code
10x20
- 0x21
GTII Type
- GTFF Type
-

LTxx - Test for Less Than
-LTII - Test for Less Than Two Integers
-LTFF - Test for Less Than Two Floats

-

Test the two operand for logically less than.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x0FByte Code
10x20
- 0x21
LTII Type
- LTFF Type
-

LEQxx - Test for Less Than or Equal
-LEQII - Test for Less Than or Equal Two Integers
-LEQFF - Test for Less Than or Equal Two Floats

-

Test the two operand for logically less than or equal.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x10Byte Code
10x20
- 0x21
LEQII Type
- LEQFF Type
-

SHLEFTII - Shift the Integer Value Left

-

Shift the value left be the given number of bits.  Operand one is the -value to shift while operand two is the number of bits to shift.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x11Byte Code
10x20Type
-

SHRIGHTII - Shift the Integer Value Right

-

Shift the value right be the given number of bits.  Operand one is the value to shift while operand two is the -number of bits to shift.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x12Byte Code
10x20Type
-

USHRIGHTII - Unsigned Shift the Integer Value Right

-

Shift the value right be the given number of bits as if it was an unsigned -integer and not a signed integer.  Operand one is the value to shift while operand two is the number of bits to -shift.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x13Byte Code
10x20Type
-

ADDxx - Add Two Values
-ADDII - Add Two Integer Values
-ADDIF - Add an Integer and Float Values
-ADDFI - Add a Float and Integer Values
-ADDFF - Add Two Float Values
-ADDSS - Add Two String Values
-ADDVV - Add Two Vector Values

-

Add the two operands.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x14Byte Code
10x20
- 0x25
- 0x26
- 0x21
- 0x23
- 0x3A
ADDII Type
- ADDIF Type
- ADDFI Type
- ADDFF Type
- ADDSS Type
- ADDVV Type
-

SUBxx - Subtract Two Values
-SUBII - Subtract Two Integer Values
-SUBIF - Subtract an Integer and Float Values
-SUBFI - Subtract a Float and Integer Values
-SUBFF - Subtract Two Float Values
-SUBVV - Subtract Two Vector Values

-

Subtract the two operands. 

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x15Byte Code
10x20
- 0x25
- 0x26
- 0x21
- 0x3A
SUBII Type
- SUBIF Type
- SUBFI Type
- SUBFF Type
- SUBVV Type
-

MULxx - Multiply Two Values
-MULII - Multiply Two Integer Values
-MULIF - Multiply an Integer and Float Values
-MULFI - Multiply a Float and Integer Values
-MULFF - Multiply Two Float Values
-MULVF - Multiply a Vector and Float Values
-MULFV - Multiply a Float and Vector Values

-

Multiply the two operands.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x16Byte Code
10x20
- 0x25
- 0x26
- 0x21
- 0x3B
- 0x3C
MULII Type
- MULIF Type
- MULFI Type
- MULFF Type
- MULVF Type
- MULFV Type
-

DIVxx - Divide Two Values
-DIVII - Divide Two Integer Values
-DIVLIF - Divide an Integer and Float Values
-DIVFI - Divide a Float and Integer Values
-DIVFF - Divide Two Float Values
-DIVVF - Divide a Vector and Float Values

-

Divide the two operands.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x17Byte Code
10x20
- 0x25
- 0x26
- 0x21
- 0x3B
DIVII Type
- DIVIF Type
- DIVFI Type
- DIVFF Type
- DIVVF Type
-

MODII- Compute the Modulus of Two Integer Values

-

Computes the modulus of two values.

-

The value of SP is increased by the size of the result while decreased by the -size of both operands.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x18Byte Code
10x20Type
-

NEGx - Compute the Negation of a Value
-NEGI - Compute the Negation of an Integer Value
-NEGF - Compute the Negation of a Float Value

-

Computes the negation of a value.

-

The value of SP remains unchanged since the operand and result are of the -same size.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x19Byte Code
10x03
- 0x04
NEGI Type
- NEGF Type
-

COMPI - Compute the One's Complement of an Integer Value

-

Computes the one's complement of a value.

-

The value of SP remains unchanged since the operand and result are of the -same size.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x1AByte Code
10x03Type
-

MOVSP - Adjust the Stack Pointer

-

Add the value specified in the instruction to the stack pointer.

-

The value of SP is adjusted by the value specified.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x1BByte Code
10x00Type
2-5OffsetValue to add to the stack pointer.
-

STORE_STATEALL - Store the Current State of the Stack (Obsolete)

-

Obsolete instruction to store the state of the stack and save a -pointer to a block of code to later be used as an "action" argument.  This -byte code is always followed by a JMP and then a block of code to be executed by -a later function such as a DelayCommand.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x1CByte Code
10x08Offset to the block of code for an "action" argument
-

JMP - Jump to a New Location

-

Change the current execution address to the relative address given in the -instruction.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x1DByte Code
10x00Type
2-5OffsetOffset to the new program location from the start of this instruction
-

JSR - Jump to Subroutine

-

Jump to the subroutine at the relative address given in the instruction.  -If the routine returns a value, the RSADDx instruction should first be used to -allocate space for the return value.  Then all arguments to the subroutine -should be pushed in reverse order.

-

The value of SP remains unchanged.  The return value is NOT placed on -the stack.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x1EByte Code
10x00Type
2-5OffsetOffset to the new program location from the start of this instruction
-

JZ - Jump if Top of Stack is Zero

-

Change the current execution address to the relative address given in the -instruction if the integer on the top of the stack is zero.

-

The value of SP is decremented by the size of the integer.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x1FByte Code
10x00Type
2-5OffsetOffset to the new program location from the start of this instruction
-

RETN - Return from a JSR

-

Return from a JSR.  All arguments used to invoke the subroutine should -be removed prior to the RETN.  This leaves any return value on the top of -the stack.  The return value must be allocated by the caller prior to -invoking the subroutine.

-

The value of SP remains unchanged.  The return value is NOT placed on -the stack.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x20Byte Code
10x00Type
-

DESTRUCT - Destroy Element on the Stack

-

Given a stack size, destroy all elements in that size excluding the given -stack element and element size.

-

The value of SP decremented by the given stack size minus the element size.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x21Byte Code
10x01Type
2-3SizeTotal number of bytes to remove off the top of the stack
4-5OffsetOffset from the start of the bytes to remove to the element not to - destroy
6-7SizeSize of the element not to destroy
-

NOTI - Compute the logical NOT of an Integer Value

-

Computes the logical not of the value.

-

The value of SP remains unchanged since the operand and result are of the -same size.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x22Byte Code
10x03Type
-

DECISP - Decrement Integer Value Relative to Stack Pointer

-

Decrements an integer relative to the current stack pointer.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x23Byte Code
10x03Type
2-5OffsetOffset of the integer relative to the stack pointer
-

INCISP - Increment Integer Value Relative to Stack Pointer

-

Increments an integer relative to the current stack pointer.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x24Byte Code
10x03Type
2-5OffsetOffset of the integer relative to the stack pointer
-

JNZ - Jump if Top of Stack is Non-Zero

-

Change the current execution address to the relative address given in the -instruction if the integer on the top of the stack is non-zero.

-

The value of SP is decremented by the size of the integer.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x25Byte Code
10x00Type
2-5OffsetOffset to the new program location from the start of this instruction
-

CPDOWNBP - Copy Down Base Pointer

-

Copy the given number of bytes from the base pointer down to the location -specified. This instruction is used to assign new values to global variables.

-

The value of SP remains unchanged.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x26Byte Code
10x01Type
2-5Offset Destination of the copy relative to the base pointer
6-7SizeNumber of bytes to copy
-
-

CPTOPBP - Copy Top Base Pointer

-

Add the given number of bytes from the location specified relative to the -base pointer to the -top of the stack.  This instruction is used to retrieve the current value -of global variables.

-

The value of SP is increased by the number of copied bytes.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x27Byte Code
10x01Type
2-5Offset Source of the copy relative to the base pointer
6-7SizeNumber of bytes to copy
-
-

DECIBP - Decrement Integer Value Relative to Base Pointer

-

Decrements an integer relative to the current base pointer.  This -instruction is used to decrement the value of global variables.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x28Byte Code
10x03Type
2-5OffsetOffset of the integer relative to the base pointer
-

INCIBP - Increment Integer Value Relative to Base Pointer

-

Increments an integer relative to the current base pointer.  This -instruction is used to increment the value of global variables.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x29Byte Code
10x03Type
2-5OffsetOffset of the integer relative to the base pointer
-

SAVEBP  - Set a New Base Pointer Value

-

Save the current value of the base pointer and set BP to the current stack -position.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x2AByte Code
10x00Type
-

RESTOREBP - Restored the BP

-

Restore the BP from a previous SAVEBP instruction.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x2BByte Code
10x00Type
-

STORE_STATE - Store the Current Stack State

-

Store the state of the stack and save a -pointer to a block of code to later be used as an "action" argument.  This -byte code is always followed by a JMP and then a block of code to be executed by -a later function such as a DelayCommand.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - - - - - - - - - - - -
BytesValueDescription
00x2CByte Code
10x10Offset to the block of code for an "action" argument
2-5SizeSize of the variables to save relative to BP.  This would be all - the global variables.
6-9SizeSize of the local routine variables to save relative to SP.
-

NOP - No-operation

-

Perform no program function.  This opcode is used as a placeholder for -the debugger.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x2DByte Code
10x00Type
-

T - Program Size

-

This byte code isn't a real instruction and is always found at offset 8 in -the NCS file.

-

The value of SP remains unchanged.

- - - - - - - - - - - - - - - - -
BytesValueDescription
00x42Byte Code
1-4SizeSize of the NCS file
- - - - \ No newline at end of file diff --git a/doc/specs/torlack/plt.html b/doc/specs/torlack/plt.html deleted file mode 100644 index 67e7aeb92f..0000000000 --- a/doc/specs/torlack/plt.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - -NWN Texture Palette File - - - - -

What is a Texture Palette File (PLT)?

- -

A texture palette file is a variant on a standard texture.  However, -instead of being a fixed texture where all the colors are constant, a texture -palette file allows the content creator to select from a wide range of color -palettes for preset areas in the texture.

- -

There are four basic elements that go into converting a PLT to a real texture -at runtime.  Some elements are supplied by the PLT file while others are -supplied by the game itself and the content creator.

-
    -
  • Palette Group - An index value from 0 to 9 that selects which palette - group is to be used (from PLT file).
  • -
  • Palette Index  - An index value from 0 to 255 that selects which - palette to use in a palette group (from the content creator).
  • -
  • Color Index - An index value from 0 to 255 that selects a specific color - from the given palette (from PLT file).
  • -
  • Palette File - A file that contains the actual color values for each - palette in a group (supplied by game).
  • -
- -

Palette Groups 

- -

There are a total of ten different palette groups. The palette name is a -human readable name displayed in the user interface.  The group index is -the index of the palette group.  This matches the indices found in the PLT -files.  The UI graphic is the palette as it is presented to the content -creator.  Each UI graphic contains 256 different selections that corresponds -directly to a row in the actual palette file in the last column of the table.

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameGroup IndexUIPalette File
Skin0
Hair1
Metal 12
Metal 23
Cloth 14
Cloth 25
Leather 16
Leather 27
Tattoo 18
Tattoo 29
- -
-
- -

Texture Palette File Format

- -

The PLT file beings with a fixed header that defines the attributes of the -texture.  Following the header is pixel information.

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000char [4]4 byte signature ("PLT ")
0x0004char [4]4 byte version ("V1  ")
0x0008UINT32Unknown
0x000CUINT32Unknown
0x0010UINT32Width of the texture in pixels
0x0014UINT32Height of the texture in pixels
0x0018Total Structure Size
-
-
-

The actual pixel data is an array of the following structure.  There are -(width X height) instances of this structure in a palette file.

-
-
- - - - - - - - - - - - - - - - - - - - -
OffsetTypeDescription
0x0000UINT8Color Index
0x0001UINT8Palette Group Index
0x0002Total Structure Size
-
-
-

Example

-

Imagine one of the body textures used to skin a model.  From this PLT -file, we have the width and height of the texture.  When NWN started, it -loads the palette files into memory.  Finally, for a specific creature, we -have 10 different values, one for each of the different palette groups.  -These values specify which palette is to be used.  These values are -supplied by the content creator.

-

For each of the pixels in the PLT texture, we have a color index and a -palette group index.  To locate the actual color value for that pixel, the -following process is used.

-
    -
  1. Using the palette group index for the pixel in the texture, get the - palette index for that palette group as specified by the 10 values supplied - by the content creator.
  2. -
  3. The palette index corresponds to a row in the palette file for given - palette group.  This row become our palette for the pixel.
  4. -
  5. Take the color index for the pixel in question and retrieve the color from - the selected palette.
  6. -
-

Credits

-

I have no idea who originally decoded the PLT file format.  It wasn't -me.  I read a very sketchy description on the Bioware forums and then -filled in the blanks.  Thanks whoever you are.

- - - -