Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.

Crimson Skies High Road to Revenge .x models

Featured Replies

  • Author
  • Localization

RevolverOcelo, posted Tue Dec 11, 2018 1:57 am (41240)


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!

CrimsonSkies_Xbox_X.zip

  • Author
  • Localization

RevolverOcelo, posted Sat Dec 15, 2018 12:29 am (41400)


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.
  • Author
  • Localization

RevolverOcelo, posted Fri Dec 28, 2018 1:48 am (41686)


Bump. Still looking for help.
  • Author
  • Localization

RevolverOcelo, posted Wed Jan 16, 2019 12:29 am (42421)


And yet another bump.
  • Author
  • Localization

Szkaradek123, posted Wed Jan 16, 2019 8:55 am (42431)


links to images are 404.



Code:
#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


  • Author
  • Localization

Yak_Forger, posted Mon Jan 28, 2019 4:43 pm (42876)


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.
  • Author
  • Localization

RevolverOcelo, posted Fri Mar 15, 2019 5:01 am (45783)


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!
  • Author
  • Localization

RevolverOcelo, posted Mon Apr 08, 2019 1:54 am (46611)


Szkaradek123 wrote:
links to images are 404.



Code:
#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
  • Author
  • Localization

IronArthur, posted Fri Sep 20, 2019 9:35 am (50817)


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:
var uaux = (reader.ReadInt16()); //Signed short
if (uaux > 16384f || uaux 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
  • Author
  • Localization

RevolverOcelo, posted Thu Sep 26, 2019 5:30 am (50904)


How would I process the UVs that way?
  • Author
  • Localization

RevolverOcelo, posted Thu Nov 26, 2020 9:11 pm (60643)


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.



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?
  • Author
  • Localization

RevolverOcelo, posted Wed Jul 21, 2021 11:20 am (65290)


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.
Guest
This topic is now closed to further replies.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.