Crimson Skies High Road to Revenge .x models

Skeletons, animations, shaders, texturing, converting, fixing and anything else related to read game models
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Hi there! I'm attempting to read and convert Crimson Skies' plane models (.x) into a format I can work with (.dae, .obj, .fbx, etc.) This does not appear to be the DirectX .x format, but more than likely something else. I need a way for it to export at least individual sub meshes or have them all in a hierarchy grouped instead of one single mesh with UV maps. Any help with this would be greatly appreciated!

This here is the Cinematic HR Devastator model.
https://www.dropbox.com/s/36ozeub6dlqow ... tor.x?dl=0

And here are textures to use as a reference for getting UV maps working.
https://www.dropbox.com/s/8undw9x8amhft ... r.png?dl=0 Main Texture
https://www.dropbox.com/s/2qf19vm4fizlc ... r.png?dl=0 Propellers texture

Also attached here is a simple Noesis plugin made by another person for reading and exporting .x files, although it does not preview textures, nor export with UV maps, submeshes or textures.
Thanks again for any help!
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Gathered some info on the models.
It appears the game was primarily modelled with Autodesk Maya 2003. I don't have it on my computer right now, but it may be possible to open up the .x models in Maya? I don't think year version differences would matter.
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Bump. Still looking for help.
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

And yet another bump.
Szkaradek123
Posts: 124
Joined: Sat Aug 29, 2015 1:13 pm

Re: Crimson Skies High Road to Revenge .x models

Post by Szkaradek123 »

links to images are 404.



Code: Select all

#Noesis Python model import+export test module, imports/exports some data from/to a made-up format
import math

from inc_noesis import *

#registerNoesisTypes is called by Noesis to allow the script to register formats.
#Do not implement this function in script files unless you want them to be dedicated format modules!
def registerNoesisTypes():
   handle = noesis.register("Crimson Skies (Xbox)", ".x")
   noesis.setHandlerTypeCheck(handle, noepyCheckType)
   noesis.setHandlerLoadModel(handle, noepyLoadModel) #see also noepyLoadModelRPG

   noesis.logPopup()
   
   return 1


#check if it's this type based on the data
def noepyCheckType(data):
   if len(data) < 8:
      return 0
   return 1

#load the model
def noepyLoadModel(data, mdlList):

   bs = NoeBitStream(data)
   
   fileDir=rapi.getDirForFilePath(rapi.getInputName());
   texDir= os.path.join(fileDir, '../TEXTURE/')
   ctx = rapi.rpgCreateContext()
      
   rapi.rpgSetOption(noesis.RPGOPT_SWAPHANDEDNESS, 1)
      
   bs.seek(32, NOESEEK_ABS)

   nameLength=bs.readInt()
   MeshName = (bs.readBytes(nameLength).decode("ASCII").rstrip("\0"))
   print("MeshName ", MeshName)

   matrix = NoeMat43( (
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) )
      )
   )
   
   rapi.rpgSetTransform(matrix) 

   bs.seek(24, NOESEEK_REL)#unk

   bs.seek(4, NOESEEK_REL) # 3 ??



   meshes = []
   numMeshesInObj = bs.readInt()
   #for i in range(0, numMeshesInObj):
   #   loadMesh(bs, rapi)

   #print(numMeshesInObj, "numMeshes")

   numChildsInObj= bs.readInt()
   #print(numChildsInObj, "numChildsInObj")
   for i in range(0, numChildsInObj):
      loadObj(bs, rapi)


   mdl = rapi.rpgConstructModel()

   # print(texList)
   # print(matList)

   #mdl.setModelMaterials(NoeModelMaterials(texList, matList))
   mdlList.append(mdl)
   
   rapi.rpgClearBufferBinds()
   return 1



