public void LoadWad(string fileName) { BaseName = Path.GetFileNameWithoutExtension(fileName); BasePath = Path.GetDirectoryName(fileName); FileName = fileName; logger.Info("Reading wad file: " + fileName); // Initialize stream using (BinaryReaderEx reader = new BinaryReaderEx(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))) { // Read wad version Version = reader.ReadInt32(); if (Version != 129 && Version != 130) { logger.Error("Wad version " + Version + " is not supported!"); throw new InvalidDataException(); } // Read textures uint numTextures = reader.ReadUInt32(); logger.Info("Wad object textures: " + numTextures); for (int i = 0; i < numTextures; i++) { wad_object_texture text; reader.ReadBlock(out text); Textures.Add(text); } uint numTextureBytes = reader.ReadUInt32(); logger.Info("Wad texture data size: " + numTextureBytes); TexturePages = reader.ReadBytes((int)numTextureBytes); NumTexturePages = (int)(numTextureBytes / 196608); // Read meshes uint numMeshPointers = reader.ReadUInt32(); logger.Info("Wad mesh pointers: " + numMeshPointers); for (int i = 0; i < numMeshPointers; i++) { Pointers.Add(reader.ReadUInt32()); RealPointers.Add(0); } uint numMeshWords = reader.ReadUInt32(); uint bytesToRead = numMeshWords * 2; uint bytesRead = 0; logger.Info("Wad mesh data size: " + bytesToRead); while (bytesRead < bytesToRead) { var startOfMesh = (uint)reader.BaseStream.Position; wad_mesh mesh = new wad_mesh(); mesh.Polygons = new List <wad_polygon>(); mesh.Vertices = new List <wad_vertex>(); mesh.Normals = new List <wad_vertex>(); mesh.Shades = new List <short>(); mesh.SphereX = reader.ReadInt16(); mesh.SphereY = reader.ReadInt16(); mesh.SphereZ = reader.ReadInt16(); mesh.Radius = reader.ReadUInt16(); mesh.Unknown = reader.ReadUInt16(); var numVertices = reader.ReadUInt16(); mesh.NumVertices = numVertices; var xMin = Int32.MaxValue; var yMin = Int32.MaxValue; var zMin = Int32.MaxValue; var xMax = Int32.MinValue; var yMax = Int32.MinValue; var zMax = Int32.MinValue; for (var i = 0; i < numVertices; i++) { var v = new wad_vertex(); v.X = reader.ReadInt16(); v.Y = reader.ReadInt16(); v.Z = reader.ReadInt16(); if (v.X < xMin) { xMin = v.X; } if (-v.Y < yMin) { yMin = -v.Y; } if (v.Z < zMin) { zMin = v.Z; } if (v.X > xMax) { xMax = v.X; } if (-v.Y > yMax) { yMax = -v.Y; } if (v.Z > zMax) { zMax = v.Z; } mesh.Vertices.Add(v); } mesh.Minimum = new Vector3(xMin, yMin, zMin); mesh.Maximum = new Vector3(xMax, yMax, zMax); short numNormals = reader.ReadInt16(); mesh.NumNormals = numNormals; if (numNormals > 0) { for (var i = 0; i < numNormals; i++) { var n = new wad_vertex(); n.X = reader.ReadInt16(); n.Y = reader.ReadInt16(); n.Z = reader.ReadInt16(); mesh.Normals.Add(n); } } else { for (var i = 0; i < -numNormals; i++) { mesh.Shades.Add(reader.ReadInt16()); } } ushort numPolygons = reader.ReadUInt16(); mesh.NumPolygons = numPolygons; ushort numQuads = 0; for (var i = 0; i < numPolygons; i++) { var poly = new wad_polygon(); poly.Shape = reader.ReadUInt16(); poly.V1 = reader.ReadUInt16(); poly.V2 = reader.ReadUInt16(); poly.V3 = reader.ReadUInt16(); if (poly.Shape == 9) { poly.V4 = reader.ReadUInt16(); } poly.Texture = reader.ReadUInt16(); poly.Attributes = reader.ReadByte(); poly.Unknown = reader.ReadByte(); if (poly.Shape == 9) { numQuads++; } mesh.Polygons.Add(poly); } if (numQuads % 2 != 0) { reader.ReadInt16(); } var endPosition = (uint)reader.BaseStream.Position; bytesRead += endPosition - startOfMesh; Meshes.Add(mesh); // Update the real pointers for (int k = 0; k < Pointers.Count; k++) { if (Pointers[k] == bytesRead) { RealPointers[k] = (uint)Meshes.Count; } } } var numAnimations = reader.ReadUInt32(); logger.Info("Wad animations: " + numAnimations); for (var i = 0; i < numAnimations; i++) { var anim = new wad_animation(); anim.KeyFrameOffset = reader.ReadUInt32(); anim.FrameDuration = reader.ReadByte(); anim.KeyFrameSize = reader.ReadByte(); anim.StateId = reader.ReadUInt16(); anim.Speed = reader.ReadInt32(); anim.Accel = reader.ReadInt32(); anim.SpeedLateral = reader.ReadInt32(); anim.AccelLateral = reader.ReadInt32(); anim.FrameStart = reader.ReadUInt16(); anim.FrameEnd = reader.ReadUInt16(); anim.NextAnimation = reader.ReadUInt16(); anim.NextFrame = reader.ReadUInt16(); anim.NumStateChanges = reader.ReadUInt16(); anim.ChangesIndex = reader.ReadUInt16(); anim.NumCommands = reader.ReadUInt16(); anim.CommandOffset = reader.ReadUInt16(); Animations.Add(anim); } var numChanges = reader.ReadUInt32(); logger.Info("Wad animation state changes: " + numChanges); for (var i = 0; i < numChanges; i++) { var change = new wad_state_change(); change.StateId = reader.ReadUInt16(); change.NumDispatches = reader.ReadUInt16(); change.DispatchesIndex = reader.ReadUInt16(); Changes.Add(change); } var numDispatches = reader.ReadUInt32(); logger.Info("Wad animation dispatches: " + numDispatches); for (var i = 0; i < numDispatches; i++) { var anim = new wad_anim_dispatch(); anim.Low = reader.ReadInt16(); anim.High = reader.ReadInt16(); anim.NextAnimation = reader.ReadInt16(); anim.NextFrame = reader.ReadInt16(); Dispatches.Add(anim); } var numCommands = reader.ReadUInt32(); logger.Info("Wad animation commands: " + numCommands); for (var i = 0; i < numCommands; i++) { Commands.Add(reader.ReadInt16()); } var numLinks = reader.ReadUInt32(); logger.Info("Wad animation links: " + numLinks); for (var i = 0; i < numLinks; i++) { Links.Add(reader.ReadInt32()); } var numFrames = reader.ReadUInt32(); logger.Info("Wad animation frames: " + numFrames); for (int i = 0; i < numFrames; i++) { KeyFrames.Add(reader.ReadInt16()); } var numMoveables = reader.ReadUInt32(); logger.Info("Wad objects (moveables): " + numMoveables); for (var i = 0; i < numMoveables; i++) { var moveable = new wad_moveable(); moveable.ObjectID = reader.ReadUInt32(); moveable.NumPointers = reader.ReadUInt16(); moveable.PointerIndex = reader.ReadUInt16(); moveable.LinksIndex = reader.ReadUInt32(); moveable.KeyFrameOffset = reader.ReadUInt32(); moveable.AnimationIndex = reader.ReadInt16(); Moveables.Add(moveable); } var numStaticMeshes = reader.ReadUInt32(); logger.Info("Wad static meshes: " + numStaticMeshes); for (var i = 0; i < numStaticMeshes; i++) { var staticMesh = new wad_static_mesh(); staticMesh.ObjectId = reader.ReadUInt32(); staticMesh.PointersIndex = reader.ReadUInt16(); staticMesh.VisibilityX1 = reader.ReadInt16(); staticMesh.VisibilityX2 = reader.ReadInt16(); staticMesh.VisibilityY1 = reader.ReadInt16(); staticMesh.VisibilityY2 = reader.ReadInt16(); staticMesh.VisibilityZ1 = reader.ReadInt16(); staticMesh.VisibilityZ2 = reader.ReadInt16(); staticMesh.CollisionX1 = reader.ReadInt16(); staticMesh.CollisionX2 = reader.ReadInt16(); staticMesh.CollisionY1 = reader.ReadInt16(); staticMesh.CollisionY2 = reader.ReadInt16(); staticMesh.CollisionZ1 = reader.ReadInt16(); staticMesh.CollisionZ2 = reader.ReadInt16(); staticMesh.Flags = reader.ReadUInt16(); Statics.Add(staticMesh); } reader.Close(); logger.Info("Wad loaded successfully."); } // Read sprites logger.Info("Reading sprites (swd file) associated with wad."); using (var readerSprites = new BinaryReaderEx(new FileStream(BasePath + Path.DirectorySeparatorChar + BaseName + ".swd", FileMode.Open, FileAccess.Read, FileShare.Read))) { // Version readerSprites.ReadUInt32(); var numSpritesTextures = readerSprites.ReadUInt32(); logger.Info("Sprites: " + numSpritesTextures); //Sprite texture array for (var i = 0; i < numSpritesTextures; i++) { var buffer = readerSprites.ReadBytes(16); var spriteTexture = new wad_sprite_texture { Tile = buffer[2], X = buffer[0], Y = buffer[1], Width = buffer[5], Height = buffer[7], LeftSide = buffer[0], TopSide = buffer[1], RightSide = (short)(buffer[0] + buffer[5] + 1), BottomSide = (short)(buffer[1] + buffer[7] + 1) }; SpriteTextures.Add(spriteTexture); } // Sprites size var spriteDataSize = readerSprites.ReadInt32(); SpriteData = readerSprites.ReadBytes(spriteDataSize); var numSequences = readerSprites.ReadUInt32(); logger.Info("Sprite sequences: " + numSequences); // Sprite sequences for (var i = 0; i < numSequences; i++) { var sequence = new wad_sprite_sequence(); sequence.ObjectID = readerSprites.ReadInt32(); sequence.NegativeLength = readerSprites.ReadInt16(); sequence.Offset = readerSprites.ReadInt16(); SpriteSequences.Add(sequence); } } // Read WAS using (var reader = new StreamReader(File.OpenRead(BasePath + Path.DirectorySeparatorChar + BaseName + ".was"))) { while (!reader.EndOfStream) { LegacyNames.Add(reader.ReadLine().Split(':')[0].Replace(" ", "").Replace("EXTRA0", "EXTRA")); } } }
internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad oldWad, int moveableIndex, Dictionary <int, WadTexture> textures) { wad_moveable oldMoveable = oldWad.Moveables[moveableIndex]; var newId = new WadMoveableId(oldMoveable.ObjectID); // A workaround to find out duplicated item IDs produced by StrPix unpatched for v130 wads. // In such case, a legacy name is used to guess real item ID, if this fails - broken item is filtered out. if (wad.Moveables.ContainsKey(newId)) { var message = "Duplicated moveable ID " + oldMoveable.ObjectID + " was identified while loading " + oldWad.BaseName + ". "; if (oldWad.LegacyNames.Count - oldWad.Statics.Count < moveableIndex) { logger.Warn(message + "Can't restore real ID by name, name table is too short. Ignoring moveable."); return(null); } bool isMoveable; var guessedId = TrCatalog.GetItemIndex(TRVersion.Game.TR4, oldWad.LegacyNames[moveableIndex], out isMoveable); if (isMoveable && guessedId.HasValue) { newId = new WadMoveableId(guessedId.Value); if (wad.Moveables.ContainsKey(newId)) { logger.Warn(message + "Can't restore real ID by name, name table is inconsistent. Ignoring moveable."); return(null); } else { logger.Warn(message + "Successfully restored real ID by name."); } } else { logger.Warn(message + "Can't find provided name in catalog. Ignoring moveable."); return(null); } } WadMoveable newMoveable = new WadMoveable(newId); var frameBases = new Dictionary <WadAnimation, ushort[]>(); // Load meshes var meshes = new List <WadMesh>(); for (int j = 0; j < oldMoveable.NumPointers; j++) { meshes.Add(ConvertTr4MeshToWadMesh(wad, oldWad, textures, oldWad.Meshes[(int)oldWad.RealPointers[oldMoveable.PointerIndex + j]], (int)oldMoveable.ObjectID)); } // Build the skeleton var root = new WadBone(); root.Name = "bone_0_root"; root.Parent = null; root.Translation = Vector3.Zero; root.Mesh = meshes[0]; newMoveable.Bones.Add(root); for (int j = 0; j < oldMoveable.NumPointers - 1; j++) { WadBone bone = new WadBone(); bone.Name = "bone_" + (j + 1).ToString(); bone.Parent = null; bone.Translation = Vector3.Zero; bone.Mesh = meshes[j + 1]; newMoveable.Bones.Add(bone); } for (int mi = 0; mi < (oldMoveable.NumPointers - 1); mi++) { int j = mi + 1; var opcode = (WadLinkOpcode)oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4)]; int linkX = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 1]; int linkY = -oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 2]; int linkZ = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 3]; newMoveable.Bones[j].OpCode = opcode; newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ); } // Convert animations int numAnimations = 0; int nextMoveable = oldWad.GetNextMoveableWithAnimations(moveableIndex); if (nextMoveable == -1) { numAnimations = oldWad.Animations.Count - oldMoveable.AnimationIndex; } else { numAnimations = oldWad.Moveables[nextMoveable].AnimationIndex - oldMoveable.AnimationIndex; } for (int j = 0; j < numAnimations; j++) { if (oldMoveable.AnimationIndex == -1) { break; } wad_animation oldAnimation = oldWad.Animations[j + oldMoveable.AnimationIndex]; WadAnimation newAnimation = new WadAnimation(); newAnimation.StateId = oldAnimation.StateId; newAnimation.FrameRate = oldAnimation.FrameDuration; newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.AnimationIndex); newAnimation.NextFrame = oldAnimation.NextFrame; newAnimation.Name = TrCatalog.GetAnimationName(TRVersion.Game.TR4, oldMoveable.ObjectID, (uint)j); // Fix wadmerger/wad format bug with inverted frame start/end on single-frame anims ushort newFrameStart = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameStart : oldAnimation.FrameEnd; ushort newFrameEnd = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameEnd : newFrameStart; newAnimation.EndFrame = (ushort)(newFrameEnd - newFrameStart); for (int k = 0; k < oldAnimation.NumStateChanges; k++) { WadStateChange sc = new WadStateChange(); wad_state_change wadSc = oldWad.Changes[(int)oldAnimation.ChangesIndex + k]; sc.StateId = (ushort)wadSc.StateId; for (int n = 0; n < wadSc.NumDispatches; n++) { WadAnimDispatch ad = new WadAnimDispatch(); wad_anim_dispatch wadAd = oldWad.Dispatches[(int)wadSc.DispatchesIndex + n]; ad.InFrame = (ushort)(wadAd.Low - newFrameStart); ad.OutFrame = (ushort)(wadAd.High - newFrameStart); ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.AnimationIndex) % numAnimations); ad.NextFrame = (ushort)wadAd.NextFrame; sc.Dispatches.Add(ad); } newAnimation.StateChanges.Add(sc); } if (oldAnimation.NumCommands < oldWad.Commands.Count) { int lastCommand = oldAnimation.CommandOffset; for (int k = 0; k < oldAnimation.NumCommands; k++) { short commandType = oldWad.Commands[lastCommand]; WadAnimCommand command = new WadAnimCommand(); command.Type = (WadAnimCommandType)commandType; switch (command.Type) { case WadAnimCommandType.SetPosition: command.Parameter1 = (short)oldWad.Commands[lastCommand + 1]; command.Parameter2 = (short)oldWad.Commands[lastCommand + 2]; command.Parameter3 = (short)oldWad.Commands[lastCommand + 3]; lastCommand += 4; break; case WadAnimCommandType.SetJumpDistance: command.Parameter1 = (short)oldWad.Commands[lastCommand + 1]; command.Parameter2 = (short)oldWad.Commands[lastCommand + 2]; lastCommand += 3; break; case WadAnimCommandType.EmptyHands: case WadAnimCommandType.KillEntity: lastCommand += 1; break; case WadAnimCommandType.PlaySound: case WadAnimCommandType.FlipEffect: command.Parameter1 = (short)(oldWad.Commands[lastCommand + 1] - newFrameStart); command.Parameter2 = (short)oldWad.Commands[lastCommand + 2]; // For single-frame anims, clamp frame number to first frame (another fix for WM/wad format range inversion bug) if (newAnimation.EndFrame == 0 && command.Parameter1 > 0) { command.Parameter1 = 0; } lastCommand += 3; break; default: // Ignore invalid anim commands (see for example karnak.wad) logger.Warn("Invalid anim command " + commandType); goto ExitForLoop; } newAnimation.AnimCommands.Add(command); } ExitForLoop: ; } int frames = (int)oldAnimation.KeyFrameOffset / 2; uint numFrames = 0; if (oldAnimation.KeyFrameSize != 0) { if ((j + oldMoveable.AnimationIndex) == (oldWad.Animations.Count - 1)) { numFrames = ((uint)(2 * oldWad.KeyFrames.Count) - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize); } else { numFrames = (oldWad.Animations[oldMoveable.AnimationIndex + j + 1].KeyFrameOffset - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize); } } for (int f = 0; f < numFrames; f++) { WadKeyFrame frame = new WadKeyFrame(); int startOfFrame = frames; frame.BoundingBox = new BoundingBox(new Vector3(oldWad.KeyFrames[frames], -oldWad.KeyFrames[frames + 2], oldWad.KeyFrames[frames + 4]), new Vector3(oldWad.KeyFrames[frames + 1], -oldWad.KeyFrames[frames + 3], oldWad.KeyFrames[frames + 5])); frames += 6; frame.Offset = new Vector3(oldWad.KeyFrames[frames], (short)(-oldWad.KeyFrames[frames + 1]), oldWad.KeyFrames[frames + 2]); frames += 3; for (int n = 0; n < oldMoveable.NumPointers; n++) { frame.Angles.Add(WadKeyFrameRotation.FromTrAngle(ref frames, oldWad.KeyFrames, false, true)); } if ((frames - startOfFrame) < oldAnimation.KeyFrameSize) { frames += ((int)oldAnimation.KeyFrameSize - (frames - startOfFrame)); } newAnimation.KeyFrames.Add(frame); } // New velocities float acceleration = oldAnimation.Accel / 65536.0f; newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f; float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f; newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f; if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0) { newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; } else { // Basic foolproofness for potentially broken animations newAnimation.EndVelocity = newAnimation.StartVelocity; newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity; } // Deduce real maximum frame number, based on interpolation and keyframes. // We need to refer this value in NextFrame-related fixes (below) because of epic WadMerger bug, // which incorrectly calculates NextFrame and "steals" last frame from every custom animation. ushort maxFrameCount = (ushort)((newAnimation.FrameRate == 1 || numFrames <= 2) ? numFrames : ((numFrames - 1) * newAnimation.FrameRate) + 1); // Also correct animation out-point if (newAnimation.EndFrame >= maxFrameCount) { newAnimation.EndFrame = (ushort)(maxFrameCount - 1); } frameBases.Add(newAnimation, new ushort[] { newFrameStart, (ushort)(maxFrameCount - 1) }); newMoveable.Animations.Add(newAnimation); } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; if (animation.KeyFrames.Count == 0) { animation.EndFrame = 0; } // HACK: this fixes some invalid NextAnimations values animation.NextAnimation %= (ushort)newMoveable.Animations.Count; newMoveable.Animations[i] = animation; } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; // HACK: this fixes some invalid NextFrame values if (frameBases[newMoveable.Animations[animation.NextAnimation]][0] != 0) { animation.NextFrame -= frameBases[newMoveable.Animations[animation.NextAnimation]][0]; if (animation.NextFrame > frameBases[newMoveable.Animations[animation.NextAnimation]][1]) { animation.NextFrame = frameBases[newMoveable.Animations[animation.NextAnimation]][1]; } } foreach (var stateChange in animation.StateChanges) { for (int j = 0; j < stateChange.Dispatches.Count; ++j) { WadAnimDispatch animDispatch = stateChange.Dispatches[j]; // HACK: Probably WadMerger's bug if (animDispatch.NextAnimation > 32767) { animDispatch.NextAnimation = 0; animDispatch.NextFrame = 0; continue; } if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0] != 0) { // HACK: In some cases dispatches have invalid NextFrame. // From tests it seems that's ok to make NextFrame equal to max frame number. animDispatch.NextFrame -= frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0]; if (animDispatch.NextFrame > frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]) { animDispatch.NextFrame = frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]; } } stateChange.Dispatches[j] = animDispatch; } } } wad.Moveables.Add(newMoveable.Id, newMoveable); return(newMoveable); }