SHARE
TWEET

Untitled

a guest Apr 17th, 2017 90 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/python
  2. # created using python 3.6
  3.  
  4. import copy
  5. from functools import partial
  6. import math
  7. import os
  8. import re
  9. from struct import pack, unpack, calcsize
  10. import subprocess
  11. import sys
  12. import tempfile
  13. from zipfile import ZipFile
  14.  
  15. ImageMagicConvert = '/usr/bin/convert'
  16.  
  17.  
  18. SCMAPMAGIC = b'\x4d\x61\x70\x1a'
  19. DDSMAGIC = b'DDS '
  20.  
  21. decalDebug = False
  22. debugPrintEnabled = False
  23.  
  24. #mirrorMode = "Diagonal Top-Left to Bottom-Right"
  25. mirrorMode = "Left-Right"
  26. #mirrorEmbeddedImages = "from left"
  27. mirrorEmbeddedImages = "from right"
  28.  
  29. if mirrorMode == "Diagonal Top-Left to Bottom-Right":
  30.     def translatePosition( position, mapSize ):
  31.         return ( position[2], position[1], position[0] )
  32.     def rotateDecal(rotation):
  33.         return ( rotation[2], -rotation[1], -rotation[0] )
  34.  
  35.     # not really posible to mirror the mesh by rotation...
  36.     def rotateProp( rotationX, rotationY, rotationZ ):
  37.         rad = math.acos( rotationX[0] ) + math.pi
  38.         newRotationX = (  math.cos(rad),  rotationX[1], math.sin(rad) )
  39.         newRotationY = (  rotationY[0],   rotationY[1], rotationY[2]  )
  40.         newRotationZ = ( -math.sin(rad),  rotationZ[1], math.cos(rad) )
  41.         return ( newRotationX, newRotationY, newRotationZ )
  42.  
  43.  
  44. elif mirrorMode == "Left-Right":
  45.     unitPositionFix = {
  46.         'xec8012': (-1, 0, 1), # -|
  47.         'xec8004': ( 1, 0, 0), # ---
  48.         'xec8008': (-1, 0, 1), #  |-
  49.         'xec8003': ( 0, 0,-1), #  |
  50.     }
  51.     def translatePosition( position, mapSize ):
  52.         return ( ( mapSize[0] - position[0] ), position[1], position[2] )
  53.     def translateUnitPosition( unitType, position, mapSize ):
  54.         position = ( ( mapSize[0] - position[0] ), position[1], position[2] )
  55.         if unitType in list(unitPositionFix):
  56.             fix = unitPositionFix[unitType]
  57.             position = ( position[0]+fix[0], position[1]+fix[1], position[2]+fix[2] )
  58.         return position
  59.  
  60.     def rotateDecal(rotation):
  61.         return ( rotation[2], math.pi/2 - rotation[1], -rotation[0] )
  62.     def rotateProp( rotationX, rotationY, rotationZ ):
  63.         rad = math.acos( rotationX[0] ) + math.pi
  64.         newRotationX = (  math.cos(rad),  rotationX[1], math.sin(rad) )
  65.         newRotationY = (  rotationY[0],   rotationY[1], rotationY[2]  )
  66.         newRotationZ = ( -math.sin(rad),  rotationZ[1], math.cos(rad) )
  67.         return ( newRotationX, newRotationY, newRotationZ )
  68.  
  69. def dummyUnitRotation( unitType, rotation ):
  70.     return ( rotation[0], rotation[1], rotation[2] )
  71.  
  72. unitTypeTranslation = {
  73.     'xec8001': 'xec8003',
  74.     'xec8002': 'xec8004',
  75.     'xec8005': 'xec8008',
  76.     'xec8006': 'xec8007',
  77.     'xec8009': 'xec8012',
  78.     'xec8010': 'xec8011',
  79.     'xec8013': 'xec8018',
  80.     'xec8014': 'xec8017',
  81.     'xec8015': 'xec8016',
  82.     'xec8019': 'xec8020',
  83. }
  84.  
  85. def translateUnitType( unitType ):
  86.     if unitType in list(unitTypeTranslation):
  87.         return unitTypeTranslation[unitType]
  88.     else:
  89.         return unitType
  90.  
  91. def mirrorProps( mapInfos ):
  92.     newPropsList = []
  93.     for prop in mapInfos['propsList']:
  94.         (blueprintPath,position,rotationX,rotationY,rotationZ,scale) = prop
  95.         # create mirrored parameters
  96.         newPosition = translatePosition( position, mapInfos['mapSize'] )
  97.         newRotation = rotateProp(rotationX,rotationY,rotationZ)
  98.         # add version with mirrored parameters to props list
  99.         newPropsList.append( (blueprintPath,newPosition,*newRotation,scale) )
  100.     mapInfos['propsList'] += newPropsList
  101.  
  102. def mirrorDecals( mapInfos, decalsArchivePath, newMapDirectory, newMapFolderNameWithVersion ):
  103.  
  104.     def generateMirroredDecal( decalsArchive, decalToMirror, isNormalMap ):
  105.         if not decalToMirror:
  106.             return b''
  107.         newDecalPath = newMapDirectory + '/mflop' + decalToMirror
  108.         newDecalIngamePath = '/maps/{}/mflop{}'.format( newMapFolderNameWithVersion, decalToMirror )
  109.         if os.path.exists( newDecalPath ):
  110.             return newDecalIngamePath.encode()
  111.  
  112.         print("Converting {}".format(newDecalPath))
  113.  
  114.         with decalsArchive.open(decalsArchive.decalsCaseInsensitiveLookup[decalToMirror[1:].lower()]) as decalTexture:
  115.             with tempfile.NamedTemporaryFile() as originalDecalTempFile:
  116.                 originalDecalTempFile.write( decalTexture.read() )
  117.  
  118.                 os.makedirs( os.path.dirname( newDecalPath ), exist_ok = True )
  119.                 cmd = [ImageMagicConvert,
  120.                         originalDecalTempFile.name,
  121.                         "-flop","-rotate","-90",
  122.                         # fix rotation in normals
  123.                         *((  "-channel", "rgba",
  124.                             "-separate", "-swap", "1,3",
  125.                             "-combine" )
  126.                         if isNormalMap else ()),
  127.                         newDecalPath]
  128.                 print("running {}".format(' '.join(cmd)))
  129.                 subprocess.run(cmd)
  130.  
  131.         return newDecalIngamePath.encode()
  132.  
  133.     newDecals = []
  134.     decalsCount = len(mapInfos['decalsList'])
  135.  
  136.     with ZipFile( decalsArchivePath, 'r' ) as decalsArchive:
  137.         decalsArchive.decalsCaseInsensitiveLookup = { s.lower():s for s in decalsArchive.namelist() }
  138.  
  139.         for decal in mapInfos['decalsList']:
  140.             (
  141.                 decalId,decalType,unknown15,
  142.                 decalsTexture1Path,decalsTexture2Path,
  143.                 scale,position,rotation,
  144.                 cutOffLOD,nearCutOffLOD,removeTick
  145.             ) = decal
  146.  
  147.             newPosition = translatePosition( position, mapInfos['mapSize'] )
  148.             newRotation = rotateDecal( rotation )
  149.  
  150.             isNormalMap = ( decalType == 2 )
  151.  
  152.             if decalDebug:
  153.                 # switch normals to albedo for debugging
  154.                 decalType = 1
  155.                 decal[1] = 1
  156.  
  157.                 # place theta bridges at decal position with decal rotation
  158.                 mapInfos['propsList'] += [(
  159.                         b'/env/redrocks/props/thetabridge01_prop.bp',
  160.                         position,
  161.                         (-math.cos(rotation[1]),0,-math.sin(rotation[1])),
  162.                         (0,1,0),
  163.                         (math.sin(rotation[1]),0,-math.cos(rotation[1])),
  164.                         (1,1,1))]
  165.                 mapInfos['propsList'] += [(
  166.                         b'/env/redrocks/props/thetabridge01_prop.bp',
  167.                         newPosition,
  168.                         (-math.cos(newRotation[1]),0,-math.sin(newRotation[1])),
  169.                         (0,1,0),
  170.                         (math.sin(newRotation[1]),0,-math.cos(newRotation[1])),
  171.                         (1,1,1))]
  172.  
  173.             newDecalsTexture1Path = generateMirroredDecal( decalsArchive, decalsTexture1Path.decode(), isNormalMap )
  174.             newDecalsTexture2Path = generateMirroredDecal( decalsArchive, decalsTexture2Path.decode(), isNormalMap )
  175.  
  176.             newDecals.append([
  177.                 decalsCount+decalId,decalType,unknown15,
  178.                 newDecalsTexture1Path,newDecalsTexture2Path,
  179.                 scale,newPosition,newRotation,
  180.                 cutOffLOD,nearCutOffLOD,removeTick
  181.             ])
  182.  
  183.     mapInfos['decalsList'] += newDecals
  184.  
  185.  
  186. def main():
  187.  
  188.     if len(sys.argv) < 3:
  189.         print("Usage: {} <infile> <outfile>".format(sys.argv[0]))
  190.         sys.exit(1)
  191.  
  192.     pathToOldScmap = sys.argv[1]
  193.     oldScmapName, oldScmapExtension = os.path.splitext(os.path.basename(  pathToOldScmap ))
  194.     pathToOldScmapSaveLua = os.path.join( os.path.dirname(  pathToOldScmap ), "{}_save.lua".format(oldScmapName) )
  195.  
  196.     pathToNewScmap = sys.argv[2]
  197.     newScmapName, newScmapExtension = os.path.splitext(os.path.basename(  pathToNewScmap ))
  198.     newMapDirectory = os.path.dirname( pathToNewScmap )
  199.     newMapFolderNameWithVersion = os.path.basename( newMapDirectory )
  200.     pathToNewScmapSaveLua = os.path.join( os.path.dirname(  pathToNewScmap ), "{}_save.lua".format(newScmapName) )
  201.  
  202.     mapInfos = readMap( pathToOldScmap )
  203.  
  204.     # TODO FIXME add as env or commandline parameter:
  205.     decalsArchivePath = '/home/b2ag/PlayOnLinux virtual drives/Supreme_Commander/drive_c/Program Files/THQ/Gas Powered Games/Supreme Commander - Forged Alliance/gamedata/env.scd'
  206.  
  207.     mirrorProps( mapInfos )
  208.     mirrorDecals( mapInfos, decalsArchivePath, newMapDirectory, newMapFolderNameWithVersion )
  209.  
  210.     if mirrorEmbeddedImages:
  211.         with tempfile.NamedTemporaryFile(suffix='.gray') as heightMap:
  212.             with tempfile.NamedTemporaryFile(suffix='.gray') as mirroredHeightMap:
  213.  
  214.                 heightMap.write(mapInfos['heightMapData'])
  215.  
  216.                 size = [ int(x) for x in mapInfos['mapSize'] ]
  217.                 cmd = [ImageMagicConvert,
  218.                         '-size',"{}x{}".format(size[0]+1,size[1]+1),
  219.                         '-depth', '16',
  220.                         heightMap.name,
  221.                             '-repage', '-{}x{}+0+0'.format(int(size[0]/2+1),size[1]+1),
  222.                             '-crop', '{}x{}+0+0'.format(int(size[0]/2+1),size[1]+1),
  223.                             '-background', 'none',
  224.                             '-flop',
  225.                         heightMap.name,
  226.                             '-crop', "{}x{}+{}+0".format(int(size[0]/2+1),size[1]+1,int(size[0]/2)),
  227.                         '+append', mirroredHeightMap.name,
  228.                     ]
  229.                 print('running {}'.format(' '.join(cmd)))
  230.                 if subprocess.run(cmd):
  231.                     mapInfos['heightMapData'] = open(mirroredHeightMap.name,'rb').read()
  232.  
  233.                 open('/tmp/debugMe.gray','wb').write(mapInfos['heightMapData'])
  234.  
  235.  
  236.     writeMirroredMap( pathToOldScmap, pathToNewScmap, mapInfos )
  237.  
  238.     scenario = mirrorStuffInSaveLua( pathToOldScmapSaveLua, pathToNewScmapSaveLua, mapInfos )
  239.     with open(pathToNewScmapSaveLua,'w') as newSaveLua:
  240.         writeSaveLua( newSaveLua, scenario, first=True )
  241.  
  242. def mirrorStuffInSaveLua( pathToOldScmapSaveLua, pathToNewScmapSaveLua, mapInfos ):
  243.  
  244.     if not os.path.exists( pathToOldScmapSaveLua ):
  245.         print("Couldn't find \"{}\".".format(pathToOldScmapSaveLua))
  246.         sys.exit(1)
  247.  
  248.     pathToOldScmapSaveModule, _ = os.path.splitext( pathToOldScmapSaveLua )
  249.  
  250.     # import map_save.lua with lupa
  251.     import lupa
  252.     lua = lupa.LuaRuntime(unpack_returned_tuples=False)
  253.     lua.execute('FLOAT=function(x) return string.format("FLOAT( %.6f )",x) end')
  254.     lua.execute('BOOLEAN=function(x) return string.format("BOOLEAN( %s )",x) end')
  255.     lua.execute('STRING=function(x) return string.format("STRING( \'%s\' )",x) end')
  256.     lua.execute('VECTOR3=function(x,y,z) return "VECTOR3( "..x..", "..y..", "..z.." )" end')
  257.     lua.execute('GROUP=function(x) return function() return x end end')
  258.     lua.require( pathToOldScmapSaveModule )
  259.  
  260.  
  261.     scenario = lua.table_from({'Scenario': lua.eval('Scenario') })
  262.  
  263.     # mirror Mexes
  264.     changeValueByPathRegex(
  265.         re.compile("/Scenario/MasterChain/[^/]*/Markers"),
  266.         partial(
  267.             partial(duplicateMirrorAndRotate,re.compile("/Mass [^/]*")),
  268.                 partial(getMaxFromPrefix,'Mass '),
  269.                 dummySetNewMaxIdFunc,
  270.                 newIdAndKeyFunc,
  271.                 partial(translateUnitPosition,mapSize=mapInfos['mapSize']),
  272.                 dummyUnitRotation,
  273.                 translateUnitType,
  274.         ), scenario )
  275.  
  276.     # mirror some armies
  277.     changeValueByPathRegex(
  278.         re.compile("/Scenario/Armies/[^/]*/[^/]*/Units/[^/]*/Units"),
  279.         partial(
  280.             partial(duplicateMirrorAndRotate,re.compile("/[^/]*")),
  281.                 partial(getMaxFromScenario,'next_unit_id'),
  282.                 partial(setNewMaxToScenario,'next_unit_id'),
  283.                 newIdAndKeyFunc,
  284.                 partial(translateUnitPosition,mapSize=mapInfos['mapSize']),
  285.                 dummyUnitRotation,
  286.                 translateUnitType,
  287.         ), scenario )
  288.  
  289.     return scenario
  290.  
  291. # duplicate 'position' and 'Position' values for tables matched by regular expression
  292. def duplicateMirrorAndRotate(regEx,getMaxIdFunc,setNewMaxIdFunc,newIdAndKeyFunc,positionFunc,rotateFunc,translateUnitTypeFunc,k,v,rootTable):
  293.     oldTables = getTablesByPathRegex( regEx, v )
  294.     oldMaxId = getMaxIdFunc(oldTables,rootTable)
  295.     newTable = {}
  296.     newMaxId = oldMaxId
  297.     for key in oldTables:
  298.         ( newCurrentId, newKey ) = newIdAndKeyFunc(key,newMaxId,oldMaxId)
  299.         if newCurrentId > newMaxId:
  300.             newMaxId = newCurrentId
  301.         newParams = {}
  302.         newParams.update( oldTables[key] )
  303.         if 'type' in newParams:
  304.             unitType = newParams['type'] = translateUnitTypeFunc( newParams['type'] )
  305.         elif 'resource' in newParams:
  306.             unitType = 'mass'
  307.         else:
  308.             unitType = 'unknown'
  309.         if 'position' in newParams:
  310.             newParams['position'] = mapSaveLuaVector( partial(positionFunc,unitType), newParams['position'] )
  311.         elif 'Position' in newParams:
  312.             newParams['Position'] = mapSaveLuaVector( partial(positionFunc,unitType), newParams['Position'] )
  313.         if 'orientation' in newParams:
  314.             newParams['orientation'] = mapSaveLuaVector( partial(rotateFunc,unitType), newParams['orientation'] )
  315.         elif 'Orientation' in newParams:
  316.             newParams['Orientation'] = mapSaveLuaVector( partial(rotateFunc,unitType), newParams['Orientation'] )
  317.         newTable.update( {newKey: newParams} )
  318.     for k2 in newTable:
  319.         v[k2] = newTable[k2]
  320.     setNewMaxIdFunc(newMaxId,newTable,rootTable)
  321.     return v
  322.  
  323. # write the output _save.lua
  324. def writeSaveLua( oStream, luaTable, path='', first=False ):
  325.     import lupa
  326.     indent = "    "*(path.count('/'))
  327.     # try to make output look more like original
  328.     keys = orderedSaveLuaKeys( list(luaTable), path )
  329.     # iterate over lua table keys
  330.     for k in keys:
  331.         newPath = '/'.join([path,str(k)])
  332.         printPathDecorator(oStream,indent,newPath)
  333.         if keyIsWrittenAlternativly(newPath):
  334.             oStream.write("{}['{}'] = ".format(indent,k))
  335.         else:
  336.             oStream.write("{}{} = ".format(indent,k))
  337.         v = luaTable[k]
  338.         if lupa.lua_type(v) == 'table' or type(v) is dict:
  339.             if list(v) == [1,2,3]:
  340.                 oStream.write("{{ {0:.6f}, {1:.6f}, {2:.6f} }}".format(v[1],v[2],v[3]))
  341.             else:
  342.                 oStream.write("{\n")
  343.                 writeSaveLua( oStream, v, newPath )
  344.                 oStream.write("{}}}".format(indent))
  345.         elif type(v) is str:
  346.             if v[:5] in ['FLOAT','BOOLE','STRIN','VECTO']:
  347.                 oStream.write("{}".format(v))
  348.             else:
  349.                 oStream.write("'{}'".format(v))
  350.         elif type(v) is int:
  351.             oStream.write("{}".format(v))
  352.         elif lupa.lua_type(v) == 'function':
  353.             oStream.write("GROUP {\n")
  354.             writeSaveLua( oStream, v(), newPath )
  355.             oStream.write("{}}}".format(indent))
  356.         elif type(v) is tuple:
  357.             oStream.write("{{ {:.6f}, {:.6f}, {:.6f} }}".format(*v))
  358.         else:
  359.             raise Exception("Unknown format {} at {} ".format(type(v),newPath))
  360.         if not first:
  361.             oStream.write(",\n")
  362.  
  363. # try to make output look more like original
  364. # by enforcing some predefined order
  365. def orderedSaveLuaKeys( keys, path ):
  366.     pathDepth = path.count('/')
  367.     oldKeys = copy.copy(sorted(keys))
  368.     newKeys = []
  369.     if path == '/Scenario':
  370.         for k in [
  371.             'next_area_id','Props','Areas',
  372.             'MasterChain','Chains',
  373.             'next_queue_id','Orders',
  374.             'next_platoon_id', 'Platoons',
  375.             'next_army_id','next_group_id','next_unit_id',
  376.             'Armies']:
  377.             if k in oldKeys:
  378.                 oldKeys.remove(k)
  379.                 newKeys.append(k)
  380.         newKeys += oldKeys
  381.         return newKeys
  382.     elif path.startswith('/Scenario/MasterChain/'):
  383.         for k in [
  384.             'size', 'resource', 'amount', 'color', 'editorIcon',
  385.             'type','prop',
  386.             'orientation','position']:
  387.             if k in oldKeys:
  388.                 oldKeys.remove(k)
  389.                 newKeys.append(k)
  390.         newKeys += oldKeys
  391.         return newKeys
  392.     elif path.startswith('/Scenario/Armies/'):
  393.         for k in [
  394.             'mass', 'energy',
  395.             'personality', 'plans', 'color', 'faction', 'Economy', 'Alliances',
  396.             'type', 'orders','platoon',
  397.             'Units','Position','Orientation',
  398.             'PlatoonBuilders', 'next_platoon_builder_id']:
  399.             if k in oldKeys:
  400.                 oldKeys.remove(k)
  401.                 newKeys.append(k)
  402.         newKeys += oldKeys
  403.         return newKeys
  404.     else:
  405.         return oldKeys
  406.  
  407. # try to make output look more like original
  408. # by adding theses huge comment sections
  409. def printPathDecorator( oStream, indent, path ):
  410.     fmt = "{0:}--[["+" "*75+"]]--\n{0:}--[[  {1: <73}]]--\n{0:}--[["+" "*75+"]]--\n"
  411.     s = ''
  412.     if path == '/Scenario':
  413.         s += fmt.format( indent, "Automatically generated code (do not edit)" )
  414.         s += fmt.format( indent, "Scenario" )
  415.     elif path == '/Scenario/Props':
  416.         s += fmt.format( indent, "Props" )
  417.     elif path == '/Scenario/Areas':
  418.         s += fmt.format( indent, "Areas" )
  419.     elif path == '/Scenario/MasterChain':
  420.         s += fmt.format( indent, "Markers" )
  421.     elif path == '/Scenario/next_queue_id':
  422.         s += fmt.format( indent, "Orders" )
  423.     elif path == '/Scenario/next_platoon_id':
  424.         s += fmt.format( indent, "Platoons" )
  425.     elif path == '/Scenario/next_army_id':
  426.         s += fmt.format( indent, "Armies" )
  427.     elif path.startswith('/Scenario/Armies/') and path.count('/') == 3:
  428.         s += fmt.format( indent, "Army" )
  429.     if s:
  430.         oStream.write(s)
  431.  
  432. # try to make output look more like original
  433. # by alternating writings e.g. Units and ['Units']
  434. def keyIsWrittenAlternativly(path):
  435.     pathDepth = path.count('/')
  436.     if path.startswith('/Scenario/MasterChain/'):
  437.         if pathDepth != 4:
  438.             return True
  439.     if path.startswith('/Scenario/Chains/'):
  440.         if pathDepth != 4:
  441.             return True
  442.     if path.startswith('/Scenario/Armies/'):
  443.         if pathDepth == 3:
  444.             return True
  445.         if pathDepth == 4 and path.endswith('/Units'):
  446.             return True
  447.         if pathDepth == 6:
  448.             return True
  449.         if pathDepth == 8:
  450.             return True
  451.     return False
  452.  
  453. def mapSaveLuaVector( mirrorFunc, value ):
  454.     import lupa
  455.     if lupa.lua_type(value) == 'table':
  456.         if list(value) == [1,2,3]:
  457.             value = mirrorFunc((value[1],value[2],value[3]))
  458.     elif value.startswith('VECTOR3'):
  459.         value = eval(value[7:])
  460.         value = mirrorFunc(value)
  461.         return "VECTOR3( {}, {}, {} )".format(*value)
  462.     return value
  463.  
  464. # find max i for a prefix e.g. 'Mass <i>' for all table keys
  465. def getMaxFromPrefix(prefix,table,rootTable):
  466.     maxId = 0
  467.     for key in table:
  468.         currentId = int(key[len(prefix):])
  469.         if currentId > maxId:
  470.             maxId = currentId
  471.     return maxId
  472.  
  473. # get max by reading some Scenario.x lua variable
  474. def getMaxFromScenario(maxVarname,table,rootTable):
  475.     return int(rootTable['Scenario'][maxVarname]) - 1
  476.  
  477. # update some Scenario.x lua variable with current max id
  478. def setNewMaxToScenario(maxVarname,newMaxId,table,rootTable):
  479.     rootTable['Scenario'][maxVarname] = str( newMaxId + 1 )
  480.  
  481. # just a dummy
  482. def dummySetNewMaxIdFunc(newMaxId,table,rootTable):
  483.     pass
  484.  
  485. # return a new unique id based on current key
  486. def newIdAndKeyFunc( key, newMaxId, oldMaxId ):
  487.     m = re.match( '(.*[^0-9])([0-9]*)', key ).groups(0)
  488.     prefix = m[0]
  489.     newId = newMaxId+1
  490.     if m[1]:
  491.         newId = max( int(m[1])+oldMaxId, newId )
  492.     else:
  493.         prefix += '_'
  494.  
  495.     newKey = '{}{}'.format( prefix, newId )
  496.     return ( newId, newKey )
  497.  
  498. # helper function to traverse lua data by regular expression
  499. def getTablesByPathRegex( regEx, luaTable, path='' ):
  500.     import lupa
  501.     ret = {}
  502.     for key in list(luaTable):
  503.         v = luaTable[key]
  504.         newPath = '/'.join([path,str(key)])
  505.         if regEx.match( newPath ):
  506.             ret.update({key:v})
  507.         elif lupa.lua_type(v) == 'table':
  508.             ret.update(getTablesByPathRegex( regEx, v, newPath ))
  509.     return ret
  510.  
  511. # update values for tables matched by regular expression
  512. def changeValueByPathRegex( regEx, func, luaTable, rootTable=None, path='' ):
  513.     import lupa
  514.     if not rootTable: rootTable = luaTable
  515.     for key in list(luaTable):
  516.         v = luaTable[key]
  517.         newPath = '/'.join([path,str(key)])
  518.         if regEx.match( newPath ):
  519.             luaTable[key] = func( key, v, rootTable )
  520.         elif lupa.lua_type(v) == 'table':
  521.             changeValueByPathRegex( regEx, func, v, rootTable, newPath )
  522.         elif lupa.lua_type(v) == 'function':
  523.             changeValueByPathRegex( regEx, func, v(), rootTable, newPath )
  524.  
  525.  
  526. class MapParsingException(Exception):
  527.     def __init__( self, subject, fileObject ):
  528.         self.offset = fileObject.tell()
  529.         self.message = "Couldn't parse {} before offset {} ".format( subject, self.offset )
  530.         super(Exception, self).__init__( self.message )
  531.  
  532. def readcstr(f):
  533.     buf = b''
  534.     while True:
  535.         b = f.read(1)
  536.         if b is None or b == b'\0':
  537.             return buf
  538.         else:
  539.             buf += b
  540.  
  541. def getOffsetsFromMap( pathToScmap, offsets ):
  542.     if type( offsets ) is not dict:
  543.         raise Exception("Offsets parameter needs to have type of dict.")
  544.  
  545. def debugPrint( label, text ):
  546.     if debugPrintEnabled:
  547.         print("{}: {}".format(label,text))
  548.  
  549.  
  550. def readMap( pathToScmap ):
  551.     infos = {}
  552.     listOfDebugProps = []
  553.     with open(pathToScmap,'rb') as scmap:
  554.  
  555.         scmapMagic = scmap.read(4)
  556.         if scmapMagic != SCMAPMAGIC:
  557.             raise MapParsingException( "file magic", scmap )
  558.  
  559.         fileVersionMajor = unpack('I', scmap.read(4) )[0]
  560.         if not fileVersionMajor:
  561.             raise MapParsingException( "file major version", scmap )
  562.         debugPrint( "fileVersionMajor", fileVersionMajor )
  563.  
  564.         # always 0xbeeffeed and other always 2
  565.         (unknown3,unknown4) = ( scmap.read(4), scmap.read(4) )
  566.         debugPrint( "unknown3", unknown3 )
  567.         debugPrint( "unknown4", unknown4 )
  568.  
  569.         (scaledMapWidth,scaledMapHeight) = unpack('ff', scmap.read(calcsize('ff')) )
  570.         if not scaledMapWidth or not scaledMapHeight:
  571.             raise MapParsingException( "scaled map size", scmap )
  572.         debugPrint( "scaledMapWidth", scaledMapWidth )
  573.         debugPrint( "scaledMapHeight", scaledMapHeight )
  574.  
  575.         # always 0
  576.         (unknown5,unknown6) = ( scmap.read(4), scmap.read(2) )
  577.         debugPrint( "unknown5", unknown5 )
  578.         debugPrint( "unknown6", unknown6 )
  579.  
  580.         #######################################################################
  581.         ### Preview Image
  582.         #######################################################################
  583.  
  584.         previewImageDataLength = unpack('I', scmap.read(4) )[0]
  585.         if not previewImageDataLength:
  586.             raise MapParsingException( "preview image data length", scmap )
  587.         previewImageData = scmap.read(previewImageDataLength)
  588.         if len(previewImageData) != previewImageDataLength:
  589.             raise MapParsingException( "preview image data ({} bytes)".format(previewImageDataLength), scmap )
  590.         debugPrint( "previewImageDataLength", "{} bytes".format(previewImageDataLength) )
  591.         debugPrint( "previewImageDataMagic", previewImageData[0:4].decode( ))
  592.         #if previewImageData[0:4] == DDSMAGIC:
  593.             #previewDataPath = "{}_preview.dds".format( scmap.name )
  594.             #with open(previewDataPath,'wb') as previewDumpFile:
  595.                 #previewDumpFile.write( previewImageData )
  596.             #print("preview image dumped to \"{}\"".format(previewDataPath))
  597.  
  598.         fileVersionMinor = unpack('I', scmap.read(4) )[0]
  599.         if not fileVersionMinor:
  600.             raise MapParsingException( "file minor version", scmap )
  601.         debugPrint( "fileVersionMinor", fileVersionMinor )
  602.  
  603.         if fileVersionMinor not in [60, 59, 56, 53]:
  604.             raise MapParsingException( "unsupported file minor version", scmap )
  605.  
  606.         #######################################################################
  607.  
  608.         mapSize = (mapWidth,mapHeight) = unpack('II', scmap.read(8) )
  609.         if not mapWidth or not mapHeight:
  610.             raise MapParsingException( "map size", scmap )
  611.         debugPrint( "mapWidth", mapWidth )
  612.         debugPrint( "mapHeight", mapHeight )
  613.         infos['mapSize'] = mapSize
  614.  
  615.         #######################################################################
  616.         ### Height Map
  617.         #######################################################################
  618.  
  619.         # Height Scale, usually 1/128
  620.         heightScale = unpack('f', scmap.read(4) )[0]
  621.         debugPrint( "heightScale", heightScale )
  622.  
  623.         heightMapDataLength = ( mapHeight + 1 ) * ( mapWidth + 1 ) * calcsize('h')
  624.         infos['heightMapStartOffset'] = scmap.tell()
  625.         heightMapData = scmap.read(heightMapDataLength)
  626.         infos['heightMapEndOffset'] = scmap.tell()
  627.  
  628.         infos['heightMapData'] = heightMapData
  629.  
  630.         #######################################################################
  631.         ### Some Shader
  632.         #######################################################################
  633.  
  634.         if fileVersionMinor >= 56:
  635.             unknown7 = readcstr(scmap)
  636.             debugPrint( "unknown7", unknown7 )
  637.  
  638.         terrain = readcstr(scmap)
  639.         debugPrint( "terrain", terrain )
  640.  
  641.         texPathBackground = readcstr(scmap)
  642.         debugPrint( "texPathBackground", texPathBackground )
  643.  
  644.         texPathSkyCubemap = readcstr(scmap)
  645.         debugPrint( "texPathSkyCubemap", texPathSkyCubemap )
  646.  
  647.         if fileVersionMinor < 56:
  648.  
  649.             texPathEnvCubemap = readcstr(scmap)
  650.             debugPrint( "texPathEnvCubemap", texPathEnvCubemap )
  651.  
  652.         elif fileVersionMinor >= 56:
  653.  
  654.             environmentLookupTexturesCount = unpack('I', scmap.read(4) )[0]
  655.             debugPrint( "environmentLookupTexturesCount", environmentLookupTexturesCount )
  656.  
  657.             for i in range(environmentLookupTexturesCount):
  658.                 environmentLookupTexturesLabel = readcstr(scmap)
  659.                 debugPrint( "environmentLookupTexturesLabel", environmentLookupTexturesLabel )
  660.                 environmentLookupTexturesFile = readcstr(scmap)
  661.                 debugPrint( "environmentLookupTexturesFile", environmentLookupTexturesFile )
  662.  
  663.         #######################################################################
  664.         ### Render Settings
  665.         #######################################################################
  666.  
  667.         lightingMultiplier = unpack('f', scmap.read(4) )[0]
  668.         debugPrint( "lightingMultiplier", lightingMultiplier )
  669.  
  670.         lightDirection = unpack('fff', scmap.read(12) )
  671.         debugPrint( "lightDirection", lightDirection )
  672.  
  673.         ambienceLightColor = unpack('fff', scmap.read(12) )
  674.         debugPrint( "ambienceLightColor", ambienceLightColor )
  675.  
  676.         lightColor = unpack('fff', scmap.read(12) )
  677.         debugPrint( "lightColor", lightColor )
  678.  
  679.         shadowFillColor = unpack('fff', scmap.read(12) )
  680.         debugPrint( "shadowFillColor", shadowFillColor )
  681.  
  682.         specularColor = unpack('ffff', scmap.read(16) )
  683.         debugPrint( "specularColor", specularColor )
  684.  
  685.         bloom = unpack('f', scmap.read(4) )[0]
  686.         debugPrint( "bloom", bloom )
  687.  
  688.         fogColor = unpack('fff', scmap.read(12) )
  689.         debugPrint( "fogColor", fogColor )
  690.  
  691.         fogStart = unpack('f', scmap.read(4) )[0]
  692.         debugPrint( "fogStart", fogStart )
  693.  
  694.         fogEnd = unpack('f', scmap.read(4) )[0]
  695.         debugPrint( "fogEnd", fogEnd )
  696.  
  697.         hasWater = unpack('c', scmap.read(1) )[0]
  698.         debugPrint( "hasWater", hasWater )
  699.  
  700.         waterElevation = unpack('f', scmap.read(4) )[0]
  701.         debugPrint( "waterElevation", waterElevation )
  702.  
  703.         waterElevationDeep = unpack('f', scmap.read(4) )[0]
  704.         debugPrint( "waterElevationDeep", waterElevationDeep )
  705.  
  706.         waterElevationAbyss = unpack('f', scmap.read(4) )[0]
  707.         debugPrint( "waterElevationAbyss", waterElevationAbyss )
  708.  
  709.  
  710.         surfaceColor = unpack('fff', scmap.read(12) )
  711.         debugPrint( "surfaceColor", surfaceColor )
  712.  
  713.         colorLerpMin = unpack('f', scmap.read(4) )[0]
  714.         debugPrint( "colorLerpMin", colorLerpMin )
  715.  
  716.         colorLerpMax = unpack('f', scmap.read(4) )[0]
  717.         debugPrint( "colorLerpMax", colorLerpMax )
  718.  
  719.         refraction = unpack('f', scmap.read(4) )[0]
  720.         debugPrint( "refraction", refraction )
  721.  
  722.         fresnelBias = unpack('f', scmap.read(4) )[0]
  723.         debugPrint( "fresnelBias", fresnelBias )
  724.  
  725.         fresnelPower = unpack('f', scmap.read(4) )[0]
  726.         debugPrint( "fresnelPower", fresnelPower )
  727.  
  728.         reflectionUnit = unpack('f', scmap.read(4) )[0]
  729.         debugPrint( "reflectionUnit", reflectionUnit )
  730.  
  731.         reflectionSky = unpack('f', scmap.read(4) )[0]
  732.         debugPrint( "reflectionSky", reflectionSky )
  733.  
  734.         sunShininess = unpack('f', scmap.read(4) )[0]
  735.         debugPrint( "sunShininess", sunShininess )
  736.  
  737.         sunStrength = unpack('f', scmap.read(4) )[0]
  738.         debugPrint( "sunStrength", sunStrength )
  739.  
  740.         sunGlow = unpack('f', scmap.read(4) )[0]
  741.         debugPrint( "sunGlow", sunGlow )
  742.  
  743.         unknown8 = unpack('f', scmap.read(4) )[0]
  744.         debugPrint( "unknown8", unknown8 )
  745.  
  746.         unknown9 = unpack('f', scmap.read(4) )[0]
  747.         debugPrint( "unknown9", unknown9 )
  748.  
  749.         sunColor = unpack('fff', scmap.read(12) )
  750.         debugPrint( "sunColor", sunColor )
  751.  
  752.         reflectionSun = unpack('f', scmap.read(4) )[0]
  753.         debugPrint( "reflectionSun", reflectionSun )
  754.  
  755.         unknown10 = unpack('f', scmap.read(4) )[0]
  756.         debugPrint( "unknown10", unknown10 )
  757.  
  758.         ### Texture Maps
  759.  
  760.         texPathWaterCubemap = readcstr(scmap)
  761.         debugPrint( "texPathWaterCubemap", texPathWaterCubemap )
  762.  
  763.         texPathWaterRamp = readcstr(scmap)
  764.         debugPrint( "texPathWaterRamp", texPathWaterRamp )
  765.  
  766.         for i in range(4):
  767.             debugPrint( "waveTexture", i )
  768.             normalsFrequency = unpack('f', scmap.read(4) )[0]
  769.             debugPrint( "normalsFrequency", normalsFrequency )
  770.  
  771.         for i in range(4):
  772.             debugPrint( "waveTexture", i )
  773.             waveTextureScaleX = unpack('f', scmap.read(4) )[0]
  774.             debugPrint( "waveTextureScaleX", waveTextureScaleX )
  775.             waveTextureScaleY = unpack('f', scmap.read(4) )[0]
  776.             debugPrint( "waveTextureScaleY", waveTextureScaleY )
  777.             waveTexturePath = readcstr(scmap)
  778.             debugPrint( "waveTexturePath", waveTexturePath )
  779.  
  780.         waveGeneratorCount = unpack('I', scmap.read(4) )[0]
  781.         debugPrint( "waveGeneratorCount", waveGeneratorCount )
  782.         for i in range(waveGeneratorCount):
  783.             debugPrint( "waveGenerator", i )
  784.             textureName = readcstr(scmap)
  785.             debugPrint( "textureName", textureName )
  786.             rampName = readcstr(scmap)
  787.             debugPrint( "rampName", rampName )
  788.             position = unpack('fff', scmap.read(12) )
  789.             debugPrint( "position", position )
  790.             rotation = unpack('f', scmap.read(4) )[0]
  791.             debugPrint( "rotation", rotation )
  792.             velocity = unpack('fff', scmap.read(12) )
  793.             debugPrint( "velocity", velocity )
  794.             lifetimeFirst = unpack('f', scmap.read(4) )[0]
  795.             debugPrint( "lifetimeFirst", lifetimeFirst )
  796.             lifetimeSecond = unpack('f', scmap.read(4) )[0]
  797.             debugPrint( "lifetimeSecond", lifetimeSecond )
  798.             periodFirst = unpack('f', scmap.read(4) )[0]
  799.             debugPrint( "periodFirst", periodFirst )
  800.             periodSecond = unpack('f', scmap.read(4) )[0]
  801.             debugPrint( "periodSecond", periodSecond )
  802.             scaleFirst = unpack('f', scmap.read(4) )[0]
  803.             debugPrint( "scaleFirst", scaleFirst )
  804.             scaleSecond = unpack('f', scmap.read(4) )[0]
  805.             debugPrint( "scaleSecond", scaleSecond )
  806.             frameCount = unpack('f', scmap.read(4) )[0]
  807.             debugPrint( "frameCount", frameCount )
  808.             frameRateFirst = unpack('f', scmap.read(4) )[0]
  809.             debugPrint( "frameRateFirst", frameRateFirst )
  810.             frameRateSecond = unpack('f', scmap.read(4) )[0]
  811.             debugPrint( "frameRateSecond", frameRateSecond )
  812.             stripCount = unpack('f', scmap.read(4) )[0]
  813.             debugPrint( "stripCount", stripCount )
  814.  
  815.         if fileVersionMinor >= 59:
  816.             unkownData12 = scmap.read(28)
  817.             debugPrint( "unkownData12", unkownData12.hex( ))
  818.         elif fileVersionMinor > 53:
  819.             unkownData12 = scmap.read(24)
  820.             debugPrint( "unkownData12", unkownData12.hex( ))
  821.         else:
  822.             noTileset = readcstr(scmap)
  823.             debugPrint( "noTileset", noTileset )
  824.  
  825.  
  826.         if fileVersionMinor > 53:
  827.  
  828.             strata = ['LowerStratum','Stratum1','Stratum2','Stratum3','Stratum4','Stratum5','Stratum6','Stratum7','Stratum8','UpperStratum']
  829.             debugPrint( "strata", strata )
  830.  
  831.             for stratum in strata:
  832.                 debugPrint( "stratum", stratum )
  833.                 albedoFile = readcstr(scmap)
  834.                 debugPrint( "albedoFile", albedoFile )
  835.                 albedoScale = unpack('f', scmap.read(4) )[0]
  836.                 debugPrint( "albedoScale", albedoScale )
  837.  
  838.             for stratum in strata:
  839.                 # fucking special cases
  840.                 if stratum == 'UpperStratum':
  841.                     # no Normal for UpperStratum
  842.                     continue
  843.                 debugPrint( "stratum", stratum )
  844.                 normalFile = readcstr(scmap)
  845.                 debugPrint( "normalFile", normalFile )
  846.                 normalScale = unpack('f', scmap.read(4) )[0]
  847.                 debugPrint( "normalScale", normalScale )
  848.  
  849.         else:
  850.  
  851.             strataCount = unpack('I', scmap.read(4) )[0]
  852.             debugPrint( "strataCount", strataCount )
  853.             for stratum in range(strataCount):
  854.                 debugPrint( "stratum", stratum )
  855.                 albedoFile = readcstr(scmap)
  856.                 debugPrint( "albedoFile", albedoFile )
  857.                 normalFile = readcstr(scmap)
  858.                 debugPrint( "normalFile", normalFile )
  859.                 albedoScale = unpack('f', scmap.read(4) )[0]
  860.                 debugPrint( "albedoScale", albedoScale )
  861.                 normalScale = unpack('f', scmap.read(4) )[0]
  862.                 debugPrint( "normalScale", normalScale )
  863.  
  864.         unknown13 = unpack('I', scmap.read(4) )[0]
  865.         debugPrint( "unknown13", unknown13 )
  866.  
  867.         unknown14 = unpack('I', scmap.read(4) )[0]
  868.         debugPrint( "unknown14", unknown14 )
  869.  
  870.         #######################################################################
  871.         ### Decals
  872.         #######################################################################
  873.  
  874.         decalsBlockStartOffset = scmap.tell()
  875.         infos["decalsBlockStartOffset"] = decalsBlockStartOffset
  876.  
  877.         decalsCount = unpack('I', scmap.read(4) )[0]
  878.         debugPrint( "decalsCount", decalsCount )
  879.  
  880.         decalsList = []
  881.         for decalIndex in range(decalsCount):
  882.  
  883.             decalId = unpack('I', scmap.read(4) )[0]
  884.             debugPrint( "decalId", decalId )
  885.  
  886.             # albedo(1), normals(2)
  887.             decalType = unpack('I', scmap.read(4) )[0]
  888.             debugPrint( "decalType", decalType )
  889.  
  890.             unknown15 = unpack('I', scmap.read(4) )[0]
  891.             debugPrint( "unknown15", unknown15 )
  892.  
  893.             decalsTexture1PathLength = unpack('I', scmap.read(4) )[0]
  894.             debugPrint( "decalsTexture1PathLength", decalsTexture1PathLength )
  895.  
  896.             if decalsTexture1PathLength > 1024:
  897.                 raise MapParsingException( "decalsTexture1PathLength", scmap )
  898.  
  899.             decalsTexture1Path = scmap.read(decalsTexture1PathLength)
  900.             debugPrint( "decalsTexture1Path", decalsTexture1Path )
  901.  
  902.             decalsTexture2PathLength = unpack('I', scmap.read(4) )[0]
  903.             debugPrint( "decalsTexture2PathLength", decalsTexture2PathLength )
  904.  
  905.             if decalsTexture2PathLength > 1024:
  906.                 raise MapParsingException( "decalsTexture2PathLength", scmap )
  907.  
  908.             if decalsTexture2PathLength > 0:
  909.                 decalsTexture2Path = scmap.read(decalsTexture2PathLength)
  910.                 debugPrint( "decalsTexture2Path", decalsTexture2Path )
  911.             else:
  912.                 decalsTexture2Path = b''
  913.  
  914.             scale = unpack('fff', scmap.read(12) )
  915.             debugPrint( "scale", scale )
  916.  
  917.             position = unpack('fff', scmap.read(12) )
  918.             debugPrint( "position", position )
  919.  
  920.             rotation = unpack('fff', scmap.read(12) )
  921.             debugPrint( "rotation", rotation )
  922.  
  923.             cutOffLOD = unpack('f', scmap.read(4) )[0]
  924.             debugPrint( "cutOffLOD", cutOffLOD )
  925.  
  926.             nearCutOffLOD = unpack('f', scmap.read(4) )[0]
  927.             debugPrint( "nearCutOffLOD", nearCutOffLOD )
  928.  
  929.             removeTick = unpack('I', scmap.read(4) )[0]
  930.             debugPrint( "removeTick", removeTick )
  931.  
  932.             decal = [
  933.                 decalId,decalType,unknown15,
  934.                 decalsTexture1Path,decalsTexture2Path,
  935.                 scale,position,rotation,
  936.                 cutOffLOD,nearCutOffLOD,removeTick
  937.                 ]
  938.  
  939.             decalsList.append(decal)
  940.  
  941.         infos["decalsList"] = decalsList
  942.  
  943.         decalsBlockEndOffset = scmap.tell()
  944.         infos["decalsBlockEndOffset"] = decalsBlockEndOffset
  945.  
  946.         decalGroupsCount = unpack('I', scmap.read(4) )[0]
  947.         debugPrint( "decalGroupsCount", decalGroupsCount )
  948.         for decalGroupIndex in range(decalGroupsCount):
  949.             decalGroupId = unpack('I', scmap.read(4) )[0]
  950.             debugPrint( "decalGroupId", decalGroupId )
  951.             decalGroupName = readcstr(scmap)
  952.             debugPrint( "decalGroupName", decalGroupName )
  953.             decalGroupEntriesCount = unpack('I', scmap.read(4) )[0]
  954.             debugPrint( "decalGroupEntriesCount", decalGroupEntriesCount )
  955.             for i in range(decalGroupEntriesCount):
  956.                 decalGroupEntry = unpack('I', scmap.read(4) )[0]
  957.                 debugPrint( "decalGroupEntry", decalGroupEntry )
  958.  
  959.         #######################################################################
  960.         ### Some DDS files
  961.         #######################################################################
  962.  
  963.         (unknown19Width,unknown19Height) = unpack('II', scmap.read(8) )
  964.         debugPrint( "unknown19Width", unknown19Width )
  965.         debugPrint( "unknown19Height", unknown19Height )
  966.  
  967.         # most often 1, sometimes 4
  968.         normalMapsCount = unpack('I', scmap.read(4) )[0]
  969.         debugPrint( "normalMapsCount", normalMapsCount )
  970.         for normalMapIndex in range(normalMapsCount):
  971.  
  972.             normalMapDataLength = unpack('I', scmap.read(4) )[0]
  973.             debugPrint( "normalMapDataLength", normalMapDataLength )
  974.             normalMapData = scmap.read(normalMapDataLength)
  975.             debugPrint( "normalMapData", "{}...".format(normalMapData[:4]) )
  976.  
  977.         if fileVersionMinor < 56:
  978.             unknown20 = unpack('I', scmap.read(4) )[0]
  979.             debugPrint( "unknown20", unknown20 )
  980.  
  981.         textureMapDataLength = unpack('I', scmap.read(4) )[0]
  982.         debugPrint( "textureMapDataLength", textureMapDataLength )
  983.         textureMapData = scmap.read(textureMapDataLength)
  984.         debugPrint( "textureMapData", "{}...".format(textureMapData[:4]) )
  985.  
  986.         if fileVersionMinor < 56:
  987.             unknown21 = unpack('I', scmap.read(4) )[0]
  988.             debugPrint( "unknown21", unknown21 )
  989.  
  990.         waterMapDataLength = unpack('I', scmap.read(4) )[0]
  991.         debugPrint( "waterMapDataLength", waterMapDataLength )
  992.         waterMapData = scmap.read(waterMapDataLength)
  993.         debugPrint( "waterMapData", "{}...".format(waterMapData[:4]) )
  994.  
  995.         if fileVersionMinor > 53:
  996.             unknown22 = unpack('I', scmap.read(4) )[0]
  997.             debugPrint( "unknown22", unknown22 )
  998.  
  999.             unknown23MapDataLength = unpack('I', scmap.read(4) )[0]
  1000.             debugPrint( "unknown23MapDataLength", unknown23MapDataLength )
  1001.             unknown23MapData = scmap.read(unknown23MapDataLength)
  1002.             debugPrint( "unknown23MapData", "{}...".format(unknown23MapData[:4]) )
  1003.             open('/tmp/unknown23MapData.dds','wb').write(unknown23MapData)
  1004.  
  1005.         someWaterMapLength = int( (mapWidth / 2) * (mapHeight / 2) )
  1006.         waterFoamMapData = scmap.read(someWaterMapLength)
  1007.         debugPrint( "waterFoamMapData", "{}...".format(waterFoamMapData[:4]) )
  1008.         waterFlatnessMapData = scmap.read(someWaterMapLength)
  1009.         debugPrint( "waterFlatnessMapData", "{}...".format(waterFlatnessMapData[:4]) )
  1010.         waterDepthBiasMapData = scmap.read(someWaterMapLength)
  1011.         debugPrint( "waterDepthBiasMapData", "{}...".format(waterDepthBiasMapData[:4]) )
  1012.  
  1013.         terrainTypeDataLength = mapWidth * mapHeight
  1014.         debugPrint( "terrainTypeDataLength", "{}...".format(terrainTypeDataLength) )
  1015.         terrainTypeData = scmap.read(terrainTypeDataLength)
  1016.         debugPrint( "terrainTypeData", "{}...".format(terrainTypeData[:4]) )
  1017.  
  1018.         if fileVersionMinor < 53:
  1019.             unknown24 = unpack('h', scmap.read(2) )[0]
  1020.             debugPrint( "unknown24", unknown24 )
  1021.  
  1022.  
  1023.         if fileVersionMinor >= 59:
  1024.             unknown25 = scmap.read(64)
  1025.             debugPrint( "unknown25", unknown25[:4] )
  1026.             unknown26String = readcstr(scmap)
  1027.             debugPrint( "unknown26String", unknown26String )
  1028.             unknown27String = readcstr(scmap)
  1029.             debugPrint( "unknown27String", unknown27String )
  1030.             unknown28 = unpack('I', scmap.read(4) )[0]
  1031.             debugPrint( "unknown28", unknown28 )
  1032.             unknown28MagicFactor = 40
  1033.             if unknown28 > 0:
  1034.                 unknown29 = scmap.read( unknown28 * unknown28MagicFactor )
  1035.                 debugPrint( "unknown29", unknown29[:4] )
  1036.             unknown30 = scmap.read(19)
  1037.             debugPrint( "unknown30", unknown30 )
  1038.             unknown31String = readcstr(scmap)
  1039.             debugPrint( "unknown31String", unknown31String )
  1040.  
  1041.             unknown31 = scmap.read(88)
  1042.             debugPrint( "unknown31", unknown31[:4] )
  1043.  
  1044.         propsBlockStartOffset = scmap.tell()
  1045.         infos["propsBlockStartOffset"] = propsBlockStartOffset
  1046.  
  1047.         propsCount = unpack('I', scmap.read(4) )[0]
  1048.         debugPrint( "propsCount", propsCount )
  1049.  
  1050.         propsList = []
  1051.         for propIndex in range( 0, propsCount ):
  1052.             blueprintPath = readcstr(scmap)
  1053.             debugPrint( "blueprintPath", blueprintPath )
  1054.             position = unpack('fff', scmap.read(12) )
  1055.             debugPrint( "position", position )
  1056.             rotationX = unpack('fff', scmap.read(12) )
  1057.             debugPrint( "rotationX", rotationX )
  1058.             rotationY = unpack('fff', scmap.read(12) )
  1059.             debugPrint( "rotationY", rotationY )
  1060.             rotationZ = unpack('fff', scmap.read(12) )
  1061.             debugPrint( "rotationZ", rotationZ )
  1062.             scale = unpack('fff', scmap.read(12) )
  1063.             debugPrint( "scale", scale )
  1064.             # add this prop to prop to props list
  1065.             propsList.append( [ blueprintPath,position,rotationX,rotationY,rotationZ,scale ] )
  1066.  
  1067.         infos["propsList"] = propsList + listOfDebugProps
  1068.  
  1069.     return infos
  1070.  
  1071. def writeMirroredMap( pathToScmap, pathToNewScmap, infos ):
  1072.     with open(pathToScmap,'rb') as scmap:
  1073.         with open(pathToNewScmap,'wb') as newscmap:
  1074.  
  1075.             newscmap.write( scmap.read( infos['heightMapStartOffset'] ))
  1076.  
  1077.             newscmap.write( infos['heightMapData'] )
  1078.  
  1079.             scmap.seek(infos["heightMapEndOffset"])
  1080.  
  1081.             newscmap.write( scmap.read( infos["decalsBlockStartOffset"] - infos["heightMapEndOffset"] ))
  1082.  
  1083.             writeDecals( newscmap, infos["decalsList"] )
  1084.  
  1085.             scmap.seek(infos["decalsBlockEndOffset"])
  1086.  
  1087.             newscmap.write(scmap.read( infos["propsBlockStartOffset"] -infos["decalsBlockEndOffset"] ))
  1088.  
  1089.             writeProps( newscmap, infos["propsList"] )
  1090.  
  1091. def writeDecals( newscmap, decalsList ):
  1092.     newscmap.write(pack('I',len(decalsList)))
  1093.     for decal in decalsList:
  1094.         (
  1095.             decalId,decalType,unknown15,
  1096.             decalsTexture1Path,decalsTexture2Path,
  1097.             scale,position,rotation,
  1098.             cutOffLOD,nearCutOffLOD,removeTick
  1099.         ) = decal
  1100.         decalRaw = pack('IIII',decalId,decalType,unknown15,len(decalsTexture1Path))
  1101.         decalRaw += decalsTexture1Path
  1102.         decalRaw += pack('I',len(decalsTexture2Path))
  1103.         if decalsTexture2Path:
  1104.             decalRaw += decalsTexture2Path
  1105.         decalRaw += pack('fffffffff',*scale,*position,*rotation)
  1106.         decalRaw += pack('ffI',cutOffLOD,nearCutOffLOD,removeTick)
  1107.         newscmap.write(decalRaw)
  1108.  
  1109. def writeProps( newscmap, propsList ):
  1110.     newscmap.write(pack('I',len(propsList)))
  1111.     for prop in propsList:
  1112.         (blueprintPath,position,rotationX,rotationY,rotationZ,scale) = prop
  1113.         newscmap.write(blueprintPath)
  1114.         newscmap.write(b'\0')
  1115.         newscmap.write(pack('fffffffffffffff',*(*position,*rotationX,*rotationY,*rotationZ,*scale)))
  1116.  
  1117. if __name__ == '__main__':
  1118.     main()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top