public AssetReader(Stream asset, Stream exp, Stream bulk = null, bool ignoreErrors = true) { BinaryReader reader = new BinaryReader(asset); var summary = new AssetSummary(reader); reader.BaseStream.Seek(summary.name_offset, SeekOrigin.Begin); FNameEntrySerialized[] name_map = new FNameEntrySerialized[summary.name_count]; for (int i = 0; i < summary.name_count; i++) { name_map[i] = new FNameEntrySerialized(reader); } reader.BaseStream.Seek(summary.import_offset, SeekOrigin.Begin); FObjectImport[] import_map = new FObjectImport[summary.import_count]; for (int i = 0; i < summary.import_count; i++) { import_map[i] = new FObjectImport(reader, name_map, import_map); } reader.BaseStream.Seek(summary.export_offset, SeekOrigin.Begin); FObjectExport[] export_map = new FObjectExport[summary.export_count]; for (int i = 0; i < summary.export_count; i++) { export_map[i] = new FObjectExport(reader, name_map, import_map); } long export_size = export_map.Sum(v => v.serial_size); reader = new BinaryReader(exp); var bulkReader = bulk == null ? null : new BinaryReader(bulk); int asset_length = summary.total_header_size; Exports = new ExportObject[summary.export_count]; int ind = 0; foreach (FObjectExport v in export_map) { string export_type = v.class_index.import; long position = v.serial_offset - asset.Length; reader.BaseStream.Seek(position, SeekOrigin.Begin); try { switch (export_type) { case "Texture2D": Exports[ind] = new Texture2D(reader, name_map, import_map, asset_length, export_size, bulkReader); break; case "FontFace": Exports[ind] = new FontFace(reader, name_map, import_map); break; case "DataTable": Exports[ind] = new UDataTable(reader, name_map, import_map); break; case "CurveTable": Exports[ind] = new UCurveTable(reader, name_map, import_map); break; case "SkeletalMesh": Exports[ind] = new USkeletalMesh(reader, name_map, import_map); break; case "AnimSequence": Exports[ind] = new UAnimSequence(reader, name_map, import_map); break; case "Skeleton": Exports[ind] = new USkeleton(reader, name_map, import_map); break; case "SoundWave": Exports[ind] = new USoundWave(reader, name_map, import_map, asset_length, export_size, bulkReader); break; default: Exports[ind] = new UObject(reader, name_map, import_map, export_type, true); break; } } catch (Exception e) { if (!ignoreErrors) { throw e; } } long valid_pos = position + v.serial_size; if (reader.BaseStream.Position != valid_pos) { Console.WriteLine($"Did not read {export_type} correctly. Current Position: {reader.BaseStream.Position}, Bytes Remaining: {valid_pos - reader.BaseStream.Position}"); reader.BaseStream.Seek(valid_pos, SeekOrigin.Begin); } ind++; } //Exports[Exports.Length - 1] = new AssetInfo(name_map, import_map, export_map); }
public CSkeletalMesh(USkeletalMesh mesh) { OriginalMesh = mesh; // convert bounds BoundingSphere = new FSphere { R = mesh.Bounds.sphere_radius / 2 }; BoundingBox = new FBox { Min = mesh.Bounds.origin - mesh.Bounds.box_extend, Max = mesh.Bounds.origin + mesh.Bounds.box_extend }; // MeshScale, MeshOrigin, RotOrigin are removed in UE4 //!! NOTE: MeshScale is integrated into RefSkeleton.RefBonePose[0].Scale3D. //!! Perhaps rotation/translation are integrated too! MeshOrigin = new CVec3 { v = new float[] { 0, 0, 0 } }; RotOrigin = new FRotator { pitch = 0, roll = 0, yaw = 0 }; MeshScale = new CVec3 { v = new float[] { 1, 1, 1 } }; // convert LODs Lods = new CSkelMeshLod[mesh.LODModels.Length]; for (int i = 0; i < Lods.Length; i++) { var SrcLod = mesh.LODModels[i]; if (SrcLod.Indices.Indices16.Length == 0 && SrcLod.Indices.Indices32.Length == 0) { // No indicies in this lod continue; } int NumTexCoords = SrcLod.NumTexCoords; if (NumTexCoords > 8) { throw new FileLoadException($"SkeletalMesh has too many ({NumTexCoords}) UV sets"); } CSkelMeshLod Lod = new CSkelMeshLod { NumTexCoords = NumTexCoords, HasNormals = true, HasTangents = true, }; // get vertex count and determine vertex source int VertexCount = SrcLod.VertexBufferGPUSkin.GetVertexCount(); bool bUseVerticesFromSections = false; // if (VertexCount == 0 && SrcLod.Sections.Length > 0 && SrcLod.Sections[0].SoftVertices.Count > 0) // above is used for editor assets, but there are no chunks for soft vertices Lod.AllocateVerts(VertexCount); int chunkIndex = -1; int lastChunkVertex = -1; int chunkVertexIndex = 0; ushort[] BoneMap = null; for (int j = 0; j < VertexCount; j++) { while (j >= lastChunkVertex) // this will fix any issues with empty chunks or sections { // UE4.13+ code: chunk information migrated to sections FSkelMeshSection S = SrcLod.Sections[++chunkIndex]; lastChunkVertex = (int)(S.base_vertex_index + S.num_vertices); BoneMap = S.bone_map; chunkVertexIndex = 0; } // get vertex from GPU skin FSkelMeshVertexBase V; if (!SrcLod.VertexBufferGPUSkin.bUseFullPrecisionUVs) { FGPUVert4Half V0 = SrcLod.VertexBufferGPUSkin.VertsHalf[j]; FMeshUVHalf[] SrcUV = V0.UV; V = new FSkelMeshVertexBase { Infs = V0.Infs, Normal = V0.Normal, Pos = V0.Pos }; // UV: convert half -> float Lod.Verts[j].UV = (FMeshUVFloat)SrcUV[0]; for (int TexCoordIndex = 1; TexCoordIndex < NumTexCoords; TexCoordIndex++) { Lod.ExtraUV[TexCoordIndex - 1][j] = (FMeshUVFloat)SrcUV[TexCoordIndex]; } } else { FGPUVert4Float V0 = SrcLod.VertexBufferGPUSkin.VertsFloat[j]; FMeshUVFloat[] SrcUV = V0.UV; V = new FSkelMeshVertexBase { Infs = V0.Infs, Normal = V0.Normal, Pos = V0.Pos }; // UV: convert half -> float Lod.Verts[j].UV = SrcUV[0]; for (int TexCoordIndex = 1; TexCoordIndex < NumTexCoords; TexCoordIndex++) { Lod.ExtraUV[TexCoordIndex - 1][j] = SrcUV[TexCoordIndex]; } } Lod.Verts[j].Position = V.Pos; Lod.Verts[j].UnpackNormals(V.Normal); // convert influences Lod.Verts[j].Bone = new short[4]; int k2 = 0; uint PackedWeights = 0; for (int k = 0; k < 4; k++) { int BoneIndex = V.Infs.bone_index[k]; byte BoneWeight = V.Infs.bone_weight[k]; if (BoneWeight == 0) { continue; // skip this influence (but do not stop the loop!) } PackedWeights |= (uint)(BoneWeight << (k2 * 8)); Lod.Verts[j].Bone[k2] = (short)BoneMap[BoneIndex]; k2++; } Lod.Verts[j].PackedWeights = PackedWeights; if (k2 < 4) { Lod.Verts[j].Bone[k2] = -1; // mark end of list } } // indices Lod.Indices.Initialize(SrcLod.Indices.Indices16, SrcLod.Indices.Indices32); // sections Lod.Sections = new CMeshSection[SrcLod.Sections.Length]; FSkeletalMeshLODInfo Info = mesh.LODInfo[i]; for (int j = 0; j < SrcLod.Sections.Length; j++) { FSkelMeshSection S = SrcLod.Sections[j]; CMeshSection Dst = new CMeshSection(); // remap material for LOD int MaterialIndex = S.material_index; if (MaterialIndex >= 0 && MaterialIndex < Info.LODMaterialMap.Length) { MaterialIndex = Info.LODMaterialMap[MaterialIndex]; } if (S.material_index < mesh.Materials.Length) { Dst.Material = new UUnrealMaterial();// mesh.Materials[MaterialIndex].Material; } // -> TODO: actually get the object from the pak Dst.FirstIndex = (int)S.base_index; Dst.NumFaces = (int)S.num_triangles; Lod.Sections[j] = Dst; } Lods[i] = Lod; } // copy skeleton int NumBones = mesh.RefSkeleton.ref_bone_info.Length; RefSkeleton = new CSkelMeshBone[NumBones]; for (int i = 0; i < NumBones; i++) { FMeshBoneInfo B = mesh.RefSkeleton.ref_bone_info[i]; FTransform T = mesh.RefSkeleton.ref_bone_pose[i]; CSkelMeshBone Dst = new CSkelMeshBone { Name = B.name, ParentIndex = B.parent_index, Position = T.translation, Orientation = T.rotation }; // fix skeleton; all bones but 0 if (i >= 1) { Dst.Orientation.Conjugate(); } RefSkeleton[i] = Dst; } Sockets = null; // dunno where this is set FinalizeMesh(); }