From 52aada22e106b6fd36b6b94fb3064510ac3ee40c Mon Sep 17 00:00:00 2001
From: Treeki <treeki@gmail.com>
Date: Sun, 13 Feb 2011 04:03:59 +0100
Subject: it converts textures too, now! some formats missing, I'll add them
 later.

---
 NW4RTools/BrresReader.cs              |  44 ++++++
 NW4RTools/Enums.cs                    |  15 ++
 NW4RTools/Misc.cs                     |  21 +++
 NW4RTools/NW4RTools.csproj            |   3 +
 NW4RTools/Texture.cs                  | 289 ++++++++++++++++++++++++++++++++++
 NW4RTools/bin/Debug/NW4RTools.dll     | Bin 159232 -> 162816 bytes
 NW4RTools/bin/Debug/NW4RTools.dll.mdb | Bin 87942 -> 89888 bytes
 TestApp/Main.cs                       |   9 +-
 TestApp/bin/Debug/NW4RTools.dll       | Bin 159232 -> 162816 bytes
 TestApp/bin/Debug/NW4RTools.dll.mdb   | Bin 87942 -> 89888 bytes
 TestApp/bin/Debug/TestApp.exe         | Bin 4096 -> 4608 bytes
 TestApp/bin/Debug/TestApp.exe.mdb     | Bin 479 -> 548 bytes
 12 files changed, 380 insertions(+), 1 deletion(-)
 create mode 100644 NW4RTools/Misc.cs
 create mode 100644 NW4RTools/Texture.cs

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
index 611af6c..870660f 100755
Binary files a/NW4RTools/bin/Debug/NW4RTools.dll and b/NW4RTools/bin/Debug/NW4RTools.dll differ
diff --git a/NW4RTools/bin/Debug/NW4RTools.dll.mdb b/NW4RTools/bin/Debug/NW4RTools.dll.mdb
index a9dff6f..d8889f1 100644
Binary files a/NW4RTools/bin/Debug/NW4RTools.dll.mdb and b/NW4RTools/bin/Debug/NW4RTools.dll.mdb differ
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
index 611af6c..870660f 100755
Binary files a/TestApp/bin/Debug/NW4RTools.dll and b/TestApp/bin/Debug/NW4RTools.dll differ
diff --git a/TestApp/bin/Debug/NW4RTools.dll.mdb b/TestApp/bin/Debug/NW4RTools.dll.mdb
index a9dff6f..d8889f1 100644
Binary files a/TestApp/bin/Debug/NW4RTools.dll.mdb and b/TestApp/bin/Debug/NW4RTools.dll.mdb differ
diff --git a/TestApp/bin/Debug/TestApp.exe b/TestApp/bin/Debug/TestApp.exe
index 5845276..4c3f608 100755
Binary files a/TestApp/bin/Debug/TestApp.exe and b/TestApp/bin/Debug/TestApp.exe differ
diff --git a/TestApp/bin/Debug/TestApp.exe.mdb b/TestApp/bin/Debug/TestApp.exe.mdb
index a86d949..46a6888 100644
Binary files a/TestApp/bin/Debug/TestApp.exe.mdb and b/TestApp/bin/Debug/TestApp.exe.mdb differ
-- 
cgit v1.2.3