def loadObj(bs, rapi):
   nameLength=bs.readInt()
   MeshName = (bs.readBytes(nameLength).decode("ASCII").rstrip("\0"))
   #print("MeshName ", MeshName)

   matrix = NoeMat43( (
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) )
      )
   )

   rapi.rpgSetTransform(matrix) 
   #print (matrix)

   bs.seek(24, NOESEEK_REL)#unk

   bs.seek(4, NOESEEK_REL) # 3 ??

   meshes = []
   numMeshesInObj = bs.readInt()
   #print(numMeshesInObj, "numMeshes")
   for i in range(0, numMeshesInObj):
      loadMesh(bs, rapi,i,MeshName)

      extraSomeBlocks= bs.readShort()   
      #print (extraSomeBlocks,"extraSomeBlocks")
      bs.seek(extraSomeBlocks * 2, NOESEEK_REL)

      whoKnows= bs.readByte()
      #print (whoKnows,"who")
      
      if whoKnows == 1:
         size=16
         numberOfUnk= bs.readInt()
      else:
         size=28
         numberOfUnk= bs.readShort()

      #print (numberOfUnk *size)

      bs.seek( numberOfUnk *size, NOESEEK_REL)

      

   numChildsInObj= bs.readInt()

   print("Obj :" ,MeshName,"numMeshes",numMeshesInObj, "numChildsInObj",numChildsInObj)   
   #print(bs.tell() ," POS PRE CHILD")

   #print(numChildsInObj, "numChildsInObj")
   for i in range(0, numChildsInObj):
      loadObj(bs, rapi)

   return 1   
   

def loadMesh(bs, rapi,matID,name):
   bs.seek(15, NOESEEK_REL)#unk

   typeText= bs.readByte()

   #exceptions
   if typeText == 17:
      typeText=3
   elif typeText == 19:
      typeText=7
   elif typeText == 23:
      typeText=15


   #print(typeText, "numofText1")
   numberOfText=math.log(typeText+1,2)
   #print(numberOfText, "numofText2")

   for i in range(0, int(numberOfText)):
      nameLength=bs.readInt()
      MaterialName = (bs.readBytes(nameLength).decode("ASCII").rstrip("\0"))
      #print(MaterialName, "MaterialName")

   numBlocks= bs.readShort()
   print(numBlocks, "numBlocks")

   tell=bs.tell()
   uvbuffer=bytes()
   for m in range(numBlocks*16):
      sh=bs.readShort()/32768.0
      uvbuffer+=struct.pack("f",sh)
      
   
   bs.seek(tell)
   VertBuff = bs.readBytes(numBlocks * 32)
   bs.seek(16, NOESEEK_REL)#uuid¿?

   extraInfo= bs.readByte()
   #print(bs.tell() ,"pos")
   if extraInfo>0:
      bs.seek(bs.readShort() * 12 * 4, NOESEEK_REL)
   #print(bs.tell() ,"pos")   
   numTris= bs.readShort()
   #print(numTris, "numTris")
   #print(bs.tell() ,"pos")
   TrisBuff =   bs.readBytes(numTris * 2)
   
   rapi.rpgSetMaterial(name+str(matID))

   rapi.rpgBindPositionBufferOfs(VertBuff, noesis.RPGEODATA_FLOAT, 32, 0)
   rapi.rpgBindUV1BufferOfs(uvbuffer, noesis.RPGEODATA_FLOAT,64,32)

   rapi.rpgCommitTriangles(TrisBuff, noesis.RPGEODATA_USHORT, numTris, noesis.RPGEO_TRIANGLE, 1)

   

   bs.seek(2, NOESEEK_REL) #FF
   #print(bs.tell() ,"pos")
   return 1


Yak_Forger
Posts: 3
Joined: Mon Jan 28, 2019 2:10 pm

Re: Crimson Skies High Road to Revenge .x models

Post by Yak_Forger »

Yup, 404 for me as well, so it isn't a temporary issue. It's a shame since I'm on the hunt for good plane models, and the Crimson Skies games were among my favorites.
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Same here. Afraid I still don't have a real solution to extracting the models w/ uvs and hierachy submeshes though, but that noesis .x plugin update works really well!
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Szkaradek123 wrote:links to images are 404.



Code: Select all

#Noesis Python model import+export test module, imports/exports some data from/to a made-up format
import math

from inc_noesis import *

