diff options
-rw-r--r-- | NW4RTools/BrresReader.cs | 44 | ||||
-rw-r--r-- | NW4RTools/Enums.cs | 15 | ||||
-rw-r--r-- | NW4RTools/Misc.cs | 21 | ||||
-rw-r--r-- | NW4RTools/NW4RTools.csproj | 3 | ||||
-rw-r--r-- | NW4RTools/Texture.cs | 289 | ||||
-rwxr-xr-x | NW4RTools/bin/Debug/NW4RTools.dll | bin | 159232 -> 162816 bytes | |||
-rw-r--r-- | NW4RTools/bin/Debug/NW4RTools.dll.mdb | bin | 87942 -> 89888 bytes | |||
-rw-r--r-- | TestApp/Main.cs | 9 | ||||
-rwxr-xr-x | TestApp/bin/Debug/NW4RTools.dll | bin | 159232 -> 162816 bytes | |||
-rw-r--r-- | TestApp/bin/Debug/NW4RTools.dll.mdb | bin | 87942 -> 89888 bytes | |||
-rwxr-xr-x | TestApp/bin/Debug/TestApp.exe | bin | 4096 -> 4608 bytes | |||
-rw-r--r-- | TestApp/bin/Debug/TestApp.exe.mdb | bin | 479 -> 548 bytes |
12 files changed, 380 insertions, 1 deletions
diff --git a/NW4RTools/BrresReader.cs b/NW4RTools/BrresReader.cs index 20d7739..71dacc3 100644 --- a/NW4RTools/BrresReader.cs +++ b/NW4RTools/BrresReader.cs @@ -64,6 +64,9 @@ namespace NW4RTools { case "3DModels(NW4R)": File.Add(name, ReadAndConvertDict<Model>(ins, ConvertModelResource)); break; + case "Textures(NW4R)": + File.Add(name, ReadAndConvertDict<Texture>(ins, ConvertTextureResource)); + break; default: Debug.Send("Not implemented"); return; @@ -71,6 +74,47 @@ namespace NW4RTools { } } + + + private Texture ConvertTextureResource(string name, InputStream ins) { + using (var c = Debug.Push(name)) { + Texture tex = new Texture(); + + int startPos = ins.Position; + + OffsetMap.Add(startPos, "Texture: " + name); + + byte[] magic = ins.ReadBytes(4); + UInt32 size = ins.ReadUInt32(); + UInt32 version = ins.ReadUInt32(); + + Debug.Send("Offset: 0x{0:X}; Size: 0x{1:X}; Version: {2}", startPos, size, version); + + Int32 resFileOffset = ins.ReadInt32(); + Int32 dataOffset = ins.ReadInt32(); + Int32 nameOffset = ins.ReadInt32(); + + // Flags stores nothing interesting, just "is CI" flag (0x1) + UInt32 flags = ins.ReadUInt32(); + + Int16 width = ins.ReadInt16(); + Int16 height = ins.ReadInt16(); + TextureFormat format = (TextureFormat)ins.ReadUInt32(); + + tex.MipMapCount = ins.ReadUInt32(); + tex.MinLOD = ins.ReadFloat(); + tex.MaxLOD = ins.ReadFloat(); + + ins.Seek(startPos + dataOffset); + OffsetMap.Add(ins.Position, String.Format("Texture Data for: {0} [{1}, {2}x{3}]", name, format, width, height)); + tex.ImportData(ins.ReadBytes(Texture.GetDataSize(width, height, format)), width, height, format); + + return tex; + } + } + + + private Model ConvertModelResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { Model mdl = new Model(); diff --git a/NW4RTools/Enums.cs b/NW4RTools/Enums.cs index 21b6e8d..0b3518b 100644 --- a/NW4RTools/Enums.cs +++ b/NW4RTools/Enums.cs @@ -10,4 +10,19 @@ namespace NW4RTools { LineStrip = 6, Points = 7 } + + + public enum TextureFormat { + I4, + I8, + IA4, + IA8, + RGB565, + RGB5A3, + RGBA8, + C4 = 8, + C8 = 9, + C14X2 = 0xA, + CMPR = 0xE + } } diff --git a/NW4RTools/Misc.cs b/NW4RTools/Misc.cs new file mode 100644 index 0000000..c42b185 --- /dev/null +++ b/NW4RTools/Misc.cs @@ -0,0 +1,21 @@ +using System; +namespace NW4RTools { + public static class Misc { + public static int AlignUp(int val, int to) { + return (val + (to - 1)) & ~(to - 1); + } + + public static uint AlignUp(uint val, uint to) { + return (val + (to - 1)) & ~(to - 1); + } + + public static int AlignDown(int val, int to) { + return val & ~(to - 1); + } + + public static uint AlignDown(uint val, uint to) { + return val & ~(to - 1); + } + } +} + diff --git a/NW4RTools/NW4RTools.csproj b/NW4RTools/NW4RTools.csproj index 6e4bc3e..0a462a3 100644 --- a/NW4RTools/NW4RTools.csproj +++ b/NW4RTools/NW4RTools.csproj @@ -34,6 +34,7 @@ <ItemGroup> <Reference Include="System" /> <Reference Include="System.Xml" /> + <Reference Include="System.Drawing" /> </ItemGroup> <ItemGroup> <Compile Include="AssemblyInfo.cs" /> @@ -59,6 +60,8 @@ <Compile Include="VertexSettings.cs" /> <Compile Include="Util\collada_schema_1_4.cs" /> <Compile Include="ColladaWriter.cs" /> + <Compile Include="Texture.cs" /> + <Compile Include="Misc.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/NW4RTools/Texture.cs b/NW4RTools/Texture.cs new file mode 100644 index 0000000..954852d --- /dev/null +++ b/NW4RTools/Texture.cs @@ -0,0 +1,289 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; + +namespace NW4RTools { + public class Texture { + public struct TextureFormatInfo { + public int TexelWidth, TexelHeight, NybblesPerPixel; + + public const int TexelByteSize = 32; + + private TextureFormatInfo(int tWidth, int tHeight, int npp) { + TexelWidth = tWidth; + TexelHeight = tHeight; + NybblesPerPixel = npp; + } + + static TextureFormatInfo() { + I4 = new TextureFormatInfo(8, 8, 1); + I8 = new TextureFormatInfo(8, 4, 2); + IA4 = new TextureFormatInfo(8, 4, 2); + IA8 = new TextureFormatInfo(4, 4, 4); + RGB565 = new TextureFormatInfo(4, 4, 4); + RGB5A3 = new TextureFormatInfo(4, 4, 4); + RGBA8 = new TextureFormatInfo(4, 4, 8); + C4 = new TextureFormatInfo(8, 8, 1); + C8 = new TextureFormatInfo(8, 4, 2); + C14X2 = new TextureFormatInfo(4, 4, 4); + CMPR = new TextureFormatInfo(8, 8, 1); + } + + public static TextureFormatInfo GetFormat(TextureFormat format) { + switch (format) { + case TextureFormat.I4: + return I4; + case TextureFormat.I8: + return I8; + case TextureFormat.IA4: + return IA4; + case TextureFormat.IA8: + return IA8; + case TextureFormat.RGB565: + return RGB565; + case TextureFormat.RGB5A3: + return RGB5A3; + case TextureFormat.RGBA8: + return RGBA8; + case TextureFormat.C4: + return C4; + case TextureFormat.C8: + return C8; + case TextureFormat.C14X2: + return C14X2; + case TextureFormat.CMPR: + return CMPR; + default: + throw new ArgumentOutOfRangeException("unknown texture format"); + } + } + + public static readonly TextureFormatInfo I4, I8, IA4, IA8, RGB565, RGB5A3, RGBA8, C4, C8, C14X2, + CMPR; + } + + + + public Bitmap BaseImage; + + public TextureFormat Format; + public UInt32 MipMapCount; + public float MinLOD, MaxLOD; + + public Texture() { + } + + + + + public static int GetDataSize(int width, int height, TextureFormat format) { + var info = TextureFormatInfo.GetFormat(format); + + // align width, height up + width = Misc.AlignUp(width, info.TexelWidth); + height = Misc.AlignUp(height, info.TexelHeight); + + return width * height * info.NybblesPerPixel / 2; + } + + unsafe public void ImportData(byte[] imgdata, int width, int height, TextureFormat format) { + var image = new Bitmap(width, height, PixelFormat.Format32bppArgb); + var bits = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, image.PixelFormat); + + var info = TextureFormatInfo.GetFormat(format); + int blkWidth = info.TexelWidth; + int blkHeight = info.TexelHeight; + + /*int blkNumY = 0; + + for (int blkY = 0; blkY < height; blkY += blkHeight) { + byte* pBlkY = (byte*)bits.Scan0 + (blkY * bits.Stride); + + int blkNumX = 0; + + for (int blkX = 0; blkX < width; blkX += blkWidth) { + byte* pBlk = pBlkY + (blkX * sizeof(uint)); + + for (int y = 0; y < blkHeight; y++) { + if ((blkY + y) >= height) + break; + + byte* pRow = pBlk + (y * bits.Stride); + + for (int x = 0; x < blkWidth; x++) { + if ((blkX + x) >= width) + break; + + uint* pPixel = (uint*)pRow + x; + + } + } + + blkNumX++; + } + + blkNumY++; + }*/ + + // What I'm missing overall: IA4, IA8, RGBA8, all the palette ones + + // this thing probably breaks on textures which aren't multiples of the texel width/height, too + // oh well + + var data = new InputStream(imgdata); + + switch (format) { + case TextureFormat.RGB565: + case TextureFormat.RGB5A3: + case TextureFormat.I4: + case TextureFormat.I8: + // I4 is stupid and breaks my parser. I'll keep some state here + bool alreadyHaveI4Nybble = false; + byte currentI4Byte = 0; + + for (int y = 0; y < height; y += blkHeight) { + int yMax = y + blkHeight; + if (yMax > height) + yMax = height; + + for (int x = 0; x < width; x += blkWidth) { + int xMax = x + blkWidth; + if (xMax > width) + xMax = width; + + for (int y1 = y; y1 < yMax; y1++) { + byte *pRow = (byte*)bits.Scan0 + (y1 * bits.Stride); + + for (int x1 = x; x1 < xMax; x1++) { + uint *pPixel = (uint*)pRow + x1; + + if (format == TextureFormat.RGB565) { + ushort val = data.ReadUInt16(); + uint r = (uint)(val & 0xF800) >> 8; + uint g = (uint)(val & 0x7e0) >> 3; + uint b = (uint)(val & 0x1f) << 3; + *pPixel = 0xFF000000 | (r << 16) | (g << 8) | b; + + } else if (format == TextureFormat.RGB5A3) { + ushort val = data.ReadUInt16(); + if ((val & 0x8000) == 0) { + uint a = (uint)(val & 0x7000) >> 3; + uint r = (uint)(val & 0xF00) >> 4; + uint g = (uint)(val & 0xF0); + uint b = (uint)(val & 0xF) << 4; + *pPixel = (a << 24) | (r << 16) | (g << 8) | b; + } else { + uint r = (uint)(val & 0x7c00) >> 7; + uint g = (uint)(val & 0x3e0) >> 2; + uint b = (uint)(val & 0x1f) << 3; + *pPixel = 0xFF000000 | (r << 16) | (g << 8) | b; + } + + } else if (format == TextureFormat.I4) { + if (!alreadyHaveI4Nybble) { + currentI4Byte = data.ReadByte(); + uint val = (uint)currentI4Byte & 0xF0; + *pPixel = 0xFF000000 | (val << 16) | (val << 8) | val; + } else { + uint val = (uint)(currentI4Byte & 0xF) << 4; + *pPixel = 0xFF000000 | (val << 16) | (val << 8) | val; + } + + alreadyHaveI4Nybble = !alreadyHaveI4Nybble; + + } else if (format == TextureFormat.I8) { + byte val = data.ReadByte(); + *pPixel = 0xFF000000 | (uint)(val << 16) | (uint)(val << 8) | val; + } + } + } + } + } + + break; + + case TextureFormat.CMPR: + // this code is messy + + ushort[] rawClrArray = new ushort[4]; + uint[] clrArray = new uint[4]; + + for (int y = 0; y < height; y += 8) { + for (int x = 0; x < width; x += 8) { + for (int iBlockY = 0; iBlockY < 2; iBlockY++) { + for (int iBlockX = 0; iBlockX < 2; iBlockX++) { + // decode a block + rawClrArray[0] = data.ReadUInt16(); + rawClrArray[1] = data.ReadUInt16(); + + bool hasAlpha = false; + if (rawClrArray[0] > rawClrArray[1]) { + rawClrArray[2] = CMPRAvgColor(2, 1, rawClrArray[0], rawClrArray[1]); + rawClrArray[3] = CMPRAvgColor(1, 2, rawClrArray[0], rawClrArray[1]); + } else { + rawClrArray[2] = CMPRAvgColor(1, 1, rawClrArray[0], rawClrArray[1]); + rawClrArray[3] = rawClrArray[1]; + hasAlpha = true; + } + + for (int i = 0; i < 4; i++) { + byte r = (byte)(((rawClrArray[i] >> 11) & 0x1F) << 3); + byte g = (byte)(((rawClrArray[i] >> 5) & 0x3F) << 2); + byte b = (byte)((rawClrArray[i] & 0x1F) << 3); + byte a = (byte)(((i == 3) && hasAlpha) ? 0 : 255); + + clrArray[i] = (uint)(a << 24) | (uint)(r << 16) | (uint)(g << 8) | b; + } + + for (int inY = 0; inY < 4; inY++) { + byte* pRow = (byte*)bits.Scan0 + ((y + (iBlockY * 4) + inY) * bits.Stride); + byte val = data.ReadByte(); + + for (int inX = 0; inX < 4; inX++) { + uint *pPixel = (uint*)pRow + x + (iBlockX * 4) + inX; + *pPixel = clrArray[(val >> 6) & 3]; + val <<= 2; + } + } + } + } + } + } + + break; + + default: + Console.WriteLine("Unsupported texture format: {0}", format); + break; + } + + + image.UnlockBits(bits); + + BaseImage = image; + Format = format; + } + + private static ushort CMPRAvgColor(ushort w0, ushort w1, ushort c0, ushort c1) { + uint result; + + ushort a0 = (ushort)(c0 >> 11); + ushort a1 = (ushort)(c1 >> 11); + uint a = (uint)((w0*a0 + w1*a1) / (w0+w1)); + result = (a << 11) & 0xffff; + + a0 = (ushort)((c0 >> 5) & 63); + a1 = (ushort)((c1 >> 5) & 63); + a = (uint)((w0 * a0 + w1 * a1) / (w0 + w1)); + result |= ((a << 5) & 0xffff); + + a0 = (ushort)(c0 & 31); + a1 = (ushort)(c1 & 31); + a = (uint)((w0 * a0 + w1 * a1) / (w0 + w1)); + result |= a; + + return (ushort)result; + } + } +} + diff --git a/NW4RTools/bin/Debug/NW4RTools.dll b/NW4RTools/bin/Debug/NW4RTools.dll Binary files differindex 611af6c..870660f 100755 --- a/NW4RTools/bin/Debug/NW4RTools.dll +++ b/NW4RTools/bin/Debug/NW4RTools.dll diff --git a/NW4RTools/bin/Debug/NW4RTools.dll.mdb b/NW4RTools/bin/Debug/NW4RTools.dll.mdb Binary files differindex a9dff6f..d8889f1 100644 --- a/NW4RTools/bin/Debug/NW4RTools.dll.mdb +++ b/NW4RTools/bin/Debug/NW4RTools.dll.mdb diff --git a/TestApp/Main.cs b/TestApp/Main.cs index afe6dda..408c95f 100644 --- a/TestApp/Main.cs +++ b/TestApp/Main.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Collections.Generic; using NW4RTools; namespace TestApp { @@ -7,7 +8,7 @@ namespace TestApp { public static void Main(string[] args) { string mdlPath = "/home/me/Games/Newer/ModelRev/"; - string mdlName = "CS_W7"; + string mdlName = "CS_W9"; //string mdlName = "bgB_4502"; string whatever = (mdlName == "CS_W2" || mdlName == "CS_W3" || mdlName == "CS_W6") ? "a" : ""; @@ -15,6 +16,12 @@ namespace TestApp { byte[] file = File.ReadAllBytes(mdlPath + mdlName + ".brres"); ResFile rf = BrresReader.LoadFile(file); + var texs = rf.GetGroup<Texture>("Textures(NW4R)"); + // wtf C#?! + foreach (var kv in (IEnumerable<KeyValuePair<string,Texture>>)texs) { + kv.Value.BaseImage.Save(mdlPath + kv.Key + ".png"); + } + //var objFile = File.Open(mdlPath + mdlName + ".obj", FileMode.OpenOrCreate); var objFile = File.Open(mdlPath + mdlName + ".dae", FileMode.OpenOrCreate); //var sw = new StreamWriter(objFile); diff --git a/TestApp/bin/Debug/NW4RTools.dll b/TestApp/bin/Debug/NW4RTools.dll Binary files differindex 611af6c..870660f 100755 --- a/TestApp/bin/Debug/NW4RTools.dll +++ b/TestApp/bin/Debug/NW4RTools.dll diff --git a/TestApp/bin/Debug/NW4RTools.dll.mdb b/TestApp/bin/Debug/NW4RTools.dll.mdb Binary files differindex a9dff6f..d8889f1 100644 --- a/TestApp/bin/Debug/NW4RTools.dll.mdb +++ b/TestApp/bin/Debug/NW4RTools.dll.mdb diff --git a/TestApp/bin/Debug/TestApp.exe b/TestApp/bin/Debug/TestApp.exe Binary files differindex 5845276..4c3f608 100755 --- a/TestApp/bin/Debug/TestApp.exe +++ b/TestApp/bin/Debug/TestApp.exe diff --git a/TestApp/bin/Debug/TestApp.exe.mdb b/TestApp/bin/Debug/TestApp.exe.mdb Binary files differindex a86d949..46a6888 100644 --- a/TestApp/bin/Debug/TestApp.exe.mdb +++ b/TestApp/bin/Debug/TestApp.exe.mdb |