import struct from enum import Enum from progressDisplay import percent_complete from texFormat import DDS virtualSegmentSize = 0x10 pageInfoSize = 0xC descriptorSize = 0x8 assetEntrySize = 0x48 guidDescriptorSize = 0x8 fileRelationSize = 0x4 externalAssetSize = 0x4 def getStringFromPage(bytes:bytes, offset:int): endOfString = False string = '' while(endOfString == False): data = bytes[offset:offset+1] if(data == b'\x00'): endOfString = True string += data.decode() offset += 1 return string[:-1] class ShaderTypes(Enum): PIXEL = 0 VERTEX = 1 GEOMETRY = 2 HARDWARE = 3 DOMAIN = 4 COMPUTE = 5 # Rpak class Descriptor: def __init__(self, data:bytes): self.index = int.from_bytes(data[0:4], byteorder='little') self.offset = int.from_bytes(data[4:8], byteorder='little') class PageInfo: def __init__(self, data:bytes, offset:int = 0): self.segmentIndex = int.from_bytes(data[0x0:0x4], byteorder='little') self.align = int.from_bytes(data[0x4:0x8], byteorder='little') self.dataSize = int.from_bytes(data[0x8:0xC], byteorder='little') self.offset = offset self.bytes = data class AssetEntry: def __init__(self, data:bytes): self.nameHash = int.from_bytes(data[0:8], byteorder='little') self.padding = int.from_bytes(data[8:0x10], byteorder='little') self.SubHeaderPageIndex = int.from_bytes(data[0x10:0x14], byteorder='little') self.SubHeaderPageOffset = int.from_bytes(data[0x14:0x18], byteorder='little', signed=True) self.RawDataPageIndex = int.from_bytes(data[0x18:0x1C], byteorder='little', signed=True) self.RawDataPageOffset = int.from_bytes(data[0x1C:0x20], byteorder='little', signed=True) self.StarpakOffset = int.from_bytes(data[0x20:0x28], byteorder='little', signed=True) self.HighestPageNum = int.from_bytes(data[0x28:0x2A], byteorder='little') # self.Un2 = int.from_bytes(data[0x2A:0x2C], byteorder='little') self.RelationsStartIndex = int.from_bytes(data[0x2C:0x30], byteorder='little') self.UsesStartIndex = int.from_bytes(data[0x30:0x34], byteorder='little') self.RelationsCount = int.from_bytes(data[0x34:0x38], byteorder='little') self.UsesCount = int.from_bytes(data[0x38:0x3A], byteorder='little') # self.unk = int.from_bytes(data[0x3A:0x3C], byteorder='little') self.SubHeaderSize = int.from_bytes(data[0x3C:0x40], byteorder='little') self.Version = int.from_bytes(data[0x40:0x44], byteorder='little') self.Magic = data[0x44:0x48].decode() # Pages # Assets class ShaderHeader: def __init__(self, data:bytes, pages: list[bytes]): pNameIndex = int.from_bytes(data[0:4], byteorder='little') pNameOffset = int.from_bytes(data[4:8], byteorder='little') self.name = getStringFromPage(pages[pNameIndex], pNameOffset) self.ShaderType = ShaderTypes(int.from_bytes(data[8:9], byteorder='little')) self.padd = struct.unpack('ccc',data[0x9:0xC]) self.dataCount = int.from_bytes(data[0xC:0x10], byteorder='little') # 16 bytes unknown data class ShaderSetHeader: def __init__(self, data:bytes, pages: list[bytes]): # self.reservedVtbl = int.from_bytes(data[0:8], byteorder='little') pNameIndex = int.from_bytes(data[8:0xC], byteorder='little') pNameOffset = int.from_bytes(data[0xC:0x10], byteorder='little') self.name = getStringFromPage(pages[pNameIndex], pNameOffset) # 8 bytes unknown # self.count1 = int.from_bytes(data[0x18:0x1A], byteorder='little') self.textureInputCount = int.from_bytes(data[0x1A:0x1C], byteorder='little') # self.count3 = int.from_bytes(data[0x1C:0x1E], byteorder='little') # self.byte1 = int.from_bytes(data[0x1E:0x1F], byteorder='little') # self.byte2 = int.from_bytes(data[0x1F:0x20], byteorder='little') # 8 bytes unknown # self.unk1 = int.from_bytes(data[0x28:0x30], byteorder='little') # self.unk2 = int.from_bytes(data[0x30:0x38], byteorder='little') # self.unk3 = int.from_bytes(data[0x38:0x40], byteorder='little') # self.unk4 = int.from_bytes(data[0x40:0x48], byteorder='little') self.vertexShaderGuid = int.from_bytes(data[0x48:0x50], byteorder='little') self.pixelShaderGuid = int.from_bytes(data[0x50:0x58], byteorder='little') class MaterialHeader: def __init__(self, data:bytes, pages:list[bytes]): # self.reservedVtbl = int.from_bytes(data[0:8], byteorder='little') # self.padding = int.from_bytes(data[8:0x10], byteorder='little') self.guid = int.from_bytes(data[0x10:0x18], byteorder='little') pNameIndex = int.from_bytes(data[0x18:0x1C], byteorder='little') pNameOffset = int.from_bytes(data[0x1C:0x20], byteorder='little') self.name = getStringFromPage(pages[pNameIndex], pNameOffset) self.pSurfacePropIndex = int.from_bytes(data[0x20:0x24], byteorder='little') self.pSurfacePropOffset = int.from_bytes(data[0x24:0x28], byteorder='little') self.pSurfaceProp2Index = int.from_bytes(data[0x28:0x2C], byteorder='little') self.pSurfaceProp2Offset = int.from_bytes(data[0x2C:0x30], byteorder='little') self.guidRef1 = int.from_bytes(data[0x30:0x38], byteorder='little') self.guidRef2 = int.from_bytes(data[0x38:0x40], byteorder='little') self.guidRef3 = int.from_bytes(data[0x40:0x48], byteorder='little') self.guidRef4 = int.from_bytes(data[0x48:0x50], byteorder='little') # 40 bytes unknownMaterialSectionV12 self.shadersetGuid = int.from_bytes(data[0x90:0x98], byteorder='little') self.pTextureHandlesIndex = int.from_bytes(data[0x98:0x9C], byteorder='little') self.pTextureHandlesOffset = int.from_bytes(data[0x9C:0xA0], byteorder='little') self.pStreamingTextureHandlesIndex = int.from_bytes(data[0xA0:0xA4], byteorder='little') self.pStreamingTextureHandlesOffset = int.from_bytes(data[0xA4:0xA8], byteorder='little') self.streamingTextureCount = int.from_bytes(data[0xA8:0xAA], byteorder='little') # self.unknown = int.from_bytes(data[0xAA:0xAE], byteorder='little') self.flags = int.from_bytes(data[0xAE:0xB0], byteorder='little') # self.unknown = int.from_bytes(data[0xB0:0xB2], byteorder='little') # self.reserved = int.from_bytes(data[0xB2:0xBA], byteorder='little') # self.unknown = int.from_bytes(data[0xBA:0xBE], byteorder='little') # self.unknown = int.from_bytes(data[0xBE:0xC2], byteorder='little') self.flags2 = int.from_bytes(data[0xC2:0xCA], byteorder='little') self.width = int.from_bytes(data[0xCA:0xCC], byteorder='little') self.height = int.from_bytes(data[0xCC:0xCE], byteorder='little') # self.unknown = int.from_bytes(data[0xCE:0xD0], byteorder='little') class TextureHeader: def __init__(self, data:bytes, pages:list[bytes]): self.guid = int.from_bytes(data[0:8], byteorder='little') pNameIndex = int.from_bytes(data[0x8:0xC], byteorder='little') pNameOffset = int.from_bytes(data[0xC:0x10], byteorder='little') self.name = getStringFromPage(pages[pNameIndex], pNameOffset) self.width = int.from_bytes(data[0x10:0x12], byteorder='little') self.height = int.from_bytes(data[0x12:0x14], byteorder='little') self.depth = int.from_bytes(data[0x14:0x16], byteorder='little') self.format = int.from_bytes(data[0x16:0x18], byteorder='little') self.dataSize = int.from_bytes(data[0x18:0x1C], byteorder='little') self.compressionType = int.from_bytes(data[0x1C:0x1D], byteorder='little') self.optStreamedMipCount = int.from_bytes(data[0x1D:0x1E], byteorder='little') self.arraySize = int.from_bytes(data[0x1E:0x1F], byteorder='little') self.layerCount = int.from_bytes(data[0x1F:0x20], byteorder='little') self.mipFlags = int.from_bytes(data[0x20:0x21], byteorder='little') self.permanentMipCount = int.from_bytes(data[0x21:0x22], byteorder='little') self.streamedMipCount = int.from_bytes(data[0x22:0x23], byteorder='little') # self.unknown = int.from_bytes(data[0x22:0x30], byteorder='little') self.numPixels = int.from_bytes(data[0x30:0x38], byteorder='little') class UIMGHeader: def __init__(self, data:bytes, pages:list[bytes]): self.widthRatio = struct.unpack('f',data[0:4]) self.heightRatio = struct.unpack('f',data[4:8]) self.width = int.from_bytes(data[0x8:0xA], byteorder='little') self.height = int.from_bytes(data[0xA:0xC], byteorder='little') self.textureOffsetsCount = int.from_bytes(data[0xC:0xE], byteorder='little') self.textureCount = int.from_bytes(data[0xE:0x10], byteorder='little') self.textureOffsetsIndex = int.from_bytes(data[0x10:0x14], byteorder='little') self.textureOffsetsOffset = int.from_bytes(data[0x14:0x18], byteorder='little') self.textureOffsetsDimsIndex = int.from_bytes(data[0x18:0x1C], byteorder='little') self.textureOffsetsDimsOffset = int.from_bytes(data[0x1C:0x20], byteorder='little') # self.unknown = int.from_bytes(data[0x20:0x24], byteorder='little') # self.unknown = int.from_bytes(data[0x24:0x28], byteorder='little') self.textureHashesIndex = int.from_bytes(data[0x28:0x2C], byteorder='little') self.textureHashesOffset = int.from_bytes(data[0x2C:0x30], byteorder='little') self.textureNamesIndex = int.from_bytes(data[0x30:0x34], byteorder='little') self.textureNamesOffset = int.from_bytes(data[0x34:0x38], byteorder='little') self.textureGuid = int.from_bytes(data[0x38:0x40], byteorder='little') class Texture: def __init__(self, assetEntry:AssetEntry, header:TextureHeader, pages:list[bytes]): self.assetEntry = assetEntry self.header = header dataPageIndex = assetEntry.RawDataPageIndex dataStart = assetEntry.RawDataPageOffset dataBytes = pages[dataPageIndex][dataStart:dataStart+header.dataSize] self.dds = DDS(dataBytes, header.height, header.width) if(header.name.endswith('leaves_ground_spread_01_col')): f = open("test.dds", "wb") f.write(self.dds.data) f.close() print() class Material: def __init__(self, assetEntry:AssetEntry, header:MaterialHeader, pages:list[bytes]): self.assetEntry = assetEntry self.header = header dataPageIndex = header.pTextureHandlesIndex dataStart = header.pTextureHandlesOffset dataBytes = pages[dataPageIndex][dataStart:] textureCount = int((header.pStreamingTextureHandlesOffset-header.pTextureHandlesOffset) / 8) # Streamed textures are ignored self.texturesGuid = [] self.textures: list[Texture] = [] for i in range(0,textureCount): textureGuid = int.from_bytes(dataBytes[(i*8):(i*8)+8], byteorder='little') self.texturesGuid.append(textureGuid) self.surfaceProp = getStringFromPage(dataBytes, (textureCount * 8)*2) def populateTextures(self, texturesByHash: dict[str, Texture]): self.textures.clear() for tex in self.texturesGuid: if(tex == 0): self.textures.append(None) continue if((tex in texturesByHash) == False): self.textures.append(None) print("Texture not found : " + str(hex(tex))) continue texture = texturesByHash[tex] self.textures.append(texture) class Shader: def __init__(self, assetEntry:AssetEntry, header:ShaderHeader): self.assetEntry = assetEntry self.header = header class ShaderSet: def __init__(self, assetEntry:AssetEntry, header:ShaderSetHeader): self.assetEntry = assetEntry self.header = header class UIMG: def __init__(self, assetEntry:AssetEntry, header:UIMGHeader): self.assetEntry = assetEntry self.header = header class Rpak: def __init__(self, file): with open(file, mode='rb') as file: fileContent = file.read() counts = struct.unpack('IIIIII',fileContent[0x40:0x58]) self.header = { 'rpak_version' :int.from_bytes(fileContent[4:6], byteorder='little'), 'flags':fileContent[6:7].decode(), 'isCompressed': struct.unpack('?',fileContent[7:8]), 'lenStarpakPaths' : int.from_bytes(fileContent[0x38:0x3A], byteorder='little'), 'virtualSegmentCount' : int.from_bytes(fileContent[0x3A:0x3C], byteorder='little'), 'pageInfoCount' : int.from_bytes(fileContent[0x3C:0x3E], byteorder='little'), # todo : patchIndex 'descriptorCount' : counts[0], 'assetEntryCount' : counts[1], 'guidDescriptorCount' : counts[2], 'fileRelationCount' : counts[3], 'externalAssetCount' : counts[4], 'externalAssetSize' : counts[5], } pointer = 0x58 self.starpakstr = fileContent[pointer:pointer+self.header['lenStarpakPaths']].decode() pointer += 0x20 self.VirtualSegment = fileContent[pointer:pointer+self.header['virtualSegmentCount'] * virtualSegmentSize] pointer += self.header['virtualSegmentCount'] * virtualSegmentSize rpakPageInfoBytes = [fileContent[pointer:pointer+self.header['pageInfoCount'] * pageInfoSize][i:i+pageInfoSize] for i in range(0,self.header['pageInfoCount'] * pageInfoSize, pageInfoSize)] self.PageInfo: list[PageInfo] = [] rpakPageInfoOffset = 0 for pageInfoBytes in rpakPageInfoBytes: info = PageInfo(pageInfoBytes, rpakPageInfoOffset) rpakPageInfoOffset += info.dataSize self.PageInfo.append(info) pointer += self.header['pageInfoCount'] * pageInfoSize rpakDescriptorBytes = [fileContent[pointer:pointer+self.header['descriptorCount'] * descriptorSize][i:i+descriptorSize] for i in range(0,self.header['descriptorCount'] * descriptorSize, descriptorSize)] self.Descriptor = list(map(Descriptor, rpakDescriptorBytes)) pointer += self.header['descriptorCount'] * descriptorSize rpakAssetsBytes = [fileContent[pointer:pointer+self.header['assetEntryCount'] * assetEntrySize][i:i+assetEntrySize] for i in range(0,self.header['assetEntryCount'] * assetEntrySize, assetEntrySize)] self.AssetEntries = list(map(AssetEntry, rpakAssetsBytes)) pointer += self.header['assetEntryCount'] * assetEntrySize # todo : guid descriptor pointer += self.header['guidDescriptorCount'] * guidDescriptorSize # todo : file relation pointer += self.header['fileRelationCount'] * fileRelationSize pointer += self.header['externalAssetCount'] * externalAssetSize pointer += self.header['externalAssetSize'] self.Pages: list[bytes] = [] for info in self.PageInfo: self.Pages.append(fileContent[info.offset+pointer:info.offset+pointer+info.dataSize]) self.Assets = { 'shaders':[], 'shaderSets':[], 'materials':[], 'textures':[], 'texturesByHash':{}, 'uimgs':[] } length = self.AssetEntries.__len__() for index, assetEntry in enumerate(self.AssetEntries): pageIndex = assetEntry.SubHeaderPageIndex headerStart = assetEntry.SubHeaderPageOffset headerEnd = assetEntry.SubHeaderPageOffset + assetEntry.SubHeaderSize headerBytes = self.Pages[pageIndex][headerStart:headerEnd] if(assetEntry.Magic == 'shdr'): header = ShaderHeader(headerBytes, self.Pages) asset = Shader(assetEntry, header) assetType = 'shaders' elif(assetEntry.Magic == 'shds'): header = ShaderSetHeader(headerBytes, self.Pages) asset = ShaderSet(assetEntry, header) assetType = 'shaderSets' elif(assetEntry.Magic == 'matl'): header = MaterialHeader(headerBytes, self.Pages) asset = Material(assetEntry, header, self.Pages) assetType = 'materials' elif(assetEntry.Magic == 'txtr'): header = TextureHeader(headerBytes, self.Pages) asset = Texture(assetEntry, header, self.Pages) assetType = 'textures' self.Assets['texturesByHash'][header.guid] = asset elif(assetEntry.Magic == 'uimg'): header = UIMGHeader(headerBytes, self.Pages) asset = UIMG(assetEntry, header) assetType = 'uimgs' else: continue self.Assets[assetType].append(asset) percent_complete(index, length, title="Importing Assets : " + file.name) percent_complete(length, length, title="Importing Assets : " + file.name) print() length = self.Assets['materials'].__len__() # for index, material in enumerate(self.Assets['materials']): # material.populateTextures(self.Assets['textures']) # percent_complete(index, length, title="Linking textures")