How to make an export script out of this?

Hello, this is my first time posting here. I am a complete novice with Maxscript. I have knowledge of 3ds Max and stuff but I can't seem to solve this problem. I want to make an export script for an old game that still has a following. I want to be able to export back to the game's original format. The game is Jurassic Park Operation Genesis. There is a (crude) import script done long ago and some information of how the models work, but no one has ever gotten an export script to work. I'd like to remedy that, I've tried to get it to work myself but I just don't have enough knowledge here.

Now I didn't make these scripts but here is what I think is a script layout of the TMD model, the format of the game's models.

struct TMDFile {
char[4] magic = "TMDL";
uint unknown1 = 0x00;
uint data_size; // = filesize - 12

char[8] catagory; // eg. "STRUCT", "MISC", "FLORA"
uint unknown2;
char[4] unknown3 = "IX2",0x00;
uint graph_size; // same as graph_size2

uint unknown4; // this is position 0x20 in file. Related to
// FirstPart.unknown2 at file position 0x74
uint unknown5;
uint unknown6;
uint unknown7[4]; // = {0,0,0,0} always?

uint graph_size2; // same as graph_size
}

// The SceneGraphBlock starts at file position 0x40.
// sizeof(SceneGraphBlock) = TMDFile.graph_size
struct SceneGraphBlock {
short numNodes;
short N; //

short P; //
short Q; //
uint unknown1[11]; // always = {0x00, 0x00, ...}?

uint unknown2; // related to TMDFile.unknown4 (at file position 0x20)

array of struct Node { // sizeof(Node) = 0xB0
float4 rotation; // quaternion

float16 transform; // position float3 starts at transform[12]
float16 invtransform;
byte node_name_length; // number of non-zero chars
char node_name[15];
short parent; // index of parent node

short no_mesh; // 0 if node has a mesh, or if a child has a mesh
// 1 otherwise
float3 pivot_offset; // usually equals transform.position
} nodes[num_nodes];

uint unknown3; // related to TMDFile.unknown4 (at file position 0x20)

uint unknown4; // = 0x71655340 always?
uint unknown5; // = 0x10 always?
uint unknown6[4]; // = {0,0,0,0} always?

uint O; //

uint unknown7; // = 0x3D088889 always?
uint unknown8[num_nodes]; // related to TMDFile.unknown4? (at file position 0x20)

uint unknown9[3]; // = {0x00200000, 0, 0} always?
uint unknown7; // = 0x3D088889 always?

// Kiosk.tmd has two sets of these tables, and has P = Q = 2 instead of 1
array[O] of struct {

uint block_num; // numbered in reverse order
...
}
}

// MeshBlock starts at immediately after the ScenGraph Block
// ie. at file position 0x40+TMDFile.graph_size
struct MeshBlock {
uint unknown1;
uint num_meshes;
uint unknown2 = 0x00;
uint unknown3[4]; // last two sometimes floats

array of struct Mesh {
uint unknown1; // = 0x01 always?
uint tri_strip_size;
uint num_verts;
char material_name[32];
uint A;
uint B;
uint num_nodes;
uint B2; // = B always

float unknown2[6];
uint nodes[num_nodes]; // indexes of scene graph nodes using this mesh

array of struct Vertex {
float3 position;
float3 unknown1; // normal?

float2 unknown2;
float2 texpos;
} verts[num_verts];

// Indexed triangle strip. If any two vertex indicies for a triangle
// are the same, skip it. Eg. vertex indicies for triangles are in:
// {tri_strip[0,1,2], tri_strip[3,2,1], tri_strip[2,3,4],
// tri_strip[5,4,3], tri_strip[4,5,6], tri_strip[7,6,5], ...}
short tri_strip[tri_strip_size];

} meshes[num_meshes];
}

And here's the import maxscript, both were done by the same guy. This import script was also for another game as well.

-- Utility to import X-Isle .CGF models, and Jurassic Part Operation Genesis .TMD models
-- Textures must have been extracted from the material libraries into a format that
-- MAX recognises (place in import directory with model). Run script, select model file.

