public byte[] Rebuild() { FileOutput o = new FileOutput(); FileOutput data = new FileOutput(); //We always want BE for the first six bytes o.Endian = Endianness.Big; data.Endian = Endianness.Big; if (Endian == Endianness.Big) { o.writeUInt(0x4E545033); //NTP3 } else if (Endian == Endianness.Little) { o.writeUInt(0x4E545744); //NTWD } //Most NTWU NUTs are 0x020E, which isn't valid for NTP3/NTWD if (Version > 0x0200) { Version = 0x0200; } o.writeUShort(Version); //After that, endian is used appropriately o.Endian = Endian; data.Endian = Endian; o.writeUShort((ushort)Textures.Count); o.writeInt(0); o.writeInt(0); //calculate total header size uint headerLength = 0; foreach (NutTexture texture in Textures) { byte surfaceCount = (byte)texture.surfaces.Count; bool isCubemap = surfaceCount == 6; if (surfaceCount < 1 || surfaceCount > 6) { throw new NotImplementedException($"Unsupported surface amount {surfaceCount} for texture with hash 0x{texture.HashId:X}. 1 to 6 faces are required."); } else if (surfaceCount > 1 && surfaceCount < 6) { throw new NotImplementedException($"Unsupported cubemap face amount for texture with hash 0x{texture.HashId:X}. Six faces are required."); } byte mipmapCount = (byte)texture.surfaces[0].mipmaps.Count; ushort headerSize = 0x50; if (isCubemap) { headerSize += 0x10; } if (mipmapCount > 1) { headerSize += (ushort)(mipmapCount * 4); while (headerSize % 0x10 != 0) { headerSize += 1; } } headerLength += headerSize; } // write headers+data foreach (NutTexture texture in Textures) { byte surfaceCount = (byte)texture.surfaces.Count; bool isCubemap = surfaceCount == 6; byte mipmapCount = (byte)texture.surfaces[0].mipmaps.Count; uint dataSize = 0; foreach (var mip in texture.GetAllMipmaps()) { dataSize += (uint)mip.Length; while (dataSize % 0x10 != 0) { dataSize += 1; } } ushort headerSize = 0x50; if (isCubemap) { headerSize += 0x10; } if (mipmapCount > 1) { headerSize += (ushort)(mipmapCount * 4); while (headerSize % 0x10 != 0) { headerSize += 1; } } o.writeUInt(dataSize + headerSize); o.writeUInt(0); o.writeUInt(dataSize); o.writeUShort(headerSize); o.writeUShort(0); o.writeByte(0); o.writeByte(mipmapCount); o.writeByte(0); o.writeByte(texture.getNutFormat()); o.writeShort(texture.Width); o.writeShort(texture.Height); o.writeInt(0); o.writeUInt(texture.DdsCaps2); if (Version < 0x0200) { o.writeUInt(0); } else if (Version >= 0x0200) { o.writeUInt((uint)(headerLength + data.size())); } headerLength -= headerSize; o.writeInt(0); o.writeInt(0); o.writeInt(0); if (isCubemap) { o.writeInt(texture.surfaces[0].mipmaps[0].Length); o.writeInt(texture.surfaces[0].mipmaps[0].Length); o.writeInt(0); o.writeInt(0); } if (texture.getNutFormat() == 14 || texture.getNutFormat() == 17) { texture.SwapChannelOrderDown(); } for (byte surfaceLevel = 0; surfaceLevel < surfaceCount; ++surfaceLevel) { for (byte mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) { int ds = data.size(); data.writeBytes(texture.surfaces[surfaceLevel].mipmaps[mipLevel]); data.align(0x10); if (mipmapCount > 1 && surfaceLevel == 0) { o.writeInt(data.size() - ds); } } } o.align(0x10); if (texture.getNutFormat() == 14 || texture.getNutFormat() == 17) { texture.SwapChannelOrderUp(); } o.writeBytes(new byte[] { 0x65, 0x58, 0x74, 0x00 }); // "eXt\0" o.writeInt(0x20); o.writeInt(0x10); o.writeInt(0x00); o.writeBytes(new byte[] { 0x47, 0x49, 0x44, 0x58 }); // "GIDX" o.writeInt(0x10); o.writeInt(texture.HashId); o.writeInt(0); if (Version < 0x0200) { o.writeOutput(data); data = new FileOutput(); } } if (Version >= 0x0200) { o.writeOutput(data); } return(o.getBytes()); }
public override byte[] Rebuild() { FileOutput f = new FileOutput(); f.Endian = Endianness.Big; f.writeHex("4C494748"); //LIGH f.writeInt(version); f.writeInt(frameCount); if (version == 5) { f.writeInt(frameDuration); } //Offsets int padding = 0; while ((((frameCount * 3) + padding) % 4) != 0) //All offsets must be multiples of 4 or the game won't use the file properly { padding++; } int[] offsets = new int[6]; int currOff = 0x24; if (version == 4) { currOff = 0x24; } else if (version == 5) { currOff = 0x28; } for (int i = 0; i < 5; i++) { offsets[i + 1] = (rgbProperties[i].enabled) ? currOff : 0x0; if (rgbProperties[i].enabled) { currOff += (frameCount * 3) + padding; } } offsets[0] = currOff; for (int i = 0; i < 6; i++) { f.writeInt(offsets[i]); } //RGB properties for (int i = 0; i < 5; i++) { for (int j = 0; j < frameCount; j++) { for (int k = 0; k < 3; k++) { f.writeByte(rgbProperties[i].frames[j][k]); } } for (int j = 0; j < padding; j++) { f.writeByte(0); } } //Light data for (int i = 0; i < frameCount; i++) { for (int j = 0; j < 17; j++) { for (int k = 0; k < 4; k++) { f.writeInt(lightFrames[i].lightSets[j].lights[k].enabled); for (int l = 0; l < 3; l++) { f.writeFloat(lightFrames[i].lightSets[j].lights[k].angle[l]); } f.writeFloat(lightFrames[i].lightSets[j].lights[k].colorHue); f.writeFloat(lightFrames[i].lightSets[j].lights[k].colorSat); f.writeFloat(lightFrames[i].lightSets[j].lights[k].colorVal); } f.writeByte(lightFrames[i].lightSets[j].fog.unknown); for (int k = 0; k < 3; k++) { f.writeByte(lightFrames[i].lightSets[j].fog.color[k]); } } f.writeByte(lightFrames[i].effect.unknown); for (int j = 0; j < 3; j++) { f.writeByte(lightFrames[i].effect.color[j]); } for (int j = 0; j < 3; j++) { f.writeFloat(lightFrames[i].effect.position[j]); } } return(f.getBytes()); }