Poplicola
Sorceror
UltimaSDK for
I'm adding support for the XNA library to UltimaSDK as I work on an XNA Ultima Online client. I'll post my additions and modifications here, so that people can reference them as they work on their own XNA Ultima Online projects. You can also use these in your non-XNA projects that are using code from UltimaSDK by commenting out the "#define IsXNA" line, which will strip all references to the XNA library from the source during debug and compile.
I've also attached an image from the running engine which displays the pitch, yaw, roll, etc. values you will need to create an orthogonal projection that matches Ultima Online (well, nearly) in XNA.
Comments and suggestions are welcome!
UltimaSDK Art.cs (May 4, 2009)
UltimaSDK Gumps.cs (May 4, 2009)
UltimaSDK Textures.cs (May 4, 2009)
I'm adding support for the XNA library to UltimaSDK as I work on an XNA Ultima Online client. I'll post my additions and modifications here, so that people can reference them as they work on their own XNA Ultima Online projects. You can also use these in your non-XNA projects that are using code from UltimaSDK by commenting out the "#define IsXNA" line, which will strip all references to the XNA library from the source during debug and compile.
I've also attached an image from the running engine which displays the pitch, yaw, roll, etc. values you will need to create an orthogonal projection that matches Ultima Online (well, nearly) in XNA.
Comments and suggestions are welcome!
UltimaSDK Art.cs (May 4, 2009)
Code:
// Poplicola last XNA Update: 5/4/09
// Comment out the following line if you are not referencing the XNA library.
#define IsXNA
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
#if IsXNA
using Microsoft.Xna.Framework.Graphics;
#endif
namespace UndeadNET.UltimaSDK
{
public sealed class Art
{
private static FileIndex m_FileIndex = new FileIndex( "Artidx.mul", "Art.mul", 0x10000, 4 );
public static FileIndex FileIndex{ get{ return m_FileIndex; } }
private static Bitmap[] m_Cache = new Bitmap[0x10000];
private static Bitmap[] Cache{ get{ return m_Cache; } }
private Art()
{
}
public unsafe static void Measure(Bitmap bmp, out int xMin, out int yMin, out int xMax, out int yMax)
{
xMin = yMin = 0;
xMax = yMax = -1;
if (bmp == null || bmp.Width <= 0 || bmp.Height <= 0)
return;
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppRgb555);
int delta = ((bd.Stride) >> 1) - bd.Width;
int lineDelta = bd.Stride >> 1;
ushort* pBuffer = (ushort*)bd.Scan0;
ushort* pLineEnd = pBuffer + bd.Width;
ushort* pEnd = pBuffer + (bd.Height * lineDelta);
bool foundPixel = false;
int x = 0, y = 0;
while (pBuffer < pEnd)
{
while (pBuffer < pLineEnd)
{
ushort c = *pBuffer++;
if ((c & 0x8000) != 0)
{
if (!foundPixel)
{
foundPixel = true;
xMin = xMax = x;
yMin = yMax = y;
}
else
{
if (x < xMin)
xMin = x;
if (y < yMin)
yMin = y;
if (x > xMax)
xMax = x;
if (y > yMax)
yMax = y;
}
}
++x;
}
pBuffer += delta;
pLineEnd += lineDelta;
++y;
x = 0;
}
bmp.UnlockBits(bd);
}
public static Bitmap GetLand( int index )
{
return GetLand(index, true);
}
public static Bitmap GetLand(int index, bool cache)
{
index &= 0x3FFF;
if (m_Cache[index] != null)
return m_Cache[index];
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
if (cache)
return m_Cache[index] = LoadLand(stream);
else
return LoadLand(stream);
}
public static Bitmap GetStatic(int index)
{
return GetStatic(index, true);
}
public static Bitmap GetStatic(int index, bool cache)
{
if (index + 0x4000 > int.MaxValue)
{
throw new ArithmeticException("The index must not excede (int.MaxValue - 0x4000)");
}
index += 0x4000;
index &= 0xFFFF;
if ( m_Cache[index] != null )
return m_Cache[index];
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek( index, out length, out extra, out patched );
if ( stream == null )
return null;
if (cache)
return m_Cache[index] = LoadStatic(stream);
else
return LoadStatic(stream);
}
private static unsafe Bitmap LoadStatic( Stream stream )
{
BinaryReader bin = new BinaryReader( stream );
bin.ReadInt32();
int width = bin.ReadInt16();
int height = bin.ReadInt16();
if ( width <= 0 || height <= 0 )
return null;
int[] lookups = new int[height];
int start = (int)bin.BaseStream.Position + (height * 2);
for ( int i = 0; i < height; ++i )
lookups[i] = (int)(start + (bin.ReadUInt16() * 2));
Bitmap bmp = new Bitmap( width, height, PixelFormat.Format16bppRgb555 );
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb555);
ushort *line = (ushort *)bd.Scan0;
int delta = bd.Stride >> 1;
for ( int y = 0; y < height; ++y, line += delta )
{
bin.BaseStream.Seek( lookups[y], SeekOrigin.Begin );
ushort *cur = line;
ushort *end;
int xOffset, xRun;
while ( ((xOffset = bin.ReadUInt16()) + (xRun = bin.ReadUInt16())) != 0 )
{
cur += xOffset;
end = cur + xRun;
while ( cur < end )
*cur++ = (ushort)(bin.ReadUInt16() ^ 0x8000);
}
}
bmp.UnlockBits( bd );
return bmp;
}
private static unsafe Bitmap LoadLand( Stream stream )
{
Bitmap bmp = new Bitmap(44, 44, PixelFormat.Format16bppRgb555);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, 44, 44), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb555);
BinaryReader bin = new BinaryReader( stream );
int xOffset = 21;
int xRun = 2;
ushort *line = (ushort *)bd.Scan0;
int delta = bd.Stride >> 1;
for ( int y = 0; y < 22; ++y, --xOffset, xRun += 2, line += delta )
{
ushort *cur = line + xOffset;
ushort *end = cur + xRun;
while ( cur < end )
*cur++ = (ushort)(bin.ReadUInt16() | 0x8000);
}
xOffset = 0;
xRun = 44;
for ( int y = 0; y < 22; ++y, ++xOffset, xRun -= 2, line += delta )
{
ushort *cur = line + xOffset;
ushort *end = cur + xRun;
while ( cur < end )
*cur++ = (ushort)(bin.ReadUInt16() | 0x8000);
}
bmp.UnlockBits( bd );
return bmp;
}
#if IsXNA
private static Texture2D[] m_CacheXNA = new Texture2D[0x10000];
public static Texture2D GetLandXNA(GraphicsDevice nGraphicsDevice, int index)
{
return GetLandXNA(nGraphicsDevice, index, true);
}
public static Texture2D GetLandXNA(GraphicsDevice nGraphicsDevice, int index, bool cache)
{
index &= 0x3FFF;
if (m_Cache[index] != null)
return m_CacheXNA[index];
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
if (cache)
return m_CacheXNA[index] = LoadLandXNA(nGraphicsDevice, stream);
else
return LoadLandXNA(nGraphicsDevice, stream);
}
public static Texture2D GetStaticXNA(GraphicsDevice nGraphicsDevice, int index)
{
return GetStaticXNA(nGraphicsDevice, index, true);
}
public static Texture2D GetStaticXNA(GraphicsDevice nGraphicsDevice, int index, bool cache)
{
if (index + 0x4000 > int.MaxValue)
{
throw new ArithmeticException("The index must not excede (int.MaxValue - 0x4000)");
}
index += 0x4000;
index &= 0xFFFF;
if (m_CacheXNA[index] != null)
return m_CacheXNA[index];
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
if (cache)
return m_CacheXNA[index] = LoadStaticXNA(nGraphicsDevice, stream);
else
return LoadStaticXNA(nGraphicsDevice, stream);
}
private static unsafe Texture2D LoadStaticXNA(GraphicsDevice nGraphicsDevice, Stream stream)
{
BinaryReader bin = new BinaryReader(stream);
bin.ReadInt32();
int width = bin.ReadInt16();
int height = bin.ReadInt16();
if (width <= 0 || height <= 0)
return null;
int[] lookups = new int[height];
int start = (int)bin.BaseStream.Position + (height * 2);
for (int i = 0; i < height; ++i)
lookups[i] = (int)(start + (bin.ReadUInt16() * 2));
int iHeightOffset = 0;
if (height < 44)
{
iHeightOffset = 44 - height;
}
uint[] pixels = new uint[width * (height + iHeightOffset)];
fixed (uint* iFirstPixel = &pixels[0])
{
for (int y = 0; y < height; ++y)
{
bin.BaseStream.Seek(lookups[y], SeekOrigin.Begin);
int xOffset, xRun;
uint* cur = iFirstPixel + (y + iHeightOffset) * width;
uint* end;
while (((xOffset = bin.ReadUInt16()) + (xRun = bin.ReadUInt16())) != 0)
{
cur += xOffset;
end = cur + xRun;
while (cur < end)
{
uint color = (uint)bin.ReadUInt16() ^ 0x8000;
*cur++ = 0xff000000 + (
((((color >> 10) & 0x1F) * 0xFF / 0x1F) << 16) |
((((color >> 5) & 0x1F) * 0xFF / 0x1F) << 8) |
(((color & 0x1F) * 0xFF / 0x1F))
);
}
}
}
}
Texture2D iTexture = new Texture2D(nGraphicsDevice, width, height + iHeightOffset);
iTexture.SetData(pixels);
return iTexture;
}
private static unsafe Texture2D LoadLandXNA(GraphicsDevice nGraphicsDevice, Stream stream)
{
BinaryReader bin = new BinaryReader(stream);
int xOffset = 21;
int xRun = 2;
uint[] pixels = new uint[44 * 44];
fixed (uint* iFirstPixel = &pixels[0])
{
for (int y = 0; y < 22; ++y, --xOffset, xRun += 2)
{
uint* cur = iFirstPixel + (y * 44) + xOffset;
uint* end = cur + xRun;
while (cur < end)
{
uint color = (uint)bin.ReadUInt16() ^ 0x8000;
*cur++ = 0xff000000 + (
((((color >> 10) & 0x1F) * 0xFF / 0x1F) << 16) |
((((color >> 5) & 0x1F) * 0xFF / 0x1F) << 8) |
(((color & 0x1F) * 0xFF / 0x1F))
);
}
}
xOffset = 0;
xRun = 44;
for (int y = 0; y < 22; ++y, ++xOffset, xRun -= 2)
{
uint* cur = iFirstPixel + ((y + 22) * 44) + xOffset;
uint* end = cur + xRun;
while (cur < end)
{
uint color = (uint)bin.ReadUInt16() ^ 0x8000;
*cur++ = 0xff000000 + (
((((color >> 10) & 0x1F) * 0xFF / 0x1F) << 16) |
((((color >> 5) & 0x1F) * 0xFF / 0x1F) << 8) |
(((color & 0x1F) * 0xFF / 0x1F))
);
}
}
}
Texture2D iTexture = new Texture2D(nGraphicsDevice, 44, 44);
iTexture.SetData(pixels);
return iTexture;
}
#endif
}
}
UltimaSDK Gumps.cs (May 4, 2009)
Code:
// Poplicola last XNA Update: 5/4/09
// Comment out the following line if you are not referencing the XNA library.
#define IsXNA
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
#if IsXNA
using Microsoft.Xna.Framework.Graphics;
#endif
namespace UndeadNET.UltimaSDK
{
public class Gumps
{
private static FileIndex m_FileIndex = new FileIndex("Gumpidx.mul", "Gumpart.mul", 0x10000, 12);
public static FileIndex FileIndex { get { return m_FileIndex; } }
private static byte[] m_PixelBuffer;
private static byte[] m_StreamBuffer;
private static byte[] m_ColorTable;
public unsafe static Bitmap GetGump(int index, Hue hue, bool onlyHueGrayPixels)
{
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
int width = (extra >> 16) & 0xFFFF;
int height = extra & 0xFFFF;
if (width <= 0 || height <= 0)
return null;
int bytesPerLine = width << 1;
int bytesPerStride = (bytesPerLine + 3) & ~3;
int bytesForImage = height * bytesPerStride;
int pixelsPerStride = (width + 1) & ~1;
int pixelsPerStrideDelta = pixelsPerStride - width;
byte[] pixelBuffer = m_PixelBuffer;
if (pixelBuffer == null || pixelBuffer.Length < bytesForImage)
m_PixelBuffer = pixelBuffer = new byte[(bytesForImage + 2047) & ~2047];
byte[] streamBuffer = m_StreamBuffer;
if (streamBuffer == null || streamBuffer.Length < length)
m_StreamBuffer = streamBuffer = new byte[(length + 2047) & ~2047];
byte[] colorTable = m_ColorTable;
if (colorTable == null)
m_ColorTable = colorTable = new byte[128];
stream.Read(streamBuffer, 0, length);
fixed (short* psHueColors = hue.Colors)
{
fixed (byte* pbStream = streamBuffer)
{
fixed (byte* pbPixels = pixelBuffer)
{
fixed (byte* pbColorTable = colorTable)
{
ushort* pHueColors = (ushort*)psHueColors;
ushort* pHueColorsEnd = pHueColors + 32;
ushort* pColorTable = (ushort*)pbColorTable;
ushort* pColorTableOpaque = pColorTable;
while (pHueColors < pHueColorsEnd)
*pColorTableOpaque++ = *pHueColors++;
ushort* pPixelDataStart = (ushort*)pbPixels;
int* pLookup = (int*)pbStream;
int* pLookupEnd = pLookup + height;
int* pPixelRleStart = pLookup;
int* pPixelRle;
ushort* pPixel = pPixelDataStart;
ushort* pRleEnd = pPixel;
ushort* pPixelEnd = pPixel + width;
ushort color, count;
if (onlyHueGrayPixels)
{
while (pLookup < pLookupEnd)
{
pPixelRle = pPixelRleStart + *pLookup++;
pRleEnd = pPixel;
while (pPixel < pPixelEnd)
{
color = *(ushort*)pPixelRle;
count = *(1 + (ushort*)pPixelRle);
++pPixelRle;
pRleEnd += count;
if (color != 0 && (color & 0x1F) == ((color >> 5) & 0x1F) && (color & 0x1F) == ((color >> 10) & 0x1F))
color = pColorTable[color >> 10];
else if (color != 0)
color ^= 0x8000;
while (pPixel < pRleEnd)
*pPixel++ = color;
}
pPixel += pixelsPerStrideDelta;
pPixelEnd += pixelsPerStride;
}
}
else
{
while (pLookup < pLookupEnd)
{
pPixelRle = pPixelRleStart + *pLookup++;
pRleEnd = pPixel;
while (pPixel < pPixelEnd)
{
color = *(ushort*)pPixelRle;
count = *(1 + (ushort*)pPixelRle);
++pPixelRle;
pRleEnd += count;
if (color != 0)
color = pColorTable[color >> 10];
while (pPixel < pRleEnd)
*pPixel++ = color;
}
pPixel += pixelsPerStrideDelta;
pPixelEnd += pixelsPerStride;
}
}
return new Bitmap(width, height, bytesPerStride, PixelFormat.Format16bppArgb1555, (IntPtr)pPixelDataStart);
}
}
}
}
}
public unsafe static Bitmap GetGump(int index)
{
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
int width = (extra >> 16) & 0xFFFF;
int height = extra & 0xFFFF;
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format16bppRgb555);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb555);
BinaryReader bin = new BinaryReader(stream);
int[] lookups = new int[height];
int start = (int)bin.BaseStream.Position;
for (int i = 0; i < height; ++i)
lookups[i] = start + (bin.ReadInt32() * 4);
ushort* line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < height; ++y, line += delta)
{
bin.BaseStream.Seek(lookups[y], SeekOrigin.Begin);
ushort* cur = line;
ushort* end = line + bd.Width;
while (cur < end)
{
ushort color = bin.ReadUInt16();
ushort* next = cur + bin.ReadUInt16();
if (color == 0)
{
cur = next;
}
else
{
color ^= 0x8000;
while (cur < next)
*cur++ = color;
}
}
}
bmp.UnlockBits(bd);
return bmp;
}
#if IsXNA
public unsafe static Texture2D GetGumpXNA(GraphicsDevice nGraphicsDevice, int index)
{
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
int width = (extra >> 16) & 0xFFFF;
int height = extra & 0xFFFF;
uint[] pixels = new uint[width * height];
BinaryReader bin = new BinaryReader(stream);
int[] lookups = new int[height];
int start = (int)bin.BaseStream.Position;
for (int i = 0; i < height; ++i)
lookups[i] = start + (bin.ReadInt32() * 4);
fixed (uint* line = &pixels[0])
{
for (int y = 0; y < height; ++y)
{
bin.BaseStream.Seek(lookups[y], SeekOrigin.Begin);
uint* cur = line + (y * width);
uint* end = cur + (width);
while (cur < end)
{
uint color = bin.ReadUInt16();
uint* next = cur + bin.ReadUInt16();
if (color == 0)
{
cur = next;
}
else
{
uint color32 = 0xff000000 + (
((((color >> 10) & 0x1F) * 0xFF / 0x1F) << 16) |
((((color >> 5) & 0x1F) * 0xFF / 0x1F) << 8) |
(((color & 0x1F) * 0xFF / 0x1F) )
);
while (cur < next)
*cur++ = color32;
}
}
}
}
Texture2D iTexture = new Texture2D(nGraphicsDevice, width, height);
iTexture.SetData(pixels);
return iTexture;
}
#endif
}
}
UltimaSDK Textures.cs (May 4, 2009)
Code:
// Poplicola last XNA Update: 5/4/09
// Comment out the following line if you are not referencing the XNA library.
#define IsXNA
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
#if IsXNA
using Microsoft.Xna.Framework.Graphics;
#endif
namespace UndeadNET.UltimaSDK
{
/// <summary>
/// Part of the UltimaSDK library. This class loads and provides
/// access to textures from the Ultima Online Client. You can
/// load a texture as a 16bpp(RGB=555) bmp, or if you are
/// using this in an XNA game, you can also load textures as XNA
/// Texture2D objects.
/// </summary>
public class Textures
{
private static FileIndex m_FileIndex = new FileIndex("Texidx.mul", "Texmaps.mul", 0x4000, 10);
private static FileIndex FileIndex { get { return m_FileIndex; } }
/// <summary>
/// Loads a UO Texture as a 16bpp(RGB=555) bmp.
/// </summary>
public unsafe static Bitmap GetTexture(int index)
{
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
return null;
int size = extra == 0 ? 64 : 128;
Bitmap bmp = new Bitmap(size, size, PixelFormat.Format16bppRgb555);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, size, size), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb555);
BinaryReader bin = new BinaryReader(stream);
ushort* line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < size; ++y, line += delta)
{
ushort* cur = line;
ushort* end = cur + size;
while (cur < end)
*cur++ = (ushort)(bin.ReadUInt16() ^ 0x8000);
}
bmp.UnlockBits(bd);
return bmp;
}
#if IsXNA
/// <summary>
/// Loads a UO Texture as a XNA Texture2D object.
/// Only works if you are referencing the XNA library.
/// </summary>
private static Texture2D[] m_XNATextureCache = new Texture2D[0x4000];
public unsafe static Texture2D GetTextureXNA(GraphicsDevice nGraphicsDevice, int index)
{
if (m_XNATextureCache[index] != null)
return m_XNATextureCache[index];
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
int size = extra == 0 ? 64 : 128;
BinaryReader bin = new BinaryReader(stream);
uint[] pixels = new uint[size * size];
fixed (uint* line = &pixels[0])
{
uint* cur = line;
for (int y = 0; y < size; ++y)
{
uint* end = cur + size;
while (cur < end)
{
uint color = (uint)bin.ReadUInt16() ^ 0x8000;
*cur++ = 0xff000000 + (
((((color >> 10) & 0x1F) * 0xFF / 0x1F) << 16) |
((((color >> 5) & 0x1F) * 0xFF / 0x1F) << 8) |
(((color & 0x1F) * 0xFF / 0x1F))
);
*cur++ = color32;
}
}
}
m_XNATextureCache[index] = new Texture2D(nGraphicsDevice, size, size);
m_XNATextureCache[index].SetData(pixels);
return m_XNATextureCache[index];
}
#endif
}
}