messageBox "CGF/TPM Import Utility. Select from the Utilities dropdown" title:"CGF/TMD Import"

utility ImportCGF "CGF/TPM Import"
(
local currentPath

--------------------
-- User Interface --
--------------------
group "About"
(
label lab1 "CGF/TMD Import v0.0 alpha"
hyperLink addy "by Andres James" align:#center address:"mailto:[email protected]" color:(color 0 0 170) hoverColor:(color 170 0 0)
button dlgImportHelp "HELP"
)

on dlgImportHelp pressed do (
local helpStr = "Utility to import JPOG .TPM models, and X-Isle .CGF models. See the Readme.txt for details.

NOTE: This is a pre-release Alpha, don't expect it to work! (But it worked for me)"
messageBox helpStr title:"CGF/TMD Import Help"
)

group "Import"
(
button importFileButton "Import CGF/TMD..." toolTip:""
)

------------------------------------------------------------------------------
-- Misc functions
------------------------------------------------------------------------------

-- Swap a file's extension
fn ChangeExt filename newExt=
(
(getFilenamePath filename) + (getFilenameFile filename) + newExt
)

-- Replacement for doesFileExist which seems to give false positives
fn DoesFileReallyExist filename=
(
local temp = fopen filename "rb"
if temp != undefined then (
fclose temp
true
) else (
false
)
)

-- Read a string of fixedLen chars
fn ReadFixedString bstream fixedLen = (
local str = ""
for i = 1 to fixedLen do (
local ch = ReadByte bstream #unsigned
str += bit.intAsChar ch
if ch == 0 then (
-- Ignore junk
fseek bstream (fixedLen - i) #seek_cur
exit
)
)
str
)

------------------------------------------------------------------------------
-- Texture management
-- I borrowed this from someone else's code... hope they don't mind :)
------------------------------------------------------------------------------

local g_textureNames = #()
local g_textures = #()

-- Clear the texture cache
fn ClearTextureCache=
(
g_textureNames = #()
g_textures = #()
)

-- Add a texture to the cache and return the loaded bitmaptexture
fn AddTextureToCache name fname=
(
local bmpTex = bitmapTexture filename:fname
-- Don't add invalid names
if name != "" then
(
append g_textureNames name
append g_textures bmpTex
)
bmpTex
)

local g_modelPath = ""
local g_q3ImageExts = #( ".bmp", ".tga", ".jpg", ".jpeg" )

-- Find a texture given a PK3 filename
fn FindTexture filename ignoreMissing:false =
(
-- First of all, check the cache
local index = findItem g_textureNames filename
if index != 0 then
(
return g_textures[index]
)

-- Not in cache so we'll try searching in the model's directory
local localFilename = g_modelPath + (fileNameFromPath filename)

-- Thanks to the Discreet/Tempest guys for the for ext... bit!
for ext in g_q3ImageExts do
(
local imgName = ChangeExt localFilename ext

if DoesFileReallyExist imgName then
(
return (AddTextureToCache filename imgName)
)
if ignoreMissing == false then
format "Failed on %\n" imgName
)

-- We're out of luck so ask the user to find it for us!
local foundFilename
--if promptForMissing.checked do
if false then
(
messageBox ("Can't find texture " + filename + "\nPlease click OK and locate it.")

foundFilename = getOpenFileName caption:("Find Texture " + filename) filename:localFilename \
types:"All supported (*.tga;*.jpg;*.jpeg)|*.tga;*.jpg;*.jpeg|All Files (*.*)|*.*|"
)

if foundFilename != undefined then
(
return (AddTextureToCache filename foundFilename)
)
else
(
if ignoreMissing == false then
format "Warning: Could not find % or any variant!\n" filename
append g_textureNames filename
append g_textures undefined
)
undefined
)