#registerNoesisTypes is called by Noesis to allow the script to register formats.
#Do not implement this function in script files unless you want them to be dedicated format modules!
def registerNoesisTypes():
   handle = noesis.register("Crimson Skies (Xbox)", ".x")
   noesis.setHandlerTypeCheck(handle, noepyCheckType)
   noesis.setHandlerLoadModel(handle, noepyLoadModel) #see also noepyLoadModelRPG

   noesis.logPopup()
   
   return 1


#check if it's this type based on the data
def noepyCheckType(data):
   if len(data) < 8:
      return 0
   return 1

#load the model
def noepyLoadModel(data, mdlList):

   bs = NoeBitStream(data)
   
   fileDir=rapi.getDirForFilePath(rapi.getInputName());
   texDir= os.path.join(fileDir, '../TEXTURE/')
   ctx = rapi.rpgCreateContext()
      
   rapi.rpgSetOption(noesis.RPGOPT_SWAPHANDEDNESS, 1)
      
   bs.seek(32, NOESEEK_ABS)

   nameLength=bs.readInt()
   MeshName = (bs.readBytes(nameLength).decode("ASCII").rstrip("\0"))
   print("MeshName ", MeshName)

   matrix = NoeMat43( (
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) )
      )
   )
   
   rapi.rpgSetTransform(matrix) 

   bs.seek(24, NOESEEK_REL)#unk

   bs.seek(4, NOESEEK_REL) # 3 ??



   meshes = []
   numMeshesInObj = bs.readInt()
   #for i in range(0, numMeshesInObj):
   #   loadMesh(bs, rapi)

   #print(numMeshesInObj, "numMeshes")

   numChildsInObj= bs.readInt()
   #print(numChildsInObj, "numChildsInObj")
   for i in range(0, numChildsInObj):
      loadObj(bs, rapi)


   mdl = rapi.rpgConstructModel()

   # print(texList)
   # print(matList)

   #mdl.setModelMaterials(NoeModelMaterials(texList, matList))
   mdlList.append(mdl)
   
   rapi.rpgClearBufferBinds()
   return 1



def loadObj(bs, rapi):
   nameLength=bs.readInt()
   MeshName = (bs.readBytes(nameLength).decode("ASCII").rstrip("\0"))
   #print("MeshName ", MeshName)

   matrix = NoeMat43( (
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) ),
      NoeVec3( (bs.readFloat(), bs.readFloat(), bs.readFloat()) )
      )
   )

   rapi.rpgSetTransform(matrix) 
   #print (matrix)

   bs.seek(24, NOESEEK_REL)#unk

   bs.seek(4, NOESEEK_REL) # 3 ??

   meshes = []
   numMeshesInObj = bs.readInt()
   #print(numMeshesInObj, "numMeshes")
   for i in range(0, numMeshesInObj):
      loadMesh(bs, rapi,i,MeshName)

      extraSomeBlocks= bs.readShort()   
      #print (extraSomeBlocks,"extraSomeBlocks")
      bs.seek(extraSomeBlocks * 2, NOESEEK_REL)

      whoKnows= bs.readByte()
      #print (whoKnows,"who")
      
      if whoKnows == 1:
         size=16
         numberOfUnk= bs.readInt()
      else:
         size=28
         numberOfUnk= bs.readShort()

      #print (numberOfUnk *size)

      bs.seek( numberOfUnk *size, NOESEEK_REL)

      

   numChildsInObj= bs.readInt()

   print("Obj :" ,MeshName,"numMeshes",numMeshesInObj, "numChildsInObj",numChildsInObj)   
   #print(bs.tell() ," POS PRE CHILD")

   #print(numChildsInObj, "numChildsInObj")
   for i in range(0, numChildsInObj):
      loadObj(bs, rapi)

   return 1   
   

