/// <summary> /// Loads a Dragon Quest VII image. /// </summary> /// <param name="data">The DMP image data</param> /// <returns>The image as a texture</returns> public static RenderBase.OTexture load(Stream data) { BinaryReader input = new BinaryReader(data); string dmpMagic = IOUtils.readString(input, 0, 3); if (dmpMagic != "DMP") { throw new Exception("DMP: Invalid or corrupted file!"); } string format = IOUtils.readString(input, 4, 4); int width = input.ReadUInt16(); int height = input.ReadUInt16(); int pow2Width = input.ReadUInt16(); int pow2Height = input.ReadUInt16(); byte[] buffer = new byte[data.Length - 0x10]; data.Read(buffer, 0, buffer.Length); data.Close(); Bitmap bmp = null; switch (format) { case "8888": bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.rgba8); break; } Bitmap newBmp = new Bitmap(width, height); Graphics gfx = Graphics.FromImage(newBmp); gfx.DrawImage(bmp, new Rectangle(0, 0, width, height), new Rectangle(0, 0, width, height), GraphicsUnit.Pixel); gfx.Dispose(); return(new RenderBase.OTexture(newBmp, "texture")); }
private void save() { using (FileStream data = new FileStream(currentFile, FileMode.Open)) { BinaryReader input = new BinaryReader(data); BinaryWriter output = new BinaryWriter(data); for (int i = 0; i < bch.mips[mipSel].textures.Count; i++) { loadedTexture tex = bch.mips[mipSel].textures[i]; if (tex.modified) { byte[] buffer = align(TextureCodec.encode(tex.texture.texture, tex.type)); int diff = buffer.Length - tex.length; replaceData(data, tex.offset, returnSize(tex.type, tex.texture.texture.Width, tex.texture.texture.Height), buffer); tex.modified = false; updateTexture(i, tex); } } } MessageBox.Show("Done!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); }
public static RenderBase.OTexture loadTexture(string fileName) { if (File.Exists(fileName)) { Serialization.SERI tex = Serialization.getSERI(fileName); int width = tex.getIntegerParameter("w"); int height = tex.getIntegerParameter("h"); int mipmap = tex.getIntegerParameter("mipmap"); int format = tex.getIntegerParameter("format"); string textureName = tex.getStringParameter("tex"); string fullTextureName = Path.Combine(Path.GetDirectoryName(fileName), textureName); if (File.Exists(fullTextureName)) { RenderBase.OTextureFormat fmt = RenderBase.OTextureFormat.dontCare; switch (format) { case 0: fmt = RenderBase.OTextureFormat.l4; break; case 1: fmt = RenderBase.OTextureFormat.l8; break; case 7: fmt = RenderBase.OTextureFormat.rgb565; break; case 8: fmt = RenderBase.OTextureFormat.rgba5551; break; case 9: fmt = RenderBase.OTextureFormat.rgba4; break; case 0xa: fmt = RenderBase.OTextureFormat.rgba8; break; case 0xb: fmt = RenderBase.OTextureFormat.rgb8; break; case 0xc: fmt = RenderBase.OTextureFormat.etc1; break; case 0xd: fmt = RenderBase.OTextureFormat.etc1a4; break; default: Debug.WriteLine("NLP Model: Unknown Texture format 0x" + format.ToString("X8")); break; } string name = Path.GetFileNameWithoutExtension(textureName); byte[] buffer = File.ReadAllBytes(fullTextureName); return(new RenderBase.OTexture(TextureCodec.decode(buffer, width, height, fmt), name)); } } return(null); }
// This REQUIRES a backing CGFX file and doesn't contain enough data to regenerate one from scratch public SimplifiedModel(CGFX cgfx) { this.cgfx = cgfx; // Models are always the first entry per CGFX standard var models = cgfx.Data.Entries[0]?.Entries; // Textures are the second var textures = cgfx.Data.Entries[1]?.Entries.Select(e => e.EntryObject).Cast <DICTObjTexture>().ToList(); if (textures != null && textures.Count > 0) { Textures = new SMTexture[textures.Count]; for (var t = 0; t < textures.Count; t++) { Bitmap textureBitmap = null; var name = textures[t].Name; var textureData = textures[t]; if (textureData != null) { var textureRGBA = TextureCodec.ConvertTextureToRGBA(new Utility(null, null, Endianness.Little), textureData.TextureCGFXData, textureData.TextureFormat, (int)textureData.Width, (int)textureData.Height); textureBitmap = new Bitmap((int)textureData.Width, (int)textureData.Height, PixelFormat.Format32bppArgb); var imgData = textureBitmap.LockBits(new Rectangle(0, 0, textureBitmap.Width, textureBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(textureRGBA, 0, imgData.Scan0, textureRGBA.Length); textureBitmap.UnlockBits(imgData); textureBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); } var smTexture = new SMTexture(name, textureBitmap); Textures[t] = smTexture; } } if (models != null && models.Count > 0) { // This probably isn't a difficult problem to work around, but it's out of my scope at this time if (models.Count != 1) { throw new InvalidOperationException("File contains more than one model; only supporting one for now."); } var model = (DICTObjModel)models.First().EntryObject; // NOTE: Currently NOT committing the skeleton back, we're just keeping it for model software var bones = model.Skeleton?.Bones.Entries.Select(e => e.EntryObject).Cast <DICTObjBone>().ToList() ?? new List <DICTObjBone>(); Bones = bones.Select(b => new SMBone { Name = b.Name, ParentName = b.Parent?.Name, Rotation = b.Rotation, Translation = b.Translation, Scale = b.Scale, LocalTransform = b.LocalTransform }).ToArray(); Meshes = new SMMesh[model.Meshes.Length]; for (var m = 0; m < model.Meshes.Length; m++) { var mesh = model.Meshes[m]; var shape = model.Shapes[mesh.ShapeIndex]; // There might be some clever way of handling multiple vertex buffers // (if it actually happens) but I'm not worried about it. Only looking // for a single one that is VertexBufferInterleaved. var vertexBuffersInterleaved = shape.VertexBuffers.Where(vb => vb is VertexBufferInterleaved); if (vertexBuffersInterleaved.Count() != 1) { throw new InvalidOperationException("Unsupported count of VertexBuffers in VertexBufferInterleaved format"); } // Only expecting / supporting 1 SubMesh entry if (shape.SubMeshes.Count != 1) { throw new InvalidOperationException("Unsupported amount of SubMeshes"); } var subMesh = shape.SubMeshes[0]; // The BoneReferences in the SubMesh are what the vertex's local index references. var boneReferences = subMesh.BoneReferences; // These aren't "faces" in the geometrical sense, but rather a header of sorts if (subMesh.Faces.Count != 1) { throw new InvalidOperationException("Unsupported amount of Faces"); } var faceHeader = subMesh.Faces[0]; // Again, just one FaceDescriptor... if (faceHeader.FaceDescriptors.Count != 1) { throw new InvalidOperationException("Unsupported amount of FaceDescriptors"); } var faceDescriptor = faceHeader.FaceDescriptors[0]; // We're also only supporting triangles at this point; the model format probably // allows for more groups of geometry, but again, out of my scope if (faceDescriptor.PrimitiveMode != FaceDescriptor.PICAPrimitiveMode.Triangles) { throw new InvalidOperationException("Only supporting triangles format"); } // Vertices are stored (in GPU-compatible form) var vertexBuffer = (VertexBufferInterleaved)vertexBuffersInterleaved.Single(); var vertexBufferIndex = shape.VertexBuffers.IndexOf(vertexBuffer); var attributes = vertexBuffer.Attributes.Select(a => VertexBufferCodec.PICAAttribute.GetPICAAttribute(a)).ToList(); // The following are the only VertexAttributes we are supporting at this time var supportedAttributes = new List <VertexBuffer.PICAAttributeName> { VertexBuffer.PICAAttributeName.Position, VertexBuffer.PICAAttributeName.Normal, VertexBuffer.PICAAttributeName.TexCoord0, VertexBuffer.PICAAttributeName.BoneIndex, VertexBuffer.PICAAttributeName.BoneWeight, // Caution: Vertex color may not be supported by all model editors! VertexBuffer.PICAAttributeName.Color }; // Check if any unsupported attributes are in use var unsupportedAttributes = attributes.Where(a => !supportedAttributes.Contains(a.Name)).Select(a => a.Name); if (unsupportedAttributes.Any()) { throw new InvalidOperationException($"This model is using the following unsupported attributes: {string.Join(", ", unsupportedAttributes)}"); } var nativeVertices = VertexBufferCodec.GetVertices(shape, vertexBufferIndex); // Convert to the simplified vertices var boneIndexCount = GetElementsOfAttribute(attributes, VertexBuffer.PICAAttributeName.BoneIndex); // How many bone indices are actually used var boneWeightCount = GetElementsOfAttribute(attributes, VertexBuffer.PICAAttributeName.BoneWeight); // How many bone weights are actually used // FIXME? There seems to be, on occasion, a bone relationship that points to // the entire mesh but not assigned to any of the vertices. So basically the // vertices are recorded as having no bone indices but in fact are all dependent // upon associating with bone index 0 (?) This will force it to use at least // one bone index even if zero is specified for this case. if (boneReferences != null && boneReferences.Count > 0) { boneIndexCount = Math.Max(boneIndexCount, 1); } var vertices = nativeVertices.Select(v => new SMVertex { Position = new Vector3(v.Position.X, v.Position.Y, v.Position.Z), Normal = new Vector3(v.Normal.X, v.Normal.Y, v.Normal.Z), TexCoord = new Vector3(v.TexCoord0.X, v.TexCoord0.Y, v.TexCoord0.Z), BoneIndices = (new[] { v.Indices.b0, v.Indices.b1, v.Indices.b2, v.Indices.b3 }).Take(boneIndexCount).ToArray(), Weights = (new[] { v.Weights.w0, v.Weights.w1, v.Weights.w2, v.Weights.w3 }).Take(boneWeightCount).ToArray(), Color = v.Color // Caution! Not all 3D model editors may support vertex color! }).ToList(); // The vertices use relative bone indices based on the SubMesh definitions, // which we're going to make absolute now for (var v = 0; v < vertices.Count; v++) { var vertex = vertices[v]; for (var i = 0; i < vertex.BoneIndices.Length; i++) { vertex.BoneIndices[i] = boneReferences[vertex.BoneIndices[i]].Index; // Also, if no bone weights are available, assign a weight of 1 to the first bone. // This won't be stored ultimately as the PICA attributes won't specify it. if (vertex.Weights.Length == 0) { vertex.Weights = new float[] { 1.0f }; } } } // Deconstruct into triangle faces var triangles = new List <SMTriangle>(); var indices = faceDescriptor.Indices; for (var i = 0; i < indices.Count; i += 3) { triangles.Add(new SMTriangle { v1 = indices[i + 0], v2 = indices[i + 1], v3 = indices[i + 2] }); } // Finally, assign material, if available (mostly for the model editor's benefit) var material = model.ModelMaterials.Entries.Select(e => e.EntryObject).Cast <DICTObjModelMaterial>().ToList()[mesh.MaterialId]; var texture = material.TextureMappers.First().TextureReference; var name = texture.ReferenceName; var smTexture = (Textures != null) ? Textures.Where(t => t.Name == name).SingleOrDefault() : null; if (smTexture == null) { smTexture = new SMTexture(name, null); } Meshes[m] = new SMMesh(vertices, triangles, shape, vertexBufferIndex, smTexture); } } }
// Imports the RGBA texture data converting it to CGFX and storing it. // NOTE: "utility" is just used for endianness, it's not reading/writing a CGFX file public void SetTexture(Utility utility, byte[] data, bool safetyCheck = true, uint?width = null, uint?height = null, Format?format = null) { // TODO: I wonder how feasible it is to use a different size or format... // (e.g., in particular, "upgrading" a grayscale texture to full color ...) // Unknown whether that might cause a game to crash if it's unexpected or too large. if (safetyCheck && ( (width.HasValue && width.Value != Width) || (height.HasValue && height.Value != Height) || (format.HasValue && format != TextureFormat) ) ) { throw new InvalidOperationException("ChunkDICTTexture SetTexture: The texture you are trying to import does not match the original width/height/format; if you're SURE about this, set safetyCheck = false"); } // NOTE!! Actually changing the texture format is untested and may cause problems / crashes... if (width.HasValue) { // WARNING: This is a GUESS if (Width == Unknown3) { Unknown3 = Width; } else { throw new NotImplementedException($"ChunkDICTTexture SetTexture: Assumption that 'Unknown3' relates to Width is WRONG (Width={Width}, Unknown3={Unknown3})"); } Width = width.Value; } if (height.HasValue) { // WARNING: This is a GUESS if (Height == Unknown2) { Unknown2 = Height; } else { throw new NotImplementedException($"ChunkDICTTexture SetTexture: Assumption that 'Unknown2' relates to Height is WRONG (Height={Height}, Unknown2={Unknown2})"); } Height = height.Value; } if (format.HasValue) { TextureFormat = format.Value; } // TODO -- BPP??!! (and Unknown1?) // TODO -- changing format doesn't update BitsPerPixel yet, not sure how critical, will need generic way to get it // ... really need to figure out how it correlates to the texture format ... // SEE ALSO: Unknown1, which appears to correlate to BPP "somehow" (but not necessarily identical) var newCGFXData = TextureCodec.ConvertTextureToCGFX(utility, data, TextureFormat, (int)Width, (int)Height); if (safetyCheck && newCGFXData.Length != TextureCGFXData.Length) { throw new InvalidOperationException("ChunkDICTTexture SetTexture: The texture data came back in a different raw data size than the original!"); } TextureCGFXData = newCGFXData; }
// Converts the CGFX texture data to RGBA for exporting // NOTE: "utility" is just used for endianness, it's not reading/writing a CGFX file public byte[] GetTextureRGBA(Utility utility) { return(TextureCodec.ConvertTextureToRGBA(utility, TextureCGFXData, TextureFormat, (int)Width, (int)Height)); }
/// <summary> /// Loads a Fantasy Life ZTEX texture from a Stream. /// </summary> /// <param name="data">The Stream with the data</param> /// <returns>The list of textures</returns> public static List <RenderBase.OTexture> load(Stream data) { List <RenderBase.OTexture> textures = new List <RenderBase.OTexture>(); BinaryReader input = new BinaryReader(data); string ztexMagic = IOUtils.readString(input, 0, 4); ushort textureCount = input.ReadUInt16(); input.ReadUInt16(); input.ReadUInt32(); List <textureEntry> entries = new List <textureEntry>(); for (int i = 0; i < textureCount; i++) { textureEntry entry = new textureEntry(); entry.name = IOUtils.readString(input, (uint)(0xc + (i * 0x58))); data.Seek(0xc + (i * 0x58) + 0x40, SeekOrigin.Begin); input.ReadUInt32(); entry.offset = input.ReadUInt32(); input.ReadUInt32(); entry.length = input.ReadUInt32(); entry.width = input.ReadUInt16(); entry.height = input.ReadUInt16(); input.ReadByte(); entry.format = input.ReadByte(); input.ReadUInt16(); entries.Add(entry); } foreach (textureEntry entry in entries) { data.Seek(entry.offset, SeekOrigin.Begin); byte[] buffer = new byte[entry.length]; data.Read(buffer, 0, buffer.Length); Bitmap bmp = null; switch (entry.format) { case 1: bmp = TextureCodec.decode(buffer, entry.width, entry.height, RenderBase.OTextureFormat.rgb565); break; case 5: bmp = TextureCodec.decode(buffer, entry.width, entry.height, RenderBase.OTextureFormat.rgba4); break; case 9: bmp = TextureCodec.decode(buffer, entry.width, entry.height, RenderBase.OTextureFormat.rgba8); break; case 0x18: bmp = TextureCodec.decode(buffer, entry.width, entry.height, RenderBase.OTextureFormat.etc1); break; case 0x19: bmp = TextureCodec.decode(buffer, entry.width, entry.height, RenderBase.OTextureFormat.etc1a4); break; } textures.Add(new RenderBase.OTexture(bmp, entry.name)); } data.Close(); return(textures); }
private loadedTexture loadPKM(FileStream data, BinaryReader input) { loadedTexture tex; tex.modified = false; long descAddress2 = data.Position; data.Seek(descAddress2 + 0x18, SeekOrigin.Begin); int texLength = input.ReadInt32(); data.Seek(descAddress2 + 0x28, SeekOrigin.Begin); string textureName = IOUtils.readStringWithLength(input, 0x40); data.Seek(descAddress2 + 0x68, SeekOrigin.Begin); ushort width = input.ReadUInt16(); ushort height = input.ReadUInt16(); ushort texFormat = input.ReadUInt16(); ushort texMipMaps = input.ReadUInt16(); data.Seek(0x10, SeekOrigin.Current); tex.offset = (uint)data.Position; byte[] texBuffer = input.ReadBytes(texLength); RenderBase.OTextureFormat fmt = RenderBase.OTextureFormat.dontCare; switch (texFormat) { case 0x2: fmt = RenderBase.OTextureFormat.rgb565; break; case 0x3: fmt = RenderBase.OTextureFormat.rgb8; break; case 0x4: fmt = RenderBase.OTextureFormat.rgba8; break; case 0x17: fmt = RenderBase.OTextureFormat.rgba5551; break; case 0x23: fmt = RenderBase.OTextureFormat.la8; break; case 0x24: fmt = RenderBase.OTextureFormat.hilo8; break; case 0x25: fmt = RenderBase.OTextureFormat.l8; break; case 0x26: fmt = RenderBase.OTextureFormat.a8; break; case 0x27: fmt = RenderBase.OTextureFormat.la4; break; case 0x28: fmt = RenderBase.OTextureFormat.l4; break; case 0x29: fmt = RenderBase.OTextureFormat.a4; break; case 0x2a: fmt = RenderBase.OTextureFormat.etc1; break; case 0x2b: fmt = RenderBase.OTextureFormat.etc1a4; break; } Bitmap texture = TextureCodec.decode(texBuffer, width, height, fmt); tex.texture = new RenderBase.OTexture(texture, textureName); tex.type = fmt; tex.gpuCommandsOffset = 0; tex.gpuCommandsWordCount = 0; tex.length = texLength; return(tex); }
private bool open(string fileName) { using (FileStream data = new FileStream(fileName, FileMode.Open)) { BinaryReader input = new BinaryReader(data); if (peek(input) == 0x00010000) { currentFile = fileName; bch = new loadedBCH(); bch.isBCH = false; bch.mips.Add(new MIPlayer()); packPNK(data, input, bch); } if (peek(input) == 0x15041213) { currentFile = fileName; bch = new loadedBCH(); bch.isBCH = false; bch.mips.Add(new MIPlayer()); bch.mips[0].textures.Add(loadPKM(data, input)); } string magic2b = getMagic(input, 2); if (magic2b == "PC" || magic2b == "CM") { bch = new loadedBCH(); bch.isBCH = false; currentFile = fileName; data.Seek(2, SeekOrigin.Current); ushort numEntrys = input.ReadUInt16(); for (int i = 0; i < (int)numEntrys; i++) { data.Seek(4 + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); uint end = input.ReadUInt32(); uint lenth = end - offset; long rtn = data.Position; data.Seek(offset, SeekOrigin.Begin); if (magic2b == "CM" & i == 0) { packPNK(data, input, bch); } if (lenth > 4) { if (magic2b == "PC") { if (peek(input) == 0x15041213) { bch.mips[0].textures.Add(loadPKM(data, input)); } } } } } string magic = IOUtils.readString(input, 0); if (magic == "BCH") { currentFile = fileName; data.Seek(4, SeekOrigin.Current); byte backwardCompatibility = input.ReadByte(); byte forwardCompatibility = input.ReadByte(); ushort version = input.ReadUInt16(); uint mainHeaderOffset = input.ReadUInt32(); uint stringTableOffset = input.ReadUInt32(); uint gpuCommandsOffset = input.ReadUInt32(); uint dataOffset = input.ReadUInt32(); uint dataExtendedOffset = backwardCompatibility > 0x20 ? input.ReadUInt32() : 0; uint relocationTableOffset = input.ReadUInt32(); uint mainHeaderLength = input.ReadUInt32(); uint stringTableLength = input.ReadUInt32(); uint gpuCommandsLength = input.ReadUInt32(); uint dataLength = input.ReadUInt32(); uint dataExtendedLength = backwardCompatibility > 0x20 ? input.ReadUInt32() : 0; uint relocationTableLength = input.ReadUInt32(); data.Seek(mainHeaderOffset, SeekOrigin.Begin); uint modelsPointerTableOffset = input.ReadUInt32() + mainHeaderOffset; uint modelsPointerTableEntries = input.ReadUInt32(); data.Seek(mainHeaderOffset + 0x24, SeekOrigin.Begin); uint texturesPointerTableOffset = input.ReadUInt32() + mainHeaderOffset; uint texturesPointerTableEntries = input.ReadUInt32(); bch = new loadedBCH(); bch.isBCH = true; for (int i = 0; i < 6; i++) { bch.mips.Add(new MIPlayer()); } MipSelect.Enabled = true; //Textures for (int index = 0; index < texturesPointerTableEntries; index++) { data.Seek(texturesPointerTableOffset + (index * 4), SeekOrigin.Begin); data.Seek(input.ReadUInt32() + mainHeaderOffset, SeekOrigin.Begin); loadedTexture tex; tex.modified = false; tex.gpuCommandsOffset = input.ReadUInt32() + gpuCommandsOffset; tex.gpuCommandsWordCount = input.ReadUInt32(); data.Seek(0x14, SeekOrigin.Current); uint textureNameOffset = input.ReadUInt32(); string textureName = IOUtils.readString(input, textureNameOffset + stringTableOffset); data.Seek(tex.gpuCommandsOffset, SeekOrigin.Begin); PICACommandReader textureCommands = new PICACommandReader(data, tex.gpuCommandsWordCount); tex.offset = textureCommands.getTexUnit0Address() + dataOffset; RenderBase.OTextureFormat fmt = textureCommands.getTexUnit0Format(); Size textureSize = textureCommands.getTexUnit0Size(); tex.type = fmt; int OGW = textureSize.Width; int OGH = textureSize.Height; for (int i = 0; i < 6; i++) { textureSize.Width = OGW / Convert.ToInt32(Math.Pow(2, i)); textureSize.Height = OGH / Convert.ToInt32(Math.Pow(2, i)); tex.length = returnSize(fmt, textureSize.Width, textureSize.Height); if (textureSize.Height >= 8 & textureSize.Width >= 8) { data.Seek(tex.offset, SeekOrigin.Begin); byte[] buffer = new byte[tex.length]; //data.Seek(tex.length + returnSize(fmt, textureSize.Width / 2, textureSize.Height / 2), SeekOrigin.Current); tex.offset = (uint)data.Position; input.Read(buffer, 0, tex.length); Bitmap texture = TextureCodec.decode( buffer, textureSize.Width, textureSize.Height, fmt); tex.texture = new RenderBase.OTexture(texture, textureName); bch.mips[i].textures.Add(tex); tex.offset = (uint)data.Position; } } } bch.mainHeaderOffset = mainHeaderOffset; bch.gpuCommandsOffset = gpuCommandsOffset; bch.dataOffset = dataOffset; bch.relocationTableOffset = relocationTableOffset; bch.relocationTableLength = relocationTableLength; } else if (magic == "CTPK\u0001") { currentFile = fileName; data.Seek(4, SeekOrigin.Current); ushort ver = input.ReadUInt16(); ushort numTexture = input.ReadUInt16(); uint TextureSectionOffset = input.ReadUInt32(); uint TextureSectionSize = input.ReadUInt32(); uint HashSectionOffset = input.ReadUInt32(); uint TextureInfoSection = input.ReadUInt32(); bch = new loadedBCH(); bch.isBCH = false; bch.mips.Add(new MIPlayer()); for (int i = 0; i < numTexture; i++) { data.Seek(0x20 * (i + 1), SeekOrigin.Begin); loadedTexture tex; tex.modified = false; tex.gpuCommandsOffset = (uint)(0x20 * (i + 1)); uint textureNameOffset = input.ReadUInt32(); string textureName = IOUtils.readString(input, textureNameOffset); tex.length = input.ReadInt32(); tex.offset = input.ReadUInt32() + TextureSectionOffset; tex.type = (RenderBase.OTextureFormat)input.ReadUInt32(); ushort Width = input.ReadUInt16(); ushort Height = input.ReadUInt16(); data.Seek(tex.offset, SeekOrigin.Begin); byte[] buffer = new byte[tex.length]; input.Read(buffer, 0, buffer.Length); Bitmap texture = TextureCodec.decode( buffer, Width, Height, tex.type); tex.texture = new RenderBase.OTexture(texture, textureName); tex.gpuCommandsWordCount = 0; bch.mips[0].textures.Add(tex); } } } updateTexturesList(); return(true); }
/// <summary> /// Loads a Binary Citra Layout Image from a Stream. /// </summary> /// <param name="data">The Stream with the data</param> /// <returns>The image as a texture</returns> public static RenderBase.OTexture load(Stream data) { BinaryReader input = new BinaryReader(data); data.Seek(-0x28, SeekOrigin.End); //Note: Stella Glow uses Little Endian BFLIMs, so please don't check the magic ;) string climMagic = IOUtils.readStringWithLength(input, 4); ushort endian = input.ReadUInt16(); uint climHeaderLength = input.ReadUInt32(); input.ReadUInt16(); uint fileLength = input.ReadUInt32(); input.ReadUInt32(); string imagMagic = IOUtils.readStringWithLength(input, 4); uint imagHeaderLength = input.ReadUInt32(); ushort width = input.ReadUInt16(); ushort height = input.ReadUInt16(); uint format = input.ReadUInt32(); uint length = input.ReadUInt32(); if (climMagic == "FLIM") { format = (format >> 16) & 0xf; } data.Seek(-(length + 0x28), SeekOrigin.End); byte[] buffer = new byte[length]; data.Read(buffer, 0, buffer.Length); data.Close(); int pow2Width = (int)(Math.Pow(2, Math.Ceiling(Math.Log(width) / Math.Log(2)))); int pow2Height = (int)(Math.Pow(2, Math.Ceiling(Math.Log(height) / Math.Log(2)))); Bitmap bmp = null; switch (format) { case 0: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.l8); break; case 1: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.a8); break; case 2: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.la4); break; case 3: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.la8); break; case 4: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.hilo8); break; case 5: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.rgb565); break; case 6: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.rgb8); break; case 7: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.rgba5551); break; case 8: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.rgba4); break; case 9: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.rgba8); break; case 0xa: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.etc1); break; case 0xb: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.etc1a4); break; case 0xc: bmp = TextureCodec.decode(buffer, pow2Width, pow2Height, RenderBase.OTextureFormat.l4); break; } Bitmap newBmp = new Bitmap(width, height); Graphics gfx = Graphics.FromImage(newBmp); gfx.DrawImage(bmp, new Rectangle(0, 0, width, height), new Rectangle(0, 0, width, height), GraphicsUnit.Pixel); gfx.Dispose(); return(new RenderBase.OTexture(newBmp, "texture")); }
/// <summary> /// Loads a Game freak texture. /// </summary> /// <param name="data">The texture data</param> /// <returns>The image as a texture</returns> public static Ohana3DS_Transfigured.Ohana.RenderBase.OTexture load(Stream data, bool keepOpen = false) { BinaryReader input = new BinaryReader(data); long descAddress = data.Position; data.Seek(8, SeekOrigin.Current); if (Ohana3DS_Transfigured.Ohana.IOUtils.readStringWithLength(input, 7) != "texture") { return(null); } data.Seek(descAddress + 0x18, SeekOrigin.Begin); int texLength = input.ReadInt32(); data.Seek(descAddress + 0x28, SeekOrigin.Begin); string texName = IOUtils.readStringWithLength(input, 0x40); data.Seek(descAddress + 0x68, SeekOrigin.Begin); ushort width = input.ReadUInt16(); ushort height = input.ReadUInt16(); ushort texFormat = input.ReadUInt16(); ushort texMipMaps = input.ReadUInt16(); data.Seek(0x10, SeekOrigin.Current); byte[] texBuffer = input.ReadBytes(texLength); RenderBase.OTextureFormat fmt = RenderBase.OTextureFormat.dontCare; switch (texFormat) { case 0x2: fmt = RenderBase.OTextureFormat.rgb565; break; case 0x3: fmt = RenderBase.OTextureFormat.rgb8; break; case 0x4: fmt = RenderBase.OTextureFormat.rgba8; break; case 0x16: fmt = RenderBase.OTextureFormat.rgba4; break; case 0x17: fmt = RenderBase.OTextureFormat.rgba5551; break; case 0x23: fmt = RenderBase.OTextureFormat.la8; break; case 0x24: fmt = RenderBase.OTextureFormat.hilo8; break; case 0x25: fmt = RenderBase.OTextureFormat.l8; break; case 0x26: fmt = RenderBase.OTextureFormat.a8; break; case 0x27: fmt = RenderBase.OTextureFormat.la4; break; case 0x28: fmt = RenderBase.OTextureFormat.l4; break; case 0x29: fmt = RenderBase.OTextureFormat.a4; break; case 0x2a: fmt = RenderBase.OTextureFormat.etc1; break; case 0x2b: fmt = RenderBase.OTextureFormat.etc1a4; break; default: Debug.WriteLine("Unk tex fmt " + texFormat.ToString("X4") + " @ " + texName); break; } Bitmap tex = TextureCodec.decode(texBuffer, width, height, fmt); if (!keepOpen) { data.Close(); } return(new RenderBase.OTexture(tex, texName)); }
private void save() { using (FileStream data = new FileStream(currentFile, FileMode.Open)) { BinaryReader input = new BinaryReader(data); BinaryWriter output = new BinaryWriter(data); for (int i = 0; i < bch.textures.Count; i++) { loadedTexture tex = bch.textures[i]; if (tex.modified) { byte[] buffer = align(TextureCodec.encode(tex.texture.texture, RenderBase.OTextureFormat.rgba8)); int diff = buffer.Length - tex.length; replaceData(data, tex.offset, tex.length, buffer); //Update offsets of next textures tex.length = buffer.Length; tex.modified = false; updateTexture(i, tex); for (int j = i; j < bch.textures.Count; j++) { loadedTexture next = bch.textures[j]; next.offset = (uint)(next.offset + diff); updateTexture(j, next); } //Update all addresses poiting after the replaced data bch.relocationTableOffset = (uint)(bch.relocationTableOffset + diff); for (int index = 0; index < bch.relocationTableLength; index += 4) { data.Seek(bch.relocationTableOffset + index, SeekOrigin.Begin); uint value = input.ReadUInt32(); uint offset = value & 0x1ffffff; byte flags = (byte)(value >> 25); if ((flags & 0x20) > 0 || flags == 7 || flags == 0xc) { if ((flags & 0x20) > 0) { data.Seek((offset * 4) + bch.gpuCommandsOffset, SeekOrigin.Begin); } else { data.Seek((offset * 4) + bch.mainHeaderOffset, SeekOrigin.Begin); } uint address = input.ReadUInt32(); if (address + bch.dataOffset > tex.offset) { address = (uint)(address + diff); data.Seek(-4, SeekOrigin.Current); output.Write(address); } } } uint newSize = (uint)((tex.texture.texture.Width << 16) | tex.texture.texture.Height); //Update texture format data.Seek(tex.gpuCommandsOffset, SeekOrigin.Begin); for (int index = 0; index < tex.gpuCommandsWordCount * 3; index++) { uint command = input.ReadUInt32(); switch (command) { case 0xf008e: case 0xf0096: case 0xf009e: replaceCommand(data, output, 0); //Set texture format to 0 = RGBA8888 break; case 0xf0082: case 0xf0092: case 0xf009a: replaceCommand(data, output, newSize); //Set new texture size break; } } //Update material texture format foreach (loadedMaterial mat in bch.materials) { data.Seek(mat.gpuCommandsOffset, SeekOrigin.Begin); for (int index = 0; index < mat.gpuCommandsWordCount; index++) { uint command = input.ReadUInt32(); switch (command) { case 0xf008e: if (mat.texture0 == tex.texture.name || mat.texture0 == "") { replaceCommand(data, output, 0); } break; case 0xf0096: if (mat.texture1 == tex.texture.name || mat.texture1 == "") { replaceCommand(data, output, 0); } break; case 0xf009e: if (mat.texture2 == tex.texture.name || mat.texture2 == "") { replaceCommand(data, output, 0); } break; } } } //Patch up BCH header for new offsets and lengths data.Seek(4, SeekOrigin.Begin); byte backwardCompatibility = input.ReadByte(); byte forwardCompatibility = input.ReadByte(); //Update Data Extended and Relocation Table offsets data.Seek(18, SeekOrigin.Current); if (backwardCompatibility > 0x20) { updateAddress(data, input, output, diff); } updateAddress(data, input, output, diff); //Update data length data.Seek(12, SeekOrigin.Current); updateAddress(data, input, output, diff); } } } MessageBox.Show("Done!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); }
private bool open(string fileName) { using (FileStream data = new FileStream(fileName, FileMode.Open)) { BinaryReader input = new BinaryReader(data); string magic = IOUtils.readString(input, 0); if (magic != "BCH") { return(false); } currentFile = fileName; data.Seek(4, SeekOrigin.Current); byte backwardCompatibility = input.ReadByte(); byte forwardCompatibility = input.ReadByte(); ushort version = input.ReadUInt16(); uint mainHeaderOffset = input.ReadUInt32(); uint stringTableOffset = input.ReadUInt32(); uint gpuCommandsOffset = input.ReadUInt32(); uint dataOffset = input.ReadUInt32(); uint dataExtendedOffset = backwardCompatibility > 0x20 ? input.ReadUInt32() : 0; uint relocationTableOffset = input.ReadUInt32(); uint mainHeaderLength = input.ReadUInt32(); uint stringTableLength = input.ReadUInt32(); uint gpuCommandsLength = input.ReadUInt32(); uint dataLength = input.ReadUInt32(); uint dataExtendedLength = backwardCompatibility > 0x20 ? input.ReadUInt32() : 0; uint relocationTableLength = input.ReadUInt32(); data.Seek(mainHeaderOffset, SeekOrigin.Begin); uint modelsPointerTableOffset = input.ReadUInt32() + mainHeaderOffset; uint modelsPointerTableEntries = input.ReadUInt32(); data.Seek(mainHeaderOffset + 0x24, SeekOrigin.Begin); uint texturesPointerTableOffset = input.ReadUInt32() + mainHeaderOffset; uint texturesPointerTableEntries = input.ReadUInt32(); bch = new loadedBCH(); //Textures for (int index = 0; index < texturesPointerTableEntries; index++) { data.Seek(texturesPointerTableOffset + (index * 4), SeekOrigin.Begin); data.Seek(input.ReadUInt32() + mainHeaderOffset, SeekOrigin.Begin); loadedTexture tex; tex.modified = false; tex.gpuCommandsOffset = input.ReadUInt32() + gpuCommandsOffset; tex.gpuCommandsWordCount = input.ReadUInt32(); data.Seek(0x14, SeekOrigin.Current); uint textureNameOffset = input.ReadUInt32(); string textureName = IOUtils.readString(input, textureNameOffset + stringTableOffset); data.Seek(tex.gpuCommandsOffset, SeekOrigin.Begin); PICACommandReader textureCommands = new PICACommandReader(data, tex.gpuCommandsWordCount); tex.offset = textureCommands.getTexUnit0Address() + dataOffset; RenderBase.OTextureFormat fmt = textureCommands.getTexUnit0Format(); Size textureSize = textureCommands.getTexUnit0Size(); switch (fmt) { case RenderBase.OTextureFormat.rgba8: tex.length = (textureSize.Width * textureSize.Height) * 4; break; case RenderBase.OTextureFormat.rgb8: tex.length = (textureSize.Width * textureSize.Height) * 3; break; case RenderBase.OTextureFormat.rgba5551: tex.length = (textureSize.Width * textureSize.Height) * 2; break; case RenderBase.OTextureFormat.rgb565: tex.length = (textureSize.Width * textureSize.Height) * 2; break; case RenderBase.OTextureFormat.rgba4: tex.length = (textureSize.Width * textureSize.Height) * 2; break; case RenderBase.OTextureFormat.la8: tex.length = (textureSize.Width * textureSize.Height) * 2; break; case RenderBase.OTextureFormat.hilo8: tex.length = (textureSize.Width * textureSize.Height) * 2; break; case RenderBase.OTextureFormat.l8: tex.length = textureSize.Width * textureSize.Height; break; case RenderBase.OTextureFormat.a8: tex.length = textureSize.Width * textureSize.Height; break; case RenderBase.OTextureFormat.la4: tex.length = textureSize.Width * textureSize.Height; break; case RenderBase.OTextureFormat.l4: tex.length = (textureSize.Width * textureSize.Height) >> 1; break; case RenderBase.OTextureFormat.a4: tex.length = (textureSize.Width * textureSize.Height) >> 1; break; case RenderBase.OTextureFormat.etc1: tex.length = (textureSize.Width * textureSize.Height) >> 1; break; case RenderBase.OTextureFormat.etc1a4: tex.length = textureSize.Width * textureSize.Height; break; default: throw new Exception("OBCHTextureReplacer: Invalid texture format on BCH!"); } while ((tex.length & 0x7f) > 0) { tex.length++; } data.Seek(tex.offset, SeekOrigin.Begin); byte[] buffer = new byte[textureSize.Width * textureSize.Height * 4]; input.Read(buffer, 0, buffer.Length); Bitmap texture = TextureCodec.decode( buffer, textureSize.Width, textureSize.Height, fmt); tex.texture = new RenderBase.OTexture(texture, textureName); bch.textures.Add(tex); } //Materials for (int mdlIndex = 0; mdlIndex < modelsPointerTableEntries; mdlIndex++) { data.Seek(modelsPointerTableOffset + (mdlIndex * 4), SeekOrigin.Begin); data.Seek(input.ReadUInt32() + mainHeaderOffset, SeekOrigin.Begin); data.Seek(0x34, SeekOrigin.Current); uint materialsTableOffset = input.ReadUInt32() + mainHeaderOffset; uint materialsTableEntries = input.ReadUInt32(); for (int index = 0; index < materialsTableEntries; index++) { if (backwardCompatibility < 0x21) { data.Seek(materialsTableOffset + (index * 0x58), SeekOrigin.Begin); } else { data.Seek(materialsTableOffset + (index * 0x2c), SeekOrigin.Begin); } loadedMaterial mat; data.Seek(0x10, SeekOrigin.Current); mat.gpuCommandsOffset = input.ReadUInt32() + gpuCommandsOffset; mat.gpuCommandsWordCount = input.ReadUInt32(); if (backwardCompatibility < 0x21) { data.Seek(0x30, SeekOrigin.Current); } else { data.Seek(4, SeekOrigin.Current); } uint texture0Offset = input.ReadUInt32() + stringTableOffset; uint texture1Offset = input.ReadUInt32() + stringTableOffset; uint texture2Offset = input.ReadUInt32() + stringTableOffset; mat.texture0 = IOUtils.readString(input, texture0Offset); mat.texture1 = IOUtils.readString(input, texture1Offset); mat.texture2 = IOUtils.readString(input, texture2Offset); bch.materials.Add(mat); } } bch.mainHeaderOffset = mainHeaderOffset; bch.gpuCommandsOffset = gpuCommandsOffset; bch.dataOffset = dataOffset; bch.relocationTableOffset = relocationTableOffset; bch.relocationTableLength = relocationTableLength; } updateTexturesList(); return(true); }