private static void ReadSidesOrThrow(MapData map, MapComponents components) { ByteReader reader = ByteReader.From(ByteOrder.Little, components.Sidedefs.Value.Data); int count = reader.Length / BytesPerSide; for (int index = 0; index < count; index++) { MapSidedef sidedef = new MapSidedef(index) { Offset = new Vec2I(reader.Short(), reader.Short()), UpperTexture = reader.StringWithoutNulls(8), LowerTexture = reader.StringWithoutNulls(8), MiddleTexture = reader.StringWithoutNulls(8), SectorID = reader.UShort() }; if (!sidedef.SectorID.InRangeExclusive(0, map.Sectors.Count)) { throw new Exception($"Sidedef {index} has out of range sector: {sidedef.SectorID}"); } map.Sidedefs.Add(sidedef); } }
private static void ReadVerticesOrThrow(MapData map, MapComponents components) { ByteReader reader = ByteReader.From(ByteOrder.Little, components.Vertices.Value.Data); int count = reader.Length / BytesPerVertex; for (int index = 0; index < count; index++) { MapVertex vertex = new MapVertex(index, reader.Short(), reader.Short()); map.Vertices.Add(vertex); } }
private static void ReadLinesOrThrow(MapData map, MapComponents components) { ByteReader reader = ByteReader.From(ByteOrder.Little, components.Linedefs.Value.Data); int count = reader.Length / BytesPerLine; for (int index = 0; index < count; index++) { MapLinedef linedef = new MapLinedef(index) { StartVertex = reader.UShort(), EndVertex = reader.UShort() }; ushort flags = reader.UShort(); ushort type = reader.UShort(); ushort sectorTag = reader.UShort(); SetLineSpecial(linedef, flags, type, sectorTag); linedef.FrontSide = reader.UShort(); int backID = reader.UShort(); if (backID != NoSidedef) { linedef.BackSide = backID; } if (!linedef.StartVertex.InRangeExclusive(0, map.Vertices.Count)) { throw new Exception($"Line {index} has out of range start vertex: {linedef.StartVertex}"); } if (!linedef.EndVertex.InRangeExclusive(0, map.Vertices.Count)) { throw new Exception($"Line {index} has out of range start vertex: {linedef.EndVertex}"); } if (!linedef.FrontSide.InRangeExclusive(0, map.Sidedefs.Count)) { throw new Exception($"Line {index} has out of range front sidedef: {linedef.FrontSide}"); } if (linedef.BackSide != null && !linedef.FrontSide.InRangeExclusive(0, map.Sidedefs.Count)) { throw new Exception($"Line {index} has out of range back sidedef: {linedef.BackSide.Value}"); } map.Linedefs.Add(linedef); } }
/// <summary> /// Reads a TEXTURE1/2 entry. /// </summary> /// <param name="number">The index of the X in the textureX. Should be /// either 1 or 2.</param> /// <param name="data">The data to read.</param> /// <returns>All the processed texture definitions, or null if the /// entry is corrupt.</returns> public static Optional <TextureX> From(int number, byte[] data) { Debug.Assert(number == 1 || number == 2, "TEXTUREx should have only 1 or 2 as a number"); try { ByteReader reader = ByteReader.From(ByteOrder.Little, data); Dictionary <UpperString, TextureXImage> images = new Dictionary <UpperString, TextureXImage>(); int numTextures = reader.Int(); List <int> offsets = new List <int>(); for (int i = 0; i < numTextures; i++) { offsets.Add(reader.Int()); } foreach (int offset in offsets) { reader.Offset = offset; Optional <TextureXImage> imageOptional = TextureXImage.From(reader); if (!imageOptional) { return(Empty); } // Unfortunately we have to follow the specification as per // the ZDoom wiki: "...however if a texture is defined // more than once in the same TEXTUREx lump, the later // definitions are skipped". TextureXImage image = imageOptional.Value; if (!images.ContainsKey(image.Name)) { images[image.Name] = image; } } return(new TextureX(number, images.Values)); } catch { return(Empty); } }
private static void ReadThingsOrThrow(MapData map, MapComponents components) { ByteReader reader = ByteReader.From(ByteOrder.Little, components.Things.Value.Data); int count = reader.Length / BytesPerThing; for (int index = 0; index < count; index++) { MapThing thing = new MapThing(index); float x = new Fixed(reader.Short(), 0).Float(); float y = new Fixed(reader.Short(), 0).Float(); thing.Position = (x, y); thing.AngleDegrees = reader.UShort(); thing.EditorID = reader.UShort(); SetThingFlags(thing, reader.UShort()); map.Things.Add(thing); } }
/// <summary> /// Reads a pnames data from the entry provided. /// </summary> /// <param name="data">The data to read from.</param> /// <returns>A read pnames object from the entry, or an empty value if /// the entry has the wrong data amount or is corrupt.</returns> public static Optional <PNames> From(byte[] data) { try { ByteReader reader = ByteReader.From(ByteOrder.Little, data); int numStrings = reader.Int(); List <UpperString> pnames = new List <UpperString>(numStrings); for (int i = 0; i < numStrings; i++) { pnames.Add(reader.StringWithoutNulls(8)); } return(new PNames(pnames)); } catch { return(Empty); } }
private static void ReadSectorsOrThrow(MapData map, MapComponents components) { ByteReader reader = ByteReader.From(ByteOrder.Little, components.Sectors.Value.Data); int count = reader.Length / BytesPerSector; for (int index = 0; index < count; index++) { MapSector sector = new MapSector(index) { FloorHeight = reader.Short(), CeilingHeight = reader.Short(), FloorTexture = reader.StringWithoutNulls(8).ToUpper(), CeilingTexture = reader.StringWithoutNulls(8).ToUpper(), LightLevel = reader.Short() }; SetSpecialBits(sector, reader.UShort()); sector.Tag = reader.UShort(); map.Sectors.Add(sector); } }
private static List <WadEntry> ReadEntriesOrThrow(byte[] wadData) { ByteReader reader = ByteReader.From(ByteOrder.Little, wadData); // The header must exist fully or else we can't read it. if (!reader.HasRemaining(12)) { throw new Exception("Cannot read Wad"); } WadResourceNamespaceTracker namespaceTracker = new WadResourceNamespaceTracker(); List <WadEntry> entries = new List <WadEntry>(); WadHeader header = ReadHeader(reader); reader.Offset = header.DirectoryTableOffset; for (int i = 0; i < header.EntryCount; i++) { WadDirectoryEntry dirEntry = ReadWadEntry(reader); ResourceNamespace resourceNamespace = namespaceTracker.Update(dirEntry); byte[] data = reader.Bytes(dirEntry.Size, dirEntry.Offset); // Unfortunately binary readers can silently fail and just // consume the remaining data. We want to let the caller // know the wad is in fact corrupt if it can't read enough. if (data.Length != dirEntry.Size) { throw new Exception("Malformed wad entry length"); } WadEntry entry = new WadEntry(dirEntry.Name, resourceNamespace, data); entries.Add(entry); } return(entries); }
/// <summary> /// Creates a palette image from a column-based image. /// </summary> /// <param name="data">The data for the image.</param> /// <param name="resourceNamespace">The namespace this image belongs /// to.</param> /// <returns>The palette image for the data, or null if the image is /// corrupt and would not make a valid column image.</returns> public static Optional <PaletteImage> FromColumn(byte[] data, ResourceNamespace resourceNamespace) { // OPTIMIZE: Short circuit if it's likely not a column image. // OPTIMIZE: Write posts horizontally, then rotate for speed? try { ByteReader reader = ByteReader.From(ByteOrder.Little, data); int width = reader.Short(); int height = reader.Short(); Vec2I offset = (reader.Short(), reader.Short()); if (offset.X < 0 || offset.Y < 0) { return(Empty); } int[] offsets = new int[width]; for (int i = 0; i < width; i++) { offsets[i] = reader.Int(); } short[] indices = Arrays.Create(width * height, TransparentIndex); for (int col = 0; col < width; col++) { reader.Offset = offsets[col]; while (true) { int rowStart = reader.Byte(); if (rowStart == 0xFF) { break; } int indicesCount = reader.Byte(); reader.Skip(1); byte[] paletteIndices = reader.Bytes(indicesCount); reader.Skip(1); int indicesOffset = (rowStart * width) + col; for (int i = 0; i < paletteIndices.Length; i++) { indices[indicesOffset] = paletteIndices[i]; indicesOffset += width; } } } return(new PaletteImage(width, height, indices) { Namespace = resourceNamespace, Offset = offset }); } catch { return(Empty); } }