fn SetupMaterialTextures mat diffuseMap opacityMap = (
if diffuseMap != "" then (
local texMap = FindTexture diffuseMap
if texMap != undefined then (
mat.maps[2] = texmap
mat.mapEnables[2] = true
showTextureMap mat texmap true
)
)
if opacityMap != "" then (
local texMap = FindTexture opacityMap ignoreMissing:true
if texMap != undefined then (
mat.maps[7] = texmap
mat.mapEnables[7] = true
showTextureMap mat texmap true
)
)
showTextureMap mat on
)

------------------------------------------------------------------------------
-- Loader for X-Isle .CGF files
------------------------------------------------------------------------------

local gMaterials = undefined

fn ReadCGFMaterial bstream = (
ReadLong bstream #unsigned
ReadLong bstream #unsigned
local numMats = ReadLong bstream #unsigned
format "\nBlock: Materials numMaterials: %\n" numMats
gMaterials = multiMaterial numsubs:numMats name:"TestMaterial"
local curMatNum = 1
for i=1 to numMats do (
local name = ReadFixedString bstream 32
format "Material: %\n" name
fseek bstream 32 #seek_cur
fseek bstream 10 #seek_cur
local diffuseMap = ReadFixedString bstream 32
local opacityMap = ReadFixedString bstream 32
local bumpMap = ReadFixedString bstream 32
fseek bstream 14 #seek_cur
-- @hack, seems that materials with no texture map are skipped.
-- If not skipped, the mesh face material indicies are all wrong.
--if diffuseMap == "" then continue

format " diffuseMap:% opacityMap:% bumpMap:%\n" diffuseMap opacityMap bumpMap
local mat = standard name:name
gMaterials[curMatNum] = mat
curMatNum += 1
if diffuseMap != "" then (
local texMap = FindTexture diffuseMap
if texMap != undefined then (
mat.maps[2] = texmap
mat.mapEnables[2] = true
showTextureMap mat texmap true
)
)
showTextureMap mat on
)
)

