/// <summary> /// Injects a dds stream to Meta Data. /// </summary> /// <param name="m">Bitmap Meta</param> /// <param name="pm">ParsedBitmap Data</param> /// <param name="br">Binary Reader Stream</param> /// <param name="index">The index # of the ParsedBitmap Data</param> /// <param name="bi">BitmapInfo Data</param> /// <remarks></remarks> public static void InjectDDS( Meta m, ParsedBitmap pm, ref BinaryReader br, int index, ParsedBitmap.BitmapInfo bi) { DDS_HEADER_STRUCTURE dds = new DDS_HEADER_STRUCTURE(); ParsedBitmap.BitmapInfo pmProp = pm.Properties[index]; // ' Read dds header dds.ReadStruct(ref br); // Check padded values as we can allow bitmaps of different size if they have the same padding int pmPropPaddedWidth = pmProp.width + (pmProp.width % 16 == 0 ? 0 : (16 - pmProp.width % 16)); int ddsPaddedWidth = dds.ddsd.width + (dds.ddsd.width % 16 == 0 ? 0 : (16 - dds.ddsd.width % 16)); if (ddsPaddedWidth != pmPropPaddedWidth || pmProp.height != dds.ddsd.height) { MessageBox.Show("ERROR (Bitmap #" + index + "): Images have different padded dimensions!"); return; } // Set new image width pmProp.width = (ushort)dds.ddsd.width; #region Discover if we are doing a bitmap, 3D or cubemap ParsedBitmap.BitmapType type = ParsedBitmap.BitmapType.BITM_TYPE_2D; if ((dds.ddsd.ddsCaps.caps2 & (int)DDSEnum.DDSCAPS2_VOLUME) > 0) type = ParsedBitmap.BitmapType.BITM_TYPE_3D; else if ((dds.ddsd.ddsCaps.caps2 & (int)DDSEnum.DDSCAPS2_CUBEMAP) > 0) type = ParsedBitmap.BitmapType.BITM_TYPE_CUBEMAP; #endregion // If they are different types if (pmProp.typename != type) { MessageBox.Show("ERROR: Images are different types!" + "\nInjected image = " + type + "\nInternal image = " + pmProp.typename); return; } if (bi.formatname.ToString().ToUpper().Contains("DXT")) bi.bitsPerPixel = 32; // Some programs don't save these values, so to be sure int rawsize = (int)br.BaseStream.Length - 128; br.BaseStream.Position = 128; byte[] bChunk = br.ReadBytes(rawsize); int cubemap = ((dds.ddsd.ddsCaps.caps2 & (int)DDSEnum.DDSCAPS2_CUBEMAP) > 0 ? 6 : 1); byte[][][] mipStreams = new byte[cubemap][][]; for (int i = 0; i < mipStreams.Length; i++) mipStreams[i] = new byte[Math.Max(dds.ddsd.MipMapCount, 1)][]; // 6 loops for cubemaps, 1 for all else for (int c = 0; c < cubemap; c++) { // the starting offset of each cubemap side int mipOffset = c * (bChunk.Length / cubemap); int inOffset = mipOffset; // Loop for each mipmap within each cube face (if it is a cubemap) for (int i = 0; i < mipStreams[c].Length; i++) { #region calculate width, height & stream length of mipmap // each mipmap is 1/2 width & height int tWidth = ddsPaddedWidth / (1 << i); int tHeight = dds.ddsd.height / (1 << i); // Contains the size of the current MipMap int mipStreamLength; switch (bi.formatname) { case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT1: mipStreamLength = Math.Max(1, tWidth / 4) * Math.Max(1, tHeight / 4) * 8; break; case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT2AND3: case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT4AND5: mipStreamLength = Math.Max(1, tWidth / 4) * Math.Max(1, tHeight / 4) * 16; break; default: mipStreamLength = Math.Max(1, tWidth) * Math.Max(1, tHeight) * (dds.ddsd.ddfPixelFormat.RGBBitCount >> 3); break; } #endregion mipStreams[c][i] = new byte[mipStreamLength]; if (dds.ddsd.width == ddsPaddedWidth) Array.Copy(bChunk, mipOffset, mipStreams[c][i], 0, mipStreams[c][i].Length); else { int byteStep = (dds.ddsd.ddfPixelFormat.RGBBitCount >> 3); Array.Clear(mipStreams[c][i], 0, mipStreams[c][i].Length); // Copy each line, adding padding to the output for (int h = 0; h < (dds.ddsd.height); h++) { Array.Copy( bChunk, mipOffset + h * dds.ddsd.width * byteStep, mipStreams[c][i], h * ddsPaddedWidth * byteStep, dds.ddsd.width * byteStep); } } mipOffset += mipStreams[c][i].Length; } #region conversion routine // Temporary. Remove after conversion routine verified / corrected // Don't allow different BPP injections if ((pmProp.bitsPerPixel != bi.bitsPerPixel) || // Don't allow compressed types to be converted to ((pmProp.formatname != bi.formatname) && pmProp.formatname.ToString().Contains("DXT"))) { string tempInfo = "\nBPP: " + pmProp.bitsPerPixel.ToString() + ", " + bi.bitsPerPixel.ToString() + "\nDXT: " + pmProp.formatname.ToString() + ", " + bi.formatname.ToString() + " (DXT = " + pmProp.formatname.ToString().Contains("DXT") + ")"; MessageBox.Show("Incompatible format types\n Check Bits Per Pixel & Format type\n *NOTE* No conversions to any DXT (compressed) format supported!\n" + tempInfo); return; } // H2 pads to a 128 boundry (not cubemaps) if (cubemap <= 1 && mipOffset % 128 != 0) mipOffset += (128 - (mipOffset % 128)); /* if (pmProp.formatname != bi.formatname) { byte[] temp = new byte[mipStreamLength]; // Check if we are at the end of Data, such as less mipmaps injected than the original if (inOffset + mipStreamLength > bAll.Length) { return; } Array.Copy(bAll, inOffset, temp, 0, mipStreamLength); Bitmap ba = DDS_Convert.DecodeDDS(temp, bi); /***** This testes to make sure it was being decoded properly Form1 f = new Form1(); System.Windows.Forms.PictureBox p1 = new System.Windows.Forms.PictureBox(); p1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; p1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; p1.Location = new System.Drawing.Point(300, 10); p1.Size = new System.Drawing.Size(70, 70); p1.Image = ba; f.Controls.Add(p1); f.ShowDialog(); *******/ /*// We need to make sure that it doesn't swizzle it here as it does it later! bool tb = pmProp.swizzle; pmProp.swizzle = false; bChunk = DDS_Convert.EncodeDDS(ba, ref pmProp); pmProp.swizzle = tb; ba.Dispose(); bi.width = (ushort)(bi.width / 2); bi.height = (ushort)(bi.height / 2); tWidth = pmProp.width; tHeight = pmProp.height; pmProp.width = (ushort)origWidth; pmProp.height = (ushort)origHeight; } */ #endregion #region Add padding, byte changes, etc for (int mi = 0; mi < mipStreams[c].Length; mi++) { int mipWidth = pmProp.width >> mi; // We don't need to pad the saved data as we pad any data read in /* if (pmProp.width % 16 != 0) //if (mipWidth % 16 != 0) { int paddedWidth = 16 - (mipWidth % 16); int byteStep = pmProp.bitsPerPixel / 8; byte[] paddedChunk = new byte[(mipWidth + paddedWidth) * pmProp.height * byteStep]; Array.Clear(paddedChunk, 0, paddedChunk.Length); // Make sure all bytes are zero to start! // Copy each line, adding padding to the output for (int h = 0; h < (pmProp.height >> mi); h++) { Array.Copy( mipStreams[c][mi], h * mipWidth * byteStep, paddedChunk, h * (mipWidth + paddedWidth) * byteStep, mipWidth * byteStep); } mipStreams[c][mi] = paddedChunk; } */ // G8B8 is based on a 128 value for 0 and adds -/+, so adjust values to 128 if (pmProp.formatname == ParsedBitmap.BitmapFormat.BITM_FORMAT_G8B8) { for (int mii = 0; mii < mipStreams[c][mi].Length; mii++) { mipStreams[c][mi][mii] += 128; } } } #endregion int lodNumber = 0; // First one is the main pic, not mipmap for (int j = 0; j < m.raw.rawChunks.Count; j++) { // If the selected index is the desired index and the LOD # starts at the mipmap # if (((BitmapRawDataChunk)m.raw.rawChunks[j]).inchunk == index && ((BitmapRawDataChunk)m.raw.rawChunks[j]).num == lodNumber) { m.Map.OpenMap(m.raw.rawChunks[j].rawLocation, false); if (!m.Map.isOpen) { return; } // Recaculate the total length (may have changed from above with conversion code) int totalLength = 0; for (int temp = lodNumber; temp < mipStreams[c].Length; temp++) totalLength += mipStreams[c][temp].Length; // H2 pads to a 128 boundry for each Bpp (not on cubemaps) switch (pmProp.formatname) { case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT1: if (totalLength % (256) != 0) totalLength += 256 - (totalLength % (256)); break; case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT2AND3: case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT4AND5: if (totalLength % (128) != 0) totalLength += 128 - (totalLength % (128)); break; default: if (totalLength % (128 * pmProp.bitsPerPixel >> 3) != 0) totalLength += (128 * (pmProp.bitsPerPixel >> 3) - (totalLength % (128 * pmProp.bitsPerPixel >> 3))); break; } byte[] tempChunk = new byte[totalLength]; int outOffset = 0; for (int temp = lodNumber; temp < mipStreams[c].Length; temp++) { if (pmProp.swizzle) { Array.Copy( Swizzler.Swizzle( mipStreams[c][temp], dds.ddsd.width >> temp, dds.ddsd.height >> temp, -1, dds.ddsd.ddfPixelFormat.RGBBitCount, false), 0, tempChunk, outOffset, mipStreams[c][temp].Length); } else Array.Copy(mipStreams[c][temp], 0, tempChunk, outOffset, mipStreams[c][temp].Length); outOffset += mipStreams[c][temp].Length; } // Append onto existing data for cubemaps m.Map.BW.BaseStream.Position = m.raw.rawChunks[j].offset + totalLength * c; m.Map.BW.Write(tempChunk, 0, Math.Min(tempChunk.Length, m.raw.rawChunks[j].size - (totalLength * c))); if (c == cubemap - 1) { m.Map.OpenMap(MapTypes.Internal); if (((BitmapRawDataChunk)m.raw.rawChunks[j]).num == 0) { m.Map.BW.BaseStream.Position = m.offset + m.raw.rawChunks[j].pointerMetaOffset - 28; pm.Properties[index].Write(ref m.Map.BW); } m.Map.BW.BaseStream.Position = m.offset + m.raw.rawChunks[j].pointerMetaOffset + 24; m.Map.BW.Write(Math.Min(tempChunk.Length * cubemap, m.raw.rawChunks[j].size)); m.Map.CloseMap(); } lodNumber++; } } inOffset += mipOffset; } }
/// <summary> /// The get bitmap format. /// </summary> /// <param name="dds">The dds.</param> /// <returns></returns> /// <remarks></remarks> public static ParsedBitmap.BitmapFormat getBitmapFormat(DDS_HEADER_STRUCTURE dds) { if (dds.ddsd.ddfPixelFormat.FourCC.StartsWith("DXT")) { switch (dds.ddsd.ddfPixelFormat.FourCC) { case "DXT1": return ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT1; case "DXT2": case "DXT3": return ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT2AND3; case "DXT4": case "DXT5": default: return ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT4AND5; } } else if (dds.ddsd.ddfPixelFormat.RGBAlphaBitMask != 0) { if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0x8000) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x7C00) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x03E0) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x001F)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_A1R5G5B5; } else if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0xF000) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x0F00) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x00F0) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x000F)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_A4R4G4B4; } else if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0x00000000) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x00FF0000) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000FF00) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x000000FF)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_X8R8G8B8; } else if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0xFF000000) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x00FF0000) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000FF00) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x000000FF)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_A8R8G8B8; } else if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0xFF00) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x00FF) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x0000)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_G8B8; } else if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0xFF) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x0000) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x0000)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_A8; } else if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0x00FF) && (dds.ddsd.ddfPixelFormat.RBitMask == 0xFF00) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x0000)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_A8Y8; } /* else // Same as above, so we need to post process to figure out which one as A8Y8 is byte & G8B8 is sbyte if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0x00FF) && (dds.ddsd.ddfPixelFormat.RBitMask == 0xFF00) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x0000)) return Raw.ParsedBitmap.BitmapFormat.BITM_FORMAT_G8B8; */ else { //// return ParsedBitmap.BitmapFormat.BITM_FORMAT_A8; } } else { if ((dds.ddsd.ddfPixelFormat.RGBAlphaBitMask == 0x00000000) && (dds.ddsd.ddfPixelFormat.RBitMask == 0x00FF0000) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x0000FF00) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x000000FF)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_X8R8G8B8; } else if ((dds.ddsd.ddfPixelFormat.RBitMask == 0xF800) && (dds.ddsd.ddfPixelFormat.GBitMask == 0x07E0) && (dds.ddsd.ddfPixelFormat.BBitMask == 0x001F)) { return ParsedBitmap.BitmapFormat.BITM_FORMAT_R5G6B5; } else { //// return ParsedBitmap.BitmapFormat.BITM_FORMAT_A8; } } }
/// <summary> /// Extracts meta data to a dds stream. /// </summary> /// <param name="m">Bitmap Meta</param> /// <param name="pm">ParsedBitmap Data</param> /// <param name="bw">Binary Writer Stream</param> /// <param name="index">The index # of the ParsedBitmap Data</param> /// <param name="bi">BitmapInfo Data</param> /// <returns>BitmapInfo containing the format of the extracted DDS</returns> /// <remarks></remarks> public static ParsedBitmap.BitmapInfo ExtractDDS( Meta m, ParsedBitmap pm, ref BinaryWriter bw, int index, ParsedBitmap.BitmapInfo bi) { DDS_HEADER_STRUCTURE dds = new DDS_HEADER_STRUCTURE(); ParsedBitmap.BitmapInfo pmProp = pm.Properties[index]; dds.generate(ref pmProp); // Get our raw data from the Meta, LOD 0 of our selected index int tempNum = -1; for (int i = 0; i < m.raw.rawChunks.Count; i++) if (((BitmapRawDataChunk)m.raw.rawChunks[i]).inchunk == index) { tempNum = i; break; } if (tempNum == -1) return null; byte[] tempChunk = m.raw.rawChunks[tempNum].MS.ToArray(); //int bytesToWrite = 0; int inOffset = 0; // If the original size needs padding, then use it, but not otherwise bool useWidthPad = pmProp.width % 16 != 0 ? true : false; bw.BaseStream.SetLength(128); bw.BaseStream.Position = 0; for (int cubemap = 0; cubemap < (pmProp.typename == ParsedBitmap.BitmapType.BITM_TYPE_CUBEMAP ? 6 : 1); cubemap++) { inOffset = cubemap * (tempChunk.Length / 6); int outOffset = 0; int tWidth = pmProp.width; int tHeight = pmProp.height; int tDepth = pmProp.depth; int mipMapCount = 0; // Halo 2 only uses mipmaps down to 2x__, instead of 1x1 (could be 2x1, 2x2, 2x4, etc), // but is padded enough we should be able to get a 1x1 extracted from the padded data while (tWidth != 0 && tHeight != 0) { int mipSize; // Any compressed images must have each mimap at least 16 bytes (4x4 image) // This concerns mipmaps such as 2x4, 2x2, 1x1, etc switch (pmProp.formatname) { case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT1: mipSize = Math.Max(Math.Max(1, tWidth) * Math.Max(1, tHeight) * Math.Max(1, tDepth) * (pmProp.bitsPerPixel >> 3) / 8, 8); //mipSize = Math.Max(1, tWidth / 4) * Math.Max(1, tHeight / 4) * 8; if (dds.ddsd.PitchOrLinearSize == 0) dds.ddsd.PitchOrLinearSize = mipSize; //dds.ddsd.PitchOrLinearSize = Math.Max(1, ((tWidth + 3) / 4)) * 8; break; case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT2AND3: case ParsedBitmap.BitmapFormat.BITM_FORMAT_DXT4AND5: mipSize = Math.Max(Math.Max(1, tWidth) * Math.Max(1, tHeight) * Math.Max(1, tDepth), 16) * (pmProp.bitsPerPixel >> 3) / 4; //mipSize = Math.Max(1, tWidth / 4) * Math.Max(1, tHeight / 4) * 16; if (dds.ddsd.PitchOrLinearSize == 0) dds.ddsd.PitchOrLinearSize = mipSize; break; default: mipSize = Math.Max(Math.Max(1, tWidth) * Math.Max(1, tHeight) * Math.Max(1, tDepth), 8) * (pmProp.bitsPerPixel >> 3); break; } #region Decode swizzled images if (pmProp.swizzle) { try { byte[] tempSwizzle = new byte[mipSize]; Array.Copy(tempChunk, inOffset + outOffset, tempSwizzle, 0, tempSwizzle.Length); tempSwizzle = Swizzler.Swizzle(tempSwizzle, tWidth, tHeight, tDepth > 1 ? tDepth : -1, pmProp.bitsPerPixel, true); Array.Copy(tempSwizzle, 0, tempChunk, inOffset + outOffset, tempSwizzle.Length); } catch { // We cannot generate all mipmaps, so just continue with the amount we have break; } } #endregion // H2 Bitmap Data is padded to a minimum of: // 1 BPP/DXT2/3/4/5 = 128 bytes // 2 BPP/DXT1 = 256 bytes // 4 BPP = 512 bytes // We need to remove the extra padding when // extracting to a DDS. DDS pads DXT1 to 8 bytes minimum & DXT2/3/4/5 to 16 bytes minimum // bytesToWrite is the width * height size. tempChunk2 will contain a stripped version #region Remove padding int widthPad = 0; //if (pmProp.typename != ParsedBitmap.BitmapType.BITM_TYPE_3D && tWidth % 16 != 0) if (useWidthPad && tWidth % 16 != 0) widthPad = 16 - tWidth % 16; if (!pmProp.formatname.ToString().Contains("DXT") && widthPad != 0) { //byte[] tempChunk2 = new byte[tWidth * tHeight * tDepth * byteStep]; byte[] tempChunk2 = new byte[mipSize]; try { // tempPadSize = Padded scanline length int tempPadSize = (tWidth + widthPad) * (pmProp.bitsPerPixel >> 3); int paddedSize = tHeight * tempPadSize; // Copy each line without the padding for (int d = 0; d < tDepth; d++) for (int h = 0; h < tHeight; h++) { Array.Copy( tempChunk, inOffset + outOffset + (d * paddedSize) + (h * tempPadSize), tempChunk2, (d * mipSize) + (h * tWidth * (pmProp.bitsPerPixel >> 3)), tWidth * (pmProp.bitsPerPixel >> 3)); } // Make a copy of the bytes beyond our current mipmap paddedSize *= tDepth; byte[] endBytes = new byte[tempChunk.Length - paddedSize - (inOffset + outOffset)]; Array.Copy(tempChunk, inOffset + outOffset + paddedSize, endBytes, 0, endBytes.Length); Array.Resize( ref tempChunk, inOffset + outOffset + tempChunk2.Length + endBytes.Length ); Array.Copy(tempChunk2, 0, tempChunk, inOffset + outOffset, tempChunk2.Length); Array.Copy(endBytes, 0, tempChunk, inOffset + outOffset + tempChunk2.Length, endBytes.Length); } catch { // We cannot generate all mipmaps, so just continue with the amount we have break; } } #endregion outOffset += mipSize; if ((pmProp.mipMapCount == 0) || (outOffset > tempChunk.Length)) break; tWidth >>= 1; tHeight >>= 1; tDepth = Math.Max(tDepth >> 1, 1); mipMapCount++; } // For exporting, use mipmaps right down to 1x1, instead of Halo's 2x2 dds.ddsd.MipMapCount = mipMapCount; // G8B8 is based on a 128 value for 0 and adds -/+, so adjust values to 128 if (pmProp.formatname == ParsedBitmap.BitmapFormat.BITM_FORMAT_G8B8) { for (int ii = 0; ii < tempChunk.Length; ii++) { tempChunk[ii] += 128; } } byte[] bChunk = new byte[outOffset]; Array.Copy(tempChunk, inOffset, bChunk, 0, Math.Min(outOffset, tempChunk.Length)); if (cubemap == 0) { dds.WriteStruct(ref bw); bw.BaseStream.Position = 128; } bw.Write(bChunk); } return pmProp; }