def loadMesh(bs, rapi,matID,name):
   bs.seek(15, NOESEEK_REL)#unk

   typeText= bs.readByte()

   #exceptions
   if typeText == 17:
      typeText=3
   elif typeText == 19:
      typeText=7
   elif typeText == 23:
      typeText=15


   #print(typeText, "numofText1")
   numberOfText=math.log(typeText+1,2)
   #print(numberOfText, "numofText2")

   for i in range(0, int(numberOfText)):
      nameLength=bs.readInt()
      MaterialName = (bs.readBytes(nameLength).decode("ASCII").rstrip("\0"))
      #print(MaterialName, "MaterialName")

   numBlocks= bs.readShort()
   print(numBlocks, "numBlocks")

   tell=bs.tell()
   uvbuffer=bytes()
   for m in range(numBlocks*16):
      sh=bs.readShort()/32768.0
      uvbuffer+=struct.pack("f",sh)
      
   
   bs.seek(tell)
   VertBuff = bs.readBytes(numBlocks * 32)
   bs.seek(16, NOESEEK_REL)#uuid¿?

   extraInfo= bs.readByte()
   #print(bs.tell() ,"pos")
   if extraInfo>0:
      bs.seek(bs.readShort() * 12 * 4, NOESEEK_REL)
   #print(bs.tell() ,"pos")   
   numTris= bs.readShort()
   #print(numTris, "numTris")
   #print(bs.tell() ,"pos")
   TrisBuff =   bs.readBytes(numTris * 2)
   
   rapi.rpgSetMaterial(name+str(matID))

   rapi.rpgBindPositionBufferOfs(VertBuff, noesis.RPGEODATA_FLOAT, 32, 0)
   rapi.rpgBindUV1BufferOfs(uvbuffer, noesis.RPGEODATA_FLOAT,64,32)

   rapi.rpgCommitTriangles(TrisBuff, noesis.RPGEODATA_USHORT, numTris, noesis.RPGEO_TRIANGLE, 1)

   

   bs.seek(2, NOESEEK_REL) #FF
   #print(bs.tell() ,"pos")
   return 1




Any way you can improve the plugin? The UVs don't appear to scale or position correctly with their associated textures, which is a massive pain in the ass to work with.
I noticed the textures are now 404'd.
Here's a new link:
https://drive.google.com/open?id=1Xe9ay ... 1WXyEwDCRB

While I wish I could do this myself and not have to rely on others, I don't have the necessary knowledge or skill to actually pull it off myself.

If it's possible to have the noesis plugin actually load and export the textures with the model when selected as well, that would be absolutely amazing.
Note the original textures are in a normally *SPAM* up .tga format, which can only be exported with another noesis plugin someone else made here:
https://drive.google.com/open?id=1r5l_M ... WWivCM_vTK
IronArthur
Posts: 5
Joined: Tue May 30, 2017 1:44 pm

Re: Crimson Skies High Road to Revenge .x models

Post by IronArthur »

The problem with UVs on Crimson skies models it that it has some edge cases that don´t work on some models (the worst model it´s the cinematic devastator).

If you process the uvs as this (c# code not python)

Code: Select all

var uaux = (reader.ReadInt16()); //Signed short
if (uaux > 16384f || uaux < -16384f)
u = ((float)(uaux & 16383) / 16384f);
else
u = ((float)(uaux) / 16384f);


You get mostly ok uvs for the modelx except for the cinematic devastator (right plane) and iirc some helis
Image

I wrote that python script but i don´t have enough confidence on my knowledge of python to try to fix it
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

How would I process the UVs that way?
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Got some new problems.

Unfortunately, seems like not all .x files can be processed through the Noesis plugin - a lot fail.
Here's an example.

SH_Redsky.7z


Also, I noticed when running the actual models through NinjaRipper instead of using the Noesis plugin... a lot of them (including the player's plane) have multiple UV sets.

Is it possible to support this?
RevolverOcelo
Posts: 29
Joined: Sat Dec 01, 2018 1:43 pm

Re: Crimson Skies High Road to Revenge .x models

Post by RevolverOcelo »

Massive update!
I now got access to the .pdb files of CS, dated 2 months prior to release. This should make it far more easy to reverse engineer the model formats, so I hope that some work here will be done.

Whoever manages to get the plugins improved through this; I'll love you forever.