fn ReadCGFMesh bstream meshName = (
format "\nBlock: Mesh\n"
fseek bstream 0x10 #seek_cur
local numVerts = ReadLong bstream #unsigned
local numTVerts = ReadLong bstream #unsigned
local numFaces = ReadLong bstream #unsigned
ReadLong bstream #unsigned
ReadLong bstream #unsigned
format " numVerts: % numTVerts: % numFaces: %\n" numVerts numTVerts numFaces
--percent = ReadFloat bstream
--local bmpFilename = ReadString (texChunk.GetDataStream())

local verts = #()
local tverts = #()
local faces = #()
local tvfaces = #()
local faceMats = #()
for i = 1 to numVerts do (
append verts [ReadFloat bstream, ReadFloat bstream, ReadFloat bstream]
fseek bstream (3*4) #seek_cur
)
--format "%\n" verts
for i = 1 to numFaces do (
append faces [(ReadLong bstream #unsigned)+1, (ReadLong bstream #unsigned)+1, (ReadLong bstream #unsigned)+1]
append faceMats (ReadLong bstream #unsigned)
ReadLong bstream #unsigned
)
--format "%\n" faces
format "%\n" faceMats
for i = 1 to numTVerts do (
append tverts [ReadFloat bstream, ReadFloat bstream, 0]
)
if numVerts != numTVerts then
for i = 1 to numFaces do (
append tvfaces [(ReadLong bstream #unsigned)+1, (ReadLong bstream #unsigned)+1, (ReadLong bstream #unsigned)+1]
)

-- Make mesh
local m = mesh name:meshName faces:faces vertices:verts material:gMaterials

setNumTVerts m tverts.count false
for i = 1 to tverts.count do settvert m i tverts[i]
buildTVFaces m false
if numTVerts == numVerts then
for i = 1 to faces.count do settvface m i faces[i]
else
for i = 1 to tvfaces.count do settvface m i tvfaces[i]

-- Assign material ID's NB. materials must be already defined
for i = 1 to faces.count do (
local matID = 1
--local matSlot = 1
--matID = material.materialIDList[matSlot]
setFaceMatID m i faceMats[i]
)
update m
)

fn ReadCGFObject bstream = (
ReadLong bstream #unsigned
ReadLong bstream #unsigned
local numObjs = ReadLong bstream #unsigned
format "\nBlock: Objects numObjects: %\n" numObjs
local meshNames = #()
for i=1 to numObjs do (
local name = ReadFixedString bstream 0x10
fseek bstream 0x30 #seek_cur
format "Object: %\n" name
append meshNames name
)
return meshNames
)

fn ImportCGF fileName = (
format "\nImporting % ...\n" fileName
local bstream = fopen fileName "rb"
if bstream == undefined then (
format "File % not found.\n" fileName
return false
)
g_modelPath = getFilenamePath fileName

fseek bstream 0x10 #seek_set
local dataDirOffset = ReadLong bstream #unsigned
fseek bstream dataDirOffset #seek_set
local numEntries = ReadLong bstream #unsigned
format " dataDirSize: % dataDirOffset: %\n" numEntries dataDirOffset
local dataDir = #();
for i=1 to numEntries do (
fseek bstream (4+dataDirOffset+0x0C*(i-1)) #seek_set
local dataType = ReadShort bstream #unsigned
ReadShort bstream #unsigned
ReadShort bstream #unsigned
ReadShort bstream #unsigned
local dataOffs = ReadLong bstream #unsigned
format "[dataType: % dataOffs: %]\n" dataType dataOffs
append dataDir #(dataType, dataOffs)
)
-- Read materials first so they're ready to be used by the mesh
for e in dataDir where e[1]==6 do (
fseek bstream e[2] #seek_set
ReadCGFMaterial bstream
)
--@hack, read objects, assume they correspond one to one with the mesh blocks
local meshNames = #()
for e in dataDir where e[1]==4 do (
fseek bstream e[2] #seek_set
meshNames = (ReadCGFObject bstream)
break
)
local meshNum = 1
for e in dataDir where e[1]==0 do (
fseek bstream e[2] #seek_set
ReadCGFMesh bstream meshNames[meshNum]
meshNum += 1
)
fclose bstream
)

------------------------------------------------------------------------------
-- Loader for JPOG .TMD files
------------------------------------------------------------------------------

fn ImportTMD fileName = (
--format "\nImporting % ...\n" fileName
local bstream = fopen fileName "rb"
if bstream == undefined then (
format "File % not found.\n" fileName
return false
)
if (ReadFixedString bstream 4) != "TMDL" then (
format "Unsupported file structure.\n"
return false
)

--@hack
g_modelPath = getFilenamePath fileName
--g_modelPath = "D:\\Andres\\Temp\\jpog\\textures\\"

local importDummies = false
local removeFunnyDummies = true

-- Read Hierarchy
local instances = #()
local instanceUsed = #()
fseek bstream 0x40 #seek_set
local M = ReadShort bstream #unsigned
for i=1 to M do (
fseek bstream (0x7C+(i-1)*0xB0) #seek_set
local q = quat (ReadFloat bstream) (ReadFloat bstream) (ReadFloat bstream) (ReadFloat bstream)
fseek bstream 0x30 #seek_cur
local pos = [(ReadFloat bstream), (ReadFloat bstream), (ReadFloat bstream)]
ReadFloat bstream -- throw away last matrix entry
fseek bstream 0x40 #seek_cur
local nsize = ReadByte bstream #unsigned
local name = ReadFixedString bstream 15
local parent = ReadShort bstream
local ukn = ReadShort bstream
local pivot = [(ReadFloat bstream), (ReadFloat bstream), (ReadFloat bstream)]
if ukn==0 then
format "i: % quat: % pos: % pivot: % parent: % %\n" name q pos pivot parent ukn
local d = dummy name:name pos:pos boxsize:[1,1,1]
append instances d
append instanceUsed false
if parent >= 0 then (
d.parent = instances[parent+1]
instanceUsed[parent+1] = true
)
--d.pivot = d.pos - pivot -- not working
)

-- Read Meshes
fseek bstream 0x1C #seek_set
local q = ReadLong bstream #unsigned
local offs = 0x3C+q+8
fseek bstream (offs) #seek_set
local numBlocks = ReadLong bstream #unsigned
fseek bstream 0x14 #seek_cur
for i=1 to numBlocks do (
offs = ftell bstream
ReadLong bstream #unsigned
local numStrips = ReadLong bstream #unsigned
local numVerts = ReadLong bstream #unsigned
local matName = ReadFixedString bstream 32
local numStrips2 = ReadLong bstream #unsigned
local numVerts2 = ReadLong bstream #unsigned
local numThings = ReadLong bstream #unsigned
local numStrips3 = ReadLong bstream #unsigned
fseek bstream 0x18 #seek_cur
--format " things:% " numThings
local parentNum = 1
for j=1 to numThings do (
local t = ReadLong bstream #unsigned
--format "% " t
--@hack, what to do when this lists more than one hierarchy node?
if j==1 then parentNum = t+1
)
format "\n"

local verts = #()
local tverts = #()
local faces = #()
local faceMats = #()
for i = 1 to numVerts do (
append verts [ReadFloat bstream, ReadFloat bstream, ReadFloat bstream]
for j=1 to 5 do ReadFloat bstream
--@hack, invert y coord
append tverts [ReadFloat bstream, 1-ReadFloat bstream, 0]
)
--format "%\n" verts
--format "%\n" tverts
local strip = #()
for i=1 to numStrips do
append strip ((ReadShort bstream #unsigned)+1)
--print strip
local curMat = 1
for i=1 to numStrips-2 do (
local a = strip[i]
local b = strip[i+1]
local c = strip[i+2]
if (bit.and i 1) != 1 then (
b = strip[i+2]
c = strip[i+1]
)
if a==b OR a==c OR b==c then
continue
append faces [a,b,c]
append faceMats curMat
)
--format "%\n" faces
--ormat "%\n" faceMats

local material = standard name:matName
local mat = material
--local materials = multiMaterial numsubs:1 name:matName
--local mat = standard name:"SubMaterial"
--materials[1] = mat
SetupMaterialTextures mat matName (matName+"_opa")

-- Make mesh
local meshName = matName
format "Mesh: %\n" meshName
local m = mesh name:meshName faces:faces vertices:verts material:material

setNumTVerts m tverts.count false
for i = 1 to tverts.count do settvert m i tverts[i]
buildTVFaces m false
for i = 1 to faces.count do settvface m i faces[i]

-- Assign material ID's NB. materials must be already defined
for i = 1 to faces.count do (
setFaceMatID m i 1
)

if removeFunnyDummies then
if substring instances[parentNum].name 1 2 == "@@" then (
parentNum = findItem instances instances[parentNum].parent
if parentNum==0 then parentNum=1
)

m.parent = instances[parentNum]
m.name = m.parent.name + "_mesh"
--m.pivot = m.parent.pos
instanceUsed[parentNum] = true
)

if NOT importDummies then (
for i=1 to instances.count do (
if NOT instanceUsed[i] then
delete instances[i]
)
)
fclose bstream
)

on importFileButton pressed do
(

fileName = getOpenFileName "Import CGF and TMD models" types:"TMD Files (*.tmd)|*.tmd|CGF Files (*.cgf)|*.cgf|All Files (*.*)|*.*"
if fileName != undefined then (
if matchpattern fileName pattern:"*.cgf" then (
ImportCGF fileName
)
if matchpattern fileName pattern:"*.tmd" then (
ImportTMD fileName
)
)
)

)

Now I'm not sure if there's enough to go by but if someone could help me out me and the modders of JPOG would really appreciate it.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Warman222's picture

Is this the right sub-forum

Is this the right sub-forum for this kind of thing? At the very least, could someone explain what I would need to do/know in order to make this myself?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.