Overwrite

Complete Overwrite of the Folder with the free shard. ServUO 57.3 has been added.
This commit is contained in:
Unstable Kitsune
2023-11-28 23:20:26 -05:00
parent 3cd54811de
commit b918192e4e
11608 changed files with 2644205 additions and 47 deletions

212
Ultima/ASCIIFont.cs Normal file
View File

@@ -0,0 +1,212 @@
#region References
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
#endregion
// ascii text support written by arul
namespace Ultima
{
public sealed class ASCIIFont
{
public byte Header { get; private set; }
public byte[] Unk { get; set; }
public Bitmap[] Characters { get; set; }
public int Height { get; set; }
public ASCIIFont(byte header)
{
Header = header;
Height = 0;
Unk = new byte[224];
Characters = new Bitmap[224];
}
/// <summary>
/// Gets Bitmap of given character
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
public Bitmap GetBitmap(char character)
{
return Characters[((((character) - 0x20) & 0x7FFFFFFF) % 224)];
}
public int GetWidth(string text)
{
if (text == null || text.Length == 0)
{
return 0;
}
int width = 0;
for (int i = 0; i < text.Length; ++i)
{
width += GetBitmap(text[i]).Width;
}
return width;
}
public void ReplaceCharacter(int character, Bitmap import)
{
Characters[character] = import;
Height = import.Height;
}
public static ASCIIFont GetFixed(int font)
{
if (font < 0 || font > 9)
{
return ASCIIText.Fonts[3];
}
return ASCIIText.Fonts[font];
}
}
public static class ASCIIText
{
public static ASCIIFont[] Fonts = new ASCIIFont[10];
static ASCIIText()
{
Initialize();
}
/// <summary>
/// Reads fonts.mul
/// </summary>
public static unsafe void Initialize()
{
string path = Files.GetFilePath("fonts.mul");
if (path != null)
{
using (var reader = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var buffer = new byte[(int)reader.Length];
reader.Read(buffer, 0, (int)reader.Length);
fixed (byte* bin = buffer)
{
byte* read = bin;
for (int i = 0; i < 10; ++i)
{
byte header = *read++;
Fonts[i] = new ASCIIFont(header);
for (int k = 0; k < 224; ++k)
{
byte width = *read++;
byte height = *read++;
byte unk = *read++; // delimeter?
if (width > 0 && height > 0)
{
if (height > Fonts[i].Height && k < 96)
{
Fonts[i].Height = height;
}
var bmp = new Bitmap(width, height);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < height; ++y, line += delta)
{
ushort* cur = line;
for (int x = 0; x < width; ++x)
{
var pixel = (ushort)(*read++ | (*read++ << 8));
if (pixel == 0)
{
cur[x] = pixel;
}
else
{
cur[x] = (ushort)(pixel ^ 0x8000);
}
}
}
bmp.UnlockBits(bd);
Fonts[i].Characters[k] = bmp;
Fonts[i].Unk[k] = unk;
}
}
}
}
}
}
}
public static unsafe void Save(string FileName)
{
using (var fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
for (int i = 0; i < 10; ++i)
{
bin.Write(Fonts[i].Header);
for (int k = 0; k < 224; ++k)
{
bin.Write((byte)Fonts[i].Characters[k].Width);
bin.Write((byte)Fonts[i].Characters[k].Height);
bin.Write(Fonts[i].Unk[k]);
Bitmap bmp = Fonts[i].Characters[k];
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < bmp.Height; ++y, line += delta)
{
ushort* cur = line;
for (int x = 0; x < bmp.Width; ++x)
{
if (cur[x] == 0)
{
bin.Write(cur[x]);
}
else
{
bin.Write((ushort)(cur[x] ^ 0x8000));
}
}
}
bmp.UnlockBits(bd);
}
}
}
}
}
/// <summary>
/// Draws Text with font in Bitmap and returns
/// </summary>
/// <param name="fontId"></param>
/// <param name="text"></param>
/// <returns></returns>
public static Bitmap DrawText(int fontId, string text)
{
ASCIIFont font = ASCIIFont.GetFixed(fontId);
var result = new Bitmap(font.GetWidth(text) + 2, font.Height + 2);
int dx = 2;
int dy = font.Height + 2;
using (Graphics graph = Graphics.FromImage(result))
{
for (int i = 0; i < text.Length; ++i)
{
Bitmap bmp = font.GetBitmap(text[i]);
graph.DrawImage(bmp, dx, dy - bmp.Height);
dx += bmp.Width;
}
}
return result;
}
}
}

1093
Ultima/AnimationEdit.cs Normal file

File diff suppressed because it is too large Load Diff

1089
Ultima/Animations.cs Normal file

File diff suppressed because it is too large Load Diff

165
Ultima/Animdata.cs Normal file
View File

@@ -0,0 +1,165 @@
#region References
using System.Collections;
using System.IO;
#endregion
namespace Ultima
{
public sealed class Animdata
{
private static int[] m_Header;
private static byte[] m_Unknown;
public static Hashtable AnimData { get; set; }
static Animdata()
{
Initialize();
}
/// <summary>
/// Reads animdata.mul and fills <see cref="AnimData" />
/// </summary>
public static void Initialize()
{
AnimData = new Hashtable();
string path = Files.GetFilePath("animdata.mul");
if (path != null)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var bin = new BinaryReader(fs))
{
unsafe
{
int id = 0;
int h = 0;
byte unk;
byte fcount;
byte finter;
byte fstart;
sbyte[] fdata;
m_Header = new int[bin.BaseStream.Length / (4 + 8 * (64 + 4))];
while (h < m_Header.Length /*bin.BaseStream.Length != bin.BaseStream.Position*/)
{
m_Header[h++] = bin.ReadInt32(); // chunk header
// Read 8 tiles
byte[] buffer = bin.ReadBytes(544);
fixed (byte* buf = buffer)
{
byte* data = buf;
for (int i = 0; i < 8; ++i, ++id)
{
fdata = new sbyte[64];
for (int j = 0; j < 64; ++j)
{
fdata[j] = (sbyte)*data++;
}
unk = *data++;
fcount = *data++;
finter = *data++;
fstart = *data++;
if (fcount > 0)
{
AnimData[id] = new Data(fdata, unk, fcount, finter, fstart);
}
}
}
}
var remaining = (int)(bin.BaseStream.Length - bin.BaseStream.Position);
if (remaining > 0)
{
m_Unknown = bin.ReadBytes(remaining);
}
}
}
}
}
}
/// <summary>
/// Gets Animation <see cref="Data" />
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static Data GetAnimData(int id)
{
if (AnimData.Contains(id))
{
return ((Data)AnimData[id]);
}
else
{
return null;
}
}
public static void Save(string path)
{
string FileName = Path.Combine(path, "animdata.mul");
using (var fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
int id = 0;
int h = 0;
while (id < m_Header.Length * 8)
{
bin.Write(m_Header[h++]);
for (int i = 0; i < 8; ++i, ++id)
{
Data data = GetAnimData(id);
for (int j = 0; j < 64; ++j)
{
if (data != null)
{
bin.Write(data.FrameData[j]);
}
else
{
bin.Write((sbyte)0);
}
}
if (data != null)
{
bin.Write(data.Unknown);
bin.Write(data.FrameCount);
bin.Write(data.FrameInterval);
bin.Write(data.FrameStart);
}
else
{
bin.Write((byte)0);
bin.Write((byte)0);
bin.Write((byte)0);
bin.Write((byte)0);
}
}
}
if (m_Unknown != null)
{
bin.Write(m_Unknown);
}
}
}
}
public class Data
{
public sbyte[] FrameData { get; set; }
public byte Unknown { get; private set; }
public byte FrameCount { get; set; }
public byte FrameInterval { get; set; }
public byte FrameStart { get; set; }
public Data(sbyte[] frame, byte unk, byte fcount, byte finter, byte fstart)
{
FrameData = frame;
Unknown = unk;
FrameCount = fcount;
FrameInterval = finter;
FrameStart = fstart;
}
}
}
}

837
Ultima/Art.cs Normal file
View File

@@ -0,0 +1,837 @@
#region References
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Security.Cryptography;
#endregion
namespace Ultima
{
public sealed class Art
{
private static FileIndex m_FileIndex = new FileIndex(
"Artidx.mul", "Art.mul", "artLegacyMUL.uop", 0x10000 /*0x13FDC*/, 4, ".tga", 0x13FDC, false);
private static Bitmap[] m_Cache;
private static bool[] m_Removed;
private static readonly Hashtable m_patched = new Hashtable();
public static bool Modified = false;
private static byte[] m_StreamBuffer;
private static byte[] Validbuffer;
private struct CheckSums
{
public byte[] checksum;
public int pos;
public int length;
public int index;
}
private static List<CheckSums> checksumsLand;
private static List<CheckSums> checksumsStatic;
static Art()
{
m_Cache = new Bitmap[0xFFFF];
m_Removed = new bool[0xFFFF];
}
public static int GetMaxItemID()
{
if (GetIdxLength() >= 0x13FDC)
{
return 0xFFFF;
}
if (GetIdxLength() == 0xC000)
{
return 0x7FFF;
}
return 0x3FFF;
}
public static bool IsUOAHS()
{
return (GetIdxLength() >= 0x13FDC);
}
public static ushort GetLegalItemID(int itemID, bool checkmaxid = true)
{
if (itemID < 0)
{
return 0;
}
if (checkmaxid)
{
int max = GetMaxItemID();
if (itemID > max)
{
return 0;
}
}
return (ushort)itemID;
}
public static int GetIdxLength()
{
return (int)(m_FileIndex.IdxLength / 12);
}
/// <summary>
/// ReReads Art.mul
/// </summary>
public static void Reload()
{
m_FileIndex = new FileIndex(
"Artidx.mul", "Art.mul", "artLegacyMUL.uop", 0x10000 /*0x13FDC*/, 4, ".tga", 0x13FDC, false);
m_Cache = new Bitmap[0xFFFF];
m_Removed = new bool[0xFFFF];
m_patched.Clear();
Modified = false;
}
/// <summary>
/// Sets bmp of index in <see cref="m_Cache" /> of Static
/// </summary>
/// <param name="index"></param>
/// <param name="bmp"></param>
public static void ReplaceStatic(int index, Bitmap bmp)
{
index = GetLegalItemID(index);
index += 0x4000;
m_Cache[index] = bmp;
m_Removed[index] = false;
if (m_patched.Contains(index))
{
m_patched.Remove(index);
}
Modified = true;
}
/// <summary>
/// Sets bmp of index in <see cref="m_Cache" /> of Land
/// </summary>
/// <param name="index"></param>
/// <param name="bmp"></param>
public static void ReplaceLand(int index, Bitmap bmp)
{
index &= 0x3FFF;
m_Cache[index] = bmp;
m_Removed[index] = false;
if (m_patched.Contains(index))
{
m_patched.Remove(index);
}
Modified = true;
}
/// <summary>
/// Removes Static index <see cref="m_Removed" />
/// </summary>
/// <param name="index"></param>
public static void RemoveStatic(int index)
{
index = GetLegalItemID(index);
index += 0x4000;
m_Removed[index] = true;
Modified = true;
}
/// <summary>
/// Removes Land index <see cref="m_Removed" />
/// </summary>
/// <param name="index"></param>
public static void RemoveLand(int index)
{
index &= 0x3FFF;
m_Removed[index] = true;
Modified = true;
}
/// <summary>
/// Tests if Static is definied (width and hight check)
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static unsafe bool IsValidStatic(int index)
{
index = GetLegalItemID(index);
index += 0x4000;
if (m_Removed[index])
{
return false;
}
if (m_Cache[index] != null)
{
return true;
}
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return false;
}
if (Validbuffer == null)
{
Validbuffer = new byte[4];
}
stream.Seek(4, SeekOrigin.Current);
stream.Read(Validbuffer, 0, 4);
fixed (byte* b = Validbuffer)
{
var dat = (short*)b;
if (*dat++ <= 0 || *dat <= 0)
{
return false;
}
return true;
}
}
/// <summary>
/// Tests if LandTile is definied
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static bool IsValidLand(int index)
{
index &= 0x3FFF;
if (m_Removed[index])
{
return false;
}
if (m_Cache[index] != null)
{
return true;
}
int length, extra;
bool patched;
return m_FileIndex.Valid(index, out length, out extra, out patched);
}
/// <summary>
/// Returns Bitmap of LandTile (with Cache)
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static Bitmap GetLand(int index)
{
bool patched;
return GetLand(index, out patched);
}
/// <summary>
/// Returns Bitmap of LandTile (with Cache) and verdata bool
/// </summary>
/// <param name="index"></param>
/// <param name="patched"></param>
/// <returns></returns>
public static Bitmap GetLand(int index, out bool patched)
{
index &= 0x3FFF;
if (m_patched.Contains(index))
{
patched = (bool)m_patched[index];
}
else
{
patched = false;
}
if (m_Removed[index])
{
return null;
}
if (m_Cache[index] != null)
{
return m_Cache[index];
}
int length, extra;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (patched)
{
m_patched[index] = true;
}
if (Files.CacheData)
{
return m_Cache[index] = LoadLand(stream, length);
}
else
{
return LoadLand(stream, length);
}
}
public static byte[] GetRawLand(int index)
{
index &= 0x3FFF;
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
var buffer = new byte[length];
stream.Read(buffer, 0, length);
stream.Close();
return buffer;
}
/// <summary>
/// Returns Bitmap of Static (with Cache)
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static Bitmap GetStatic(int index, bool checkmaxid = true)
{
bool patched;
return GetStatic(index, out patched, checkmaxid);
}
/// <summary>
/// Returns Bitmap of Static (with Cache) and verdata bool
/// </summary>
/// <param name="index"></param>
/// <param name="patched"></param>
/// <returns></returns>
public static Bitmap GetStatic(int index, out bool patched, bool checkmaxid = true)
{
index = GetLegalItemID(index, checkmaxid);
index += 0x4000;
if (m_patched.Contains(index))
{
patched = (bool)m_patched[index];
}
else
{
patched = false;
}
if (m_Removed[index])
{
return null;
}
if (m_Cache[index] != null)
{
return m_Cache[index];
}
int length, extra;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (patched)
{
m_patched[index] = true;
}
if (Files.CacheData)
{
return m_Cache[index] = LoadStatic(stream, length);
}
else
{
return LoadStatic(stream, length);
}
}
public static byte[] GetRawStatic(int index)
{
index = GetLegalItemID(index);
index += 0x4000;
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
var buffer = new byte[length];
stream.Read(buffer, 0, length);
stream.Close();
return buffer;
}
public static unsafe 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, Settings.PixelFormat);
int delta = (bd.Stride >> 1) - bd.Width;
int lineDelta = bd.Stride >> 1;
var 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);
}
private static unsafe Bitmap LoadStatic(Stream stream, int length)
{
Bitmap bmp;
if (m_StreamBuffer == null || m_StreamBuffer.Length < length)
{
m_StreamBuffer = new byte[length];
}
stream.Read(m_StreamBuffer, 0, length);
stream.Close();
fixed (byte* data = m_StreamBuffer)
{
var bindata = (ushort*)data;
int count = 2;
//bin.ReadInt32();
int width = bindata[count++];
int height = bindata[count++];
if (width <= 0 || height <= 0)
{
return null;
}
var lookups = new int[height];
int start = (height + 4);
for (int i = 0; i < height; ++i)
{
lookups[i] = (start + (bindata[count++]));
}
bmp = new Bitmap(width, height, Settings.PixelFormat);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < height; ++y, line += delta)
{
count = lookups[y];
ushort* cur = line;
ushort* end;
int xOffset, xRun;
while (((xOffset = bindata[count++]) + (xRun = bindata[count++])) != 0)
{
if (xOffset > delta)
{
break;
}
cur += xOffset;
if (xOffset + xRun > delta)
{
break;
}
end = cur + xRun;
while (cur < end)
{
*cur++ = (ushort)(bindata[count++] ^ 0x8000);
}
}
}
bmp.UnlockBits(bd);
}
return bmp;
}
private static unsafe Bitmap LoadLand(Stream stream, int length)
{
var bmp = new Bitmap(44, 44, Settings.PixelFormat);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, 44, 44), ImageLockMode.WriteOnly, Settings.PixelFormat);
if (m_StreamBuffer == null || m_StreamBuffer.Length < length)
{
m_StreamBuffer = new byte[length];
}
stream.Read(m_StreamBuffer, 0, length);
stream.Close();
fixed (byte* bindata = m_StreamBuffer)
{
var bdata = (ushort*)bindata;
int xOffset = 21;
int xRun = 2;
var 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)(*bdata++ | 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)(*bdata++ | 0x8000);
}
}
}
bmp.UnlockBits(bd);
return bmp;
}
/// <summary>
/// Saves mul
/// </summary>
/// <param name="path"></param>
public static unsafe void Save(string path)
{
checksumsLand = new List<CheckSums>();
checksumsStatic = new List<CheckSums>();
string idx = Path.Combine(path, "artidx.mul");
string mul = Path.Combine(path, "art.mul");
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
var memidx = new MemoryStream();
var memmul = new MemoryStream();
var sha = new SHA256Managed();
//StreamWriter Tex = new StreamWriter(new FileStream("d:/artlog.txt", FileMode.Create, FileAccess.ReadWrite));
using (BinaryWriter binidx = new BinaryWriter(memidx), binmul = new BinaryWriter(memmul))
{
for (int index = 0; index < GetIdxLength(); index++)
{
Files.FireFileSaveEvent();
if (m_Cache[index] == null)
{
if (index < 0x4000)
{
m_Cache[index] = GetLand(index);
}
else
{
m_Cache[index] = GetStatic(index - 0x4000, false);
}
}
Bitmap bmp = m_Cache[index];
if ((bmp == null) || (m_Removed[index]))
{
binidx.Write(-1); // lookup
binidx.Write(0); // length
binidx.Write(-1); // extra
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, (int)-1, (int)-1));
}
else if (index < 0x4000)
{
var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Bmp);
byte[] checksum = sha.ComputeHash(ms.ToArray());
CheckSums sum;
if (compareSaveImagesLand(checksum, out sum))
{
binidx.Write(sum.pos); //lookup
binidx.Write(sum.length);
binidx.Write(0);
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, (int)sum.pos, (int)sum.length));
//Tex.WriteLine(System.String.Format("0x{0:X4} -> 0x{1:X4}", sum.index, index));
continue;
}
//land
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
binidx.Write((int)binmul.BaseStream.Position); //lookup
var length = (int)binmul.BaseStream.Position;
int x = 22;
int y = 0;
int linewidth = 2;
for (int m = 0; m < 22; ++m, ++y, line += delta, linewidth += 2)
{
--x;
ushort* cur = line;
for (int n = 0; n < linewidth; ++n)
{
binmul.Write((ushort)(cur[x + n] ^ 0x8000));
}
}
x = 0;
linewidth = 44;
y = 22;
line = (ushort*)bd.Scan0;
line += delta * 22;
for (int m = 0; m < 22; m++, y++, line += delta, ++x, linewidth -= 2)
{
ushort* cur = line;
for (int n = 0; n < linewidth; n++)
{
binmul.Write((ushort)(cur[x + n] ^ 0x8000));
}
}
int start = length;
length = (int)binmul.BaseStream.Position - length;
binidx.Write(length);
binidx.Write(0);
bmp.UnlockBits(bd);
var s = new CheckSums
{
pos = start,
length = length,
checksum = checksum,
index = index
};
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, start, length));
checksumsLand.Add(s);
}
else
{
var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Bmp);
byte[] checksum = sha.ComputeHash(ms.ToArray());
CheckSums sum;
if (compareSaveImagesStatic(checksum, out sum))
{
binidx.Write(sum.pos); //lookup
binidx.Write(sum.length);
binidx.Write(0);
//Tex.WriteLine(System.String.Format("0x{0:X4} -> 0x{1:X4}", sum.index, index));
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, sum.pos, sum.length));
continue;
}
// art
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
binidx.Write((int)binmul.BaseStream.Position); //lookup
var length = (int)binmul.BaseStream.Position;
binmul.Write(1234); // header
binmul.Write((short)bmp.Width);
binmul.Write((short)bmp.Height);
var lookup = (int)binmul.BaseStream.Position;
int streamloc = lookup + bmp.Height * 2;
int width = 0;
for (int i = 0; i < bmp.Height; ++i) // fill lookup
{
binmul.Write(width);
}
int X = 0;
for (int Y = 0; Y < bmp.Height; ++Y, line += delta)
{
ushort* cur = line;
width = (int)(binmul.BaseStream.Position - streamloc) / 2;
binmul.BaseStream.Seek(lookup + Y * 2, SeekOrigin.Begin);
binmul.Write(width);
binmul.BaseStream.Seek(streamloc + width * 2, SeekOrigin.Begin);
int i = 0;
int j = 0;
X = 0;
while (i < bmp.Width)
{
i = X;
for (i = X; i <= bmp.Width; ++i)
{
//first pixel set
if (i < bmp.Width)
{
if (cur[i] != 0)
{
break;
}
}
}
if (i < bmp.Width)
{
for (j = (i + 1); j < bmp.Width; ++j)
{
//next non set pixel
if (cur[j] == 0)
{
break;
}
}
binmul.Write((short)(i - X)); //xoffset
binmul.Write((short)(j - i)); //run
for (int p = i; p < j; ++p)
{
binmul.Write((ushort)(cur[p] ^ 0x8000));
}
X = j;
}
}
binmul.Write((short)0); //xOffset
binmul.Write((short)0); //Run
}
int start = length;
length = (int)binmul.BaseStream.Position - length;
binidx.Write(length);
binidx.Write(0);
bmp.UnlockBits(bd);
var s = new CheckSums
{
pos = start,
length = length,
checksum = checksum,
index = index
};
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, start, length));
checksumsStatic.Add(s);
}
}
memidx.WriteTo(fsidx);
memmul.WriteTo(fsmul);
}
}
}
private static bool compareSaveImagesLand(byte[] newchecksum, out CheckSums sum)
{
sum = new CheckSums();
for (int i = 0; i < checksumsLand.Count; ++i)
{
byte[] cmp = checksumsLand[i].checksum;
if (((cmp == null) || (newchecksum == null)) || (cmp.Length != newchecksum.Length))
{
return false;
}
bool valid = true;
for (int j = 0; j < cmp.Length; ++j)
{
if (cmp[j] != newchecksum[j])
{
valid = false;
break;
}
}
if (valid)
{
sum = checksumsLand[i];
return true;
}
}
return false;
}
private static bool compareSaveImagesStatic(byte[] newchecksum, out CheckSums sum)
{
sum = new CheckSums();
for (int i = 0; i < checksumsStatic.Count; ++i)
{
byte[] cmp = checksumsStatic[i].checksum;
if (((cmp == null) || (newchecksum == null)) || (cmp.Length != newchecksum.Length))
{
return false;
}
bool valid = true;
for (int j = 0; j < cmp.Length; ++j)
{
if (cmp[j] != newchecksum[j])
{
valid = false;
break;
}
}
if (valid)
{
sum = checksumsStatic[i];
return true;
}
}
return false;
}
}
}

62
Ultima/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,62 @@
#region References
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
#endregion
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("UltimaSDK")]
[assembly: AssemblyDescription("The Ultima SDK is a Software Development Kit")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
//
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("2.0.*")]
//
// In order to sign your assembly you must specify a key to use. Refer to the
// Microsoft .NET Framework documentation for more information on assembly signing.
//
// Use the attributes below to control which key is used for signing.
//
// Notes:
// (*) If no key is specified, the assembly is not signed.
// (*) KeyName refers to a key that has been installed in the Crypto Service
// Provider (CSP) on your machine. KeyFile refers to a file which contains
// a key.
// (*) If the KeyFile and the KeyName values are both specified, the
// following processing occurs:
// (1) If the KeyName can be found in the CSP, that key is used.
// (2) If the KeyName does not exist and the KeyFile does exist, the key
// in the KeyFile is installed into the CSP and used.
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
// When specifying the KeyFile, the location of the KeyFile should be
// relative to the project output directory which is
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
// located in the project directory, you would specify the AssemblyKeyFile
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
// documentation for more information on this.
//
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
[assembly: ComVisible(false)]
[assembly: NeutralResourcesLanguage("")]

230
Ultima/CalibrationInfo.cs Normal file
View File

@@ -0,0 +1,230 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
#endregion
namespace Ultima
{
public sealed class CalibrationInfo
{
public byte[] Mask { get; private set; }
public byte[] Vals { get; private set; }
public byte[] DetX { get; private set; }
public byte[] DetY { get; private set; }
public byte[] DetZ { get; private set; }
public byte[] DetF { get; private set; }
public CalibrationInfo(byte[] mask, byte[] vals, byte[] detx, byte[] dety, byte[] detz, byte[] detf)
{
Mask = mask;
Vals = vals;
DetX = detx;
DetY = dety;
DetZ = detz;
DetF = detf;
}
private static byte[] ReadBytes(StreamReader ip)
{
string line = ip.ReadLine();
if (line == null)
{
return null;
}
var buffer = new byte[(line.Length + 2) / 3];
int index = 0;
for (int i = 0; (i + 1) < line.Length; i += 3)
{
char ch = line[i + 0];
char cl = line[i + 1];
if (ch >= '0' && ch <= '9')
{
ch -= '0';
}
else if (ch >= 'a' && ch <= 'f')
{
ch -= (char)('a' - 10);
}
else if (ch >= 'A' && ch <= 'F')
{
ch -= (char)('A' - 10);
}
else
{
return null;
}
if (cl >= '0' && cl <= '9')
{
cl -= '0';
}
else if (cl >= 'a' && cl <= 'f')
{
cl -= (char)('a' - 10);
}
else if (cl >= 'A' && cl <= 'F')
{
cl -= (char)('A' - 10);
}
else
{
return null;
}
buffer[index++] = (byte)((ch << 4) | cl);
}
return buffer;
}
private static CalibrationInfo[] m_DefaultList = new[]
{
new CalibrationInfo(
//Post 7.0.4.0 (Andreew)
new byte[]
{
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF
},
new byte[]
{
0xFF, 0xD0, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x11, 0x8B, 0x82, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xD0, 0x5B, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC
},
new byte[] {0x22, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x0C},
//x
new byte[] {0x22, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x08},
//y
new byte[] {0x22, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x04},
//z
new byte[] {0x22, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x10}), //f
new CalibrationInfo(
/* (arul) 6.0.9.x+ : Calibrates both */
new byte[]
{
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF
},
new byte[]
{
0xFF, 0xD0, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x11, 0x8B, 0x82, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xD0, 0x5E, 0xE9, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x0D
},
new byte[] {0x1F, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x0C},
new byte[] {0x1F, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x08},
new byte[] {0x1F, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x04},
new byte[] {0x1F, 0x04, 0xFF, 0xFF, 0xFF, 0x04, 0x10}),
new CalibrationInfo(
/* Facet */
new byte[] {0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
new byte[] {0xA0, 0x00, 0x00, 0x00, 0x00, 0x84, 0xC0, 0x0F, 0x85, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x0D},
new byte[0],
new byte[0],
new byte[0],
new byte[] {0x01, 0x04, 0xFF, 0xFF, 0xFF, 0x01}),
new CalibrationInfo(
/* Location */
new byte[]
{
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00
},
new byte[]
{
0x8B, 0x15, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0x66, 0x89, 0x5A, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x66,
0x89, 0x78, 0x00, 0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x66, 0x89, 0x71, 0x00
},
new byte[] {0x02, 0x04, 0x04, 0x0C, 0x01, 0x02},
new byte[] {0x0E, 0x04, 0x04, 0x15, 0x01, 0x02},
new byte[] {0x18, 0x04, 0x04, 0x1F, 0x01, 0x02},
new byte[0]),
new CalibrationInfo(
/* UO3D Only, calibrates both */
new byte[]
{
0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00
},
new byte[]
{
0xA1, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x2E, 0x04, 0x01, 0x0F, 0xBF, 0x50, 0x00, 0x0F, 0xBF, 0x48, 0x00, 0x52,
0x51, 0x0F, 0xBF, 0x50, 0x00, 0x52, 0x8D, 0x85, 0xE4, 0xFD, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x50, 0xE8,
0x07, 0x44, 0x10, 0x00, 0x8A, 0x0D, 0x00, 0x00, 0x00, 0x00
},
new byte[] {0x01, 0x04, 0x04, 0x17, 0x01, 0x02},
new byte[] {0x01, 0x04, 0x04, 0x11, 0x01, 0x02},
new byte[] {0x01, 0x04, 0x04, 0x0D, 0x01, 0x02},
new byte[] {0x2C, 0x04, 0xFF, 0xFF, 0xFF, 0x01})
};
public static CalibrationInfo[] DefaultList { get { return m_DefaultList; } set { m_DefaultList = value; } }
public static CalibrationInfo[] GetList()
{
var list = new List<CalibrationInfo>();
string path = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
path = Path.Combine(path, "calibration.cfg");
if (File.Exists(path))
{
using (var ip = new StreamReader(path))
{
string line;
while ((line = ip.ReadLine()) != null)
{
line = line.Trim();
if (line.Equals("Begin", StringComparison.OrdinalIgnoreCase))
{
byte[] mask, vals, detx, dety, detz, detf;
if ((mask = ReadBytes(ip)) == null)
{
continue;
}
if ((vals = ReadBytes(ip)) == null)
{
continue;
}
if ((detx = ReadBytes(ip)) == null)
{
continue;
}
if ((dety = ReadBytes(ip)) == null)
{
continue;
}
if ((detz = ReadBytes(ip)) == null)
{
continue;
}
if ((detf = ReadBytes(ip)) == null)
{
continue;
}
list.Add(new CalibrationInfo(mask, vals, detx, dety, detz, detf));
}
}
}
}
list.AddRange(DefaultList);
return list.ToArray();
}
}
}

495
Ultima/Client.cs Normal file
View File

@@ -0,0 +1,495 @@
#region References
using System;
using System.IO;
#endregion
namespace Ultima
{
/// <summary>
/// Provides methods to interact with the Ultima Online client.
/// </summary>
public sealed class Client
{
private const int WM_CHAR = 0x102;
private static ClientWindowHandle m_Handle = ClientWindowHandle.Invalid;
private static WindowProcessStream m_ProcStream;
private static LocationPointer m_LocationPointer;
private static bool m_Is_Iris2;
private Client()
{ }
/// <summary>
/// Gets a <see cref="ProcessStream" /> instance which can be used to read the memory. Null is returned if the Client is not running.
/// </summary>
public static ProcessStream ProcessStream
{
get
{
if (m_ProcStream == null || m_ProcStream.Window != Handle)
{
if (Running)
{
m_ProcStream = new WindowProcessStream(Handle);
}
else
{
m_ProcStream = null;
}
}
return m_ProcStream;
}
}
/// <summary>
/// Reads the current <paramref name="x" />, <paramref name="y" />, and <paramref name="z" /> from memory based on a
/// <see
/// cref="Calibrate">
/// calibrated memory location
/// </see>
/// .
/// <seealso cref="Calibrate" />
/// <seealso cref="ProcessStream" />
/// <returns>True if the location was found, false if not</returns>
/// </summary>
public static bool FindLocation(ref int x, ref int y, ref int z, ref int facet)
{
LocationPointer lp = LocationPointer;
ProcessStream pc = ProcessStream;
if (pc == null || lp == null)
{
return false;
}
pc.BeginAccess();
if (lp.PointerX > 0)
{
pc.Seek(lp.PointerX, SeekOrigin.Begin);
x = Read(pc, lp.SizeX);
}
if (lp.PointerY > 0)
{
pc.Seek(lp.PointerY, SeekOrigin.Begin);
y = Read(pc, lp.SizeY);
}
if (lp.PointerZ > 0)
{
pc.Seek(lp.PointerZ, SeekOrigin.Begin);
z = Read(pc, lp.SizeZ);
}
if (lp.PointerF > 0)
{
pc.Seek(lp.PointerF, SeekOrigin.Begin);
facet = Read(pc, lp.SizeF);
}
pc.EndAccess();
return true;
}
public static int Read(ProcessStream pc, int bytes)
{
var buffer = new byte[bytes];
pc.Read(buffer, 0, bytes);
switch (bytes)
{
case 1:
return (sbyte)buffer[0];
case 2:
return (short)(buffer[0] | (buffer[1] << 8));
case 4:
return (buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24));
}
int val = 0;
int bits = 0;
for (int i = 0; i < buffer.Length; ++i)
{
val |= buffer[i] << bits;
bits += 8;
}
return val;
}
public static int Search(ProcessStream pc, byte[] mask, byte[] vals)
{
if (mask.Length != vals.Length)
{
throw new Exception();
}
const int chunkSize = 4096;
int readSize = chunkSize + mask.Length;
pc.BeginAccess();
var read = new byte[readSize];
for (int i = 0;; ++i)
{
pc.Seek(0x400000 + (i * chunkSize), SeekOrigin.Begin);
int count = pc.Read(read, 0, readSize);
if (count != readSize)
{
break;
}
for (int j = 0; j < chunkSize; ++j)
{
bool ok = true;
for (int k = 0; ok && k < mask.Length; ++k)
{
ok = ((read[j + k] & mask[k]) == vals[k]);
}
if (ok)
{
pc.EndAccess();
return 0x400000 + (i * chunkSize) + j;
}
}
}
pc.EndAccess();
return 0;
}
public static int Search(ProcessStream pc, byte[] buffer)
{
const int chunkSize = 4096;
int readSize = chunkSize + buffer.Length;
pc.BeginAccess();
var read = new byte[readSize];
for (int i = 0;; ++i)
{
pc.Seek(0x400000 + (i * chunkSize), SeekOrigin.Begin);
int count = pc.Read(read, 0, readSize);
if (count != readSize)
{
break;
}
for (int j = 0; j < chunkSize; ++j)
{
bool ok = true;
for (int k = 0; ok && k < buffer.Length; ++k)
{
ok = (buffer[k] == read[j + k]);
}
if (ok)
{
pc.EndAccess();
return 0x400000 + (i * chunkSize) + j;
}
}
}
pc.EndAccess();
return 0;
}
/// <summary>
/// Attempts to calibrate the <see cref="FindLocation" /> method based on an input <paramref name="x" />,
/// <paramref
/// name="y" />
/// , and <paramref name="z" />.
/// <seealso cref="FindLocation" />
/// <seealso cref="ProcessStream" />
/// </summary>
/// <returns>The calibrated memory location -or- 0 if it could not be found.</returns>
public static void Calibrate(int x, int y, int z)
{
m_LocationPointer = null;
ProcessStream pc = ProcessStream;
if (pc == null)
{
return;
}
var buffer = new byte[12];
buffer[0] = (byte)z;
buffer[1] = (byte)(z >> 8);
buffer[2] = (byte)(z >> 16);
buffer[3] = (byte)(z >> 24);
buffer[4] = (byte)y;
buffer[5] = (byte)(y >> 8);
buffer[6] = (byte)(y >> 16);
buffer[7] = (byte)(y >> 24);
buffer[8] = (byte)x;
buffer[9] = (byte)(x >> 8);
buffer[10] = (byte)(x >> 16);
buffer[11] = (byte)(x >> 24);
int ptr = Search(pc, buffer);
if (ptr == 0)
{
return;
}
m_LocationPointer = new LocationPointer(ptr + 8, ptr + 4, ptr, 0, 4, 4, 4, 0);
}
/// <summary>
/// Attempts to automatically calibrate the <see cref="FindLocation" /> method.
/// </summary>
/// <returns>The calibrated memory location -or- 0 if it could not be found.</returns>
public static void Calibrate()
{
Calibrate(CalibrationInfo.GetList());
}
/// <summary>
/// Attempts to automatically calibrate the <see cref="FindLocation" /> method.
/// </summary>
/// <returns>The calibrated memory location -or- 0 if it could not be found.</returns>
public static void Calibrate(CalibrationInfo[] info)
{
m_LocationPointer = null;
ProcessStream pc = ProcessStream;
if (pc == null)
{
return;
}
int ptrX = 0, sizeX = 0;
int ptrY = 0, sizeY = 0;
int ptrZ = 0, sizeZ = 0;
int ptrF = 0, sizeF = 0;
for (int i = 0; i < info.Length; ++i)
{
CalibrationInfo ci = info[i];
int ptr = Search(pc, ci.Mask, ci.Vals);
if (ptr == 0)
{
continue;
}
if (ptrX == 0 && ci.DetX.Length > 0)
{
GetCoordDetails(pc, ptr, ci.DetX, out ptrX, out sizeX);
}
if (ptrY == 0 && ci.DetY.Length > 0)
{
GetCoordDetails(pc, ptr, ci.DetY, out ptrY, out sizeY);
}
if (ptrZ == 0 && ci.DetZ.Length > 0)
{
GetCoordDetails(pc, ptr, ci.DetZ, out ptrZ, out sizeZ);
}
if (ptrF == 0 && ci.DetF.Length > 0)
{
GetCoordDetails(pc, ptr, ci.DetF, out ptrF, out sizeF);
}
if (ptrX != 0 && ptrY != 0 && ptrZ != 0 && ptrF != 0)
{
break;
}
}
if (ptrX != 0 || ptrY != 0 || ptrZ != 0 || ptrF != 0)
{
m_LocationPointer = new LocationPointer(ptrX, ptrY, ptrZ, ptrF, sizeX, sizeY, sizeZ, sizeF);
}
}
private static void GetCoordDetails(ProcessStream pc, int ptr, byte[] dets, out int coordPointer, out int coordSize)
{
pc.Seek(ptr + dets[0], SeekOrigin.Begin);
coordPointer = Read(pc, dets[1]);
if (dets[2] < 0xFF)
{
pc.Seek(coordPointer, SeekOrigin.Begin);
coordPointer = Read(pc, dets[2]);
}
if (dets[3] < 0xFF)
{
pc.Seek(ptr + dets[3], SeekOrigin.Begin);
coordPointer += Read(pc, dets[4]);
}
/*
* arul:
* The variable 'dets[6]' represents an offset into the struct that holds an info about players current location.
* Added not to break functionality with the older clients (I hope).
*
* The struct looks as follows:
*
* DWORD fLoggedIn;
* DWORD Z;
* DWORD Y;
* DWORD X;
* DWORD Facet;
*
*/
if (dets.Length == 7 && dets[6] < 0xFF)
{
coordPointer += dets[6];
}
coordSize = dets[5];
}
/// <summary>
/// Gets or sets the memory location currently used for the <see cref="FindLocation" /> method.
/// <seealso cref="FindLocation" />
/// <seealso cref="Calibrate" />
/// </summary>
public static LocationPointer LocationPointer { get { return m_LocationPointer; } set { m_LocationPointer = value; } }
/// <summary>
/// Gets the current window handle. A value of <c>ClientHandle.Invalid</c> is returned if the Client is not currently running.
/// <seealso cref="Running" />
/// </summary>
public static ClientWindowHandle Handle
{
get
{
if (NativeMethods.IsWindow(m_Handle) == 0)
{
m_Handle = FindHandle();
}
return m_Handle;
}
}
/// <summary>
/// Whether or not the Client is currently running.
/// <seealso cref="ClientHandle" />
/// </summary>
public static bool Running { get { return (!Handle.IsInvalid); } }
/// <summary>
/// Is Client Iris2
/// </summary>
public static bool Is_Iris2 { get { return m_Is_Iris2; } set { m_Is_Iris2 = value; } }
private static void SendChar(ClientWindowHandle hWnd, char c)
{
int value = c;
int lParam = 1 | ((NativeMethods.OemKeyScan(value) & 0xFF) << 16) | (0x3 << 30);
NativeMethods.PostMessage(hWnd, WM_CHAR, value, lParam);
}
/// <summary>
/// Brings the Client window to the foreground.
/// </summary>
/// <returns>True if the Client is running, false if not.</returns>
public static bool BringToTop()
{
ClientWindowHandle hWnd = Handle;
if (!hWnd.IsInvalid)
{
NativeMethods.SetForegroundWindow(hWnd);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Sends a <see cref="string" /> of characters (<paramref name="text" />) to the Client. The string is followed by a carriage return and line feed.
/// </summary>
/// <returns>True if the Client is running, false if not.</returns>
public static bool SendText(string text)
{
ClientWindowHandle hWnd = Handle;
if (!hWnd.IsInvalid)
{
for (int i = 0; i < text.Length; ++i)
{
SendChar(hWnd, text[i]);
}
SendChar(hWnd, '\r');
SendChar(hWnd, '\n');
return true;
}
else
{
return false;
}
}
/// <summary>
/// Sends a formatted <see cref="string" /> of characters to the Client. The string is followed by a carriage return and line feed. The format functionality is the same as
/// <see
/// cref="string.Format">
/// String.Format
/// </see>
/// .
/// </summary>
/// <returns>True if the Client is running, false if not.</returns>
public static bool SendText(string format, params object[] args)
{
return SendText(String.Format(format, args));
}
private static ClientWindowHandle FindHandle()
{
ClientWindowHandle hWnd;
if (NativeMethods.IsWindow(hWnd = NativeMethods.FindWindowA("Ultima Online", null)) != 0)
{
return hWnd;
}
if (NativeMethods.IsWindow(hWnd = NativeMethods.FindWindowA("Ultima Online Third Dawn", null)) != 0)
{
return hWnd;
}
if (NativeMethods.IsWindow(hWnd = NativeMethods.FindWindowA("OgreGLWindow", null)) != 0)
{
m_Is_Iris2 = true;
return hWnd;
}
return ClientWindowHandle.Invalid;
}
}
}

48
Ultima/ClientHandles.cs Normal file
View File

@@ -0,0 +1,48 @@
#region References
using System;
using Microsoft.Win32.SafeHandles;
#endregion
namespace Ultima
{
public class ClientWindowHandle : CriticalHandleZeroOrMinusOneIsInvalid
{
public static ClientWindowHandle Invalid = new ClientWindowHandle(new IntPtr(-1));
public ClientWindowHandle()
{ }
public ClientWindowHandle(IntPtr value)
{
handle = value;
}
protected override bool ReleaseHandle()
{
if (!IsClosed)
{
return ReleaseHandle();
}
return true;
}
}
public class ClientProcessHandle : CriticalHandleZeroOrMinusOneIsInvalid
{
public static ClientProcessHandle Invalid = new ClientProcessHandle(new IntPtr(-1));
public ClientProcessHandle()
{ }
public ClientProcessHandle(IntPtr value)
{
handle = value;
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(this) == 0;
}
}
}

564
Ultima/FileIndex.cs Normal file
View File

@@ -0,0 +1,564 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
#endregion
namespace Ultima
{
public sealed class FileIndex
{
public Entry3D[] Index { get; private set; }
public Stream Stream { get; private set; }
public long IdxLength { get; private set; }
private readonly string MulPath;
public Stream Seek(int index, out int length, out int extra, out bool patched)
{
if (index < 0 || index >= Index.Length)
{
length = extra = 0;
patched = false;
return null;
}
Entry3D e = Index[index];
if (e.lookup < 0)
{
length = extra = 0;
patched = false;
return null;
}
length = e.length & 0x7FFFFFFF;
extra = e.extra;
if ((e.length & (1 << 31)) != 0)
{
patched = true;
Verdata.Seek(e.lookup);
return Verdata.Stream;
}
if (e.length < 0)
{
length = extra = 0;
patched = false;
return null;
}
if ((Stream == null) || (!Stream.CanRead) || (!Stream.CanSeek))
{
if (MulPath == null)
{
Stream = null;
}
else
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
}
}
if (Stream == null)
{
length = extra = 0;
patched = false;
return null;
}
else if (Stream.Length < e.lookup)
{
length = extra = 0;
patched = false;
return null;
}
patched = false;
Stream.Seek(e.lookup, SeekOrigin.Begin);
return Stream;
}
public bool Valid(int index, out int length, out int extra, out bool patched)
{
if (index < 0 || index >= Index.Length)
{
length = extra = 0;
patched = false;
return false;
}
Entry3D e = Index[index];
if (e.lookup < 0)
{
length = extra = 0;
patched = false;
return false;
}
length = e.length & 0x7FFFFFFF;
extra = e.extra;
if ((e.length & (1 << 31)) != 0)
{
patched = true;
return true;
}
if (e.length < 0)
{
length = extra = 0;
patched = false;
return false;
}
if ((MulPath == null) || !File.Exists(MulPath))
{
length = extra = 0;
patched = false;
return false;
}
if ((Stream == null) || (!Stream.CanRead) || (!Stream.CanSeek))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
if (Stream.Length < e.lookup)
{
length = extra = 0;
patched = false;
return false;
}
patched = false;
return true;
}
public FileIndex(string idxFile, string mulFile, int length, int file)
: this(idxFile, mulFile, null, length, file, ".dat", -1, false)
{ }
public FileIndex(
string idxFile,
string mulFile,
string uopFile,
int length,
int file,
string uopEntryExtension,
int idxLength,
bool hasExtra)
{
Index = new Entry3D[length];
string idxPath = null;
MulPath = null;
string uopPath = null;
if (Files.MulPath == null)
{
Files.LoadMulPath();
}
if (Files.MulPath.Count > 0)
{
idxPath = Files.MulPath[idxFile.ToLower()];
MulPath = Files.MulPath[mulFile.ToLower()];
if (!String.IsNullOrEmpty(uopFile) && Files.MulPath.ContainsKey(uopFile.ToLower()))
{
uopPath = Files.MulPath[uopFile.ToLower()];
}
if (String.IsNullOrEmpty(idxPath))
{
idxPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(idxPath)))
{
idxPath = Path.Combine(Files.RootDir, idxPath);
}
if (!File.Exists(idxPath))
{
idxPath = null;
}
}
if (String.IsNullOrEmpty(MulPath))
{
MulPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(MulPath)))
{
MulPath = Path.Combine(Files.RootDir, MulPath);
}
if (!File.Exists(MulPath))
{
MulPath = null;
}
}
if (String.IsNullOrEmpty(uopPath))
{
uopPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(uopPath)))
{
uopPath = Path.Combine(Files.RootDir, uopPath);
}
if (!File.Exists(uopPath))
{
uopPath = null;
}
else
{
MulPath = uopPath;
}
}
}
/* UOP files support code, written by Wyatt (c) www.ruosi.org
* idxLength variable was added for compatibility with legacy code for art (see art.cs)
* At the moment the only UOP file having entries with extra field is gumpartlegacy.uop,
* and it's two dwords in the beginning of the entry.
* It's possible that UOP can include some entries with unknown hash: not really unknown for me, but
* not useful for reading legacy entries. That's why i removed unknown hash exception throwing from this code
*/
if (MulPath != null && MulPath.EndsWith(".uop"))
{
using (var index = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var fi = new FileInfo(MulPath);
string uopPattern = fi.Name.Replace(fi.Extension, "").ToLowerInvariant();
using (var br = new BinaryReader(Stream))
{
br.BaseStream.Seek(0, SeekOrigin.Begin);
if (br.ReadInt32() != 0x50594D)
{
throw new ArgumentException("Bad UOP file.");
}
br.ReadInt64(); // version + signature
long nextBlock = br.ReadInt64();
br.ReadInt32(); // block capacity
int count = br.ReadInt32();
if (idxLength > 0)
{
IdxLength = idxLength * 12;
}
var hashes = new Dictionary<ulong, int>();
for (int i = 0; i < length; i++)
{
string entryName = string.Format("build/{0}/{1:D8}{2}", uopPattern, i, uopEntryExtension);
ulong hash = HashFileName(entryName);
if (!hashes.ContainsKey(hash))
{
hashes.Add(hash, i);
}
}
br.BaseStream.Seek(nextBlock, SeekOrigin.Begin);
do
{
int filesCount = br.ReadInt32();
nextBlock = br.ReadInt64();
for (int i = 0; i < filesCount; i++)
{
long offset = br.ReadInt64();
int headerLength = br.ReadInt32();
int compressedLength = br.ReadInt32();
int decompressedLength = br.ReadInt32();
ulong hash = br.ReadUInt64();
br.ReadUInt32(); // Adler32
short flag = br.ReadInt16();
int entryLength = flag == 1 ? compressedLength : decompressedLength;
if (offset == 0)
{
continue;
}
int idx;
if (hashes.TryGetValue(hash, out idx))
{
if (idx < 0 || idx > Index.Length)
{
throw new IndexOutOfRangeException("hashes dictionary and files collection have different count of entries!");
}
Index[idx].lookup = (int)(offset + headerLength);
Index[idx].length = entryLength;
if (hasExtra)
{
long curPos = br.BaseStream.Position;
br.BaseStream.Seek(offset + headerLength, SeekOrigin.Begin);
byte[] extra = br.ReadBytes(8);
var extra1 = (ushort)((extra[3] << 24) | (extra[2] << 16) | (extra[1] << 8) | extra[0]);
var extra2 = (ushort)((extra[7] << 24) | (extra[6] << 16) | (extra[5] << 8) | extra[4]);
Index[idx].lookup += 8;
Index[idx].extra = extra1 << 16 | extra2;
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
}
}
}
while (br.BaseStream.Seek(nextBlock, SeekOrigin.Begin) != 0);
}
}
}
else if ((idxPath != null) && (MulPath != null))
{
using (var index = new FileStream(idxPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var count = (int)(index.Length / 12);
IdxLength = index.Length;
GCHandle gc = GCHandle.Alloc(Index, GCHandleType.Pinned);
var buffer = new byte[index.Length];
index.Read(buffer, 0, (int)index.Length);
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)Math.Min(IdxLength, length * 12));
gc.Free();
for (int i = count; i < length; ++i)
{
Index[i].lookup = -1;
Index[i].length = -1;
Index[i].extra = -1;
}
}
}
else
{
Stream = null;
return;
}
Entry5D[] patches = Verdata.Patches;
if (file > -1)
{
for (int i = 0; i < patches.Length; ++i)
{
Entry5D patch = patches[i];
if (patch.file == file && patch.index >= 0 && patch.index < length)
{
Index[patch.index].lookup = patch.lookup;
Index[patch.index].length = patch.length | (1 << 31);
Index[patch.index].extra = patch.extra;
}
}
}
}
public FileIndex(string idxFile, string mulFile, int file)
{
string idxPath = null;
MulPath = null;
if (Files.MulPath == null)
{
Files.LoadMulPath();
}
if (Files.MulPath.Count > 0)
{
idxPath = Files.MulPath[idxFile.ToLower()];
MulPath = Files.MulPath[mulFile.ToLower()];
if (String.IsNullOrEmpty(idxPath))
{
idxPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(idxPath)))
{
idxPath = Path.Combine(Files.RootDir, idxPath);
}
if (!File.Exists(idxPath))
{
idxPath = null;
}
}
if (String.IsNullOrEmpty(MulPath))
{
MulPath = null;
}
else
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(MulPath)))
{
MulPath = Path.Combine(Files.RootDir, MulPath);
}
if (!File.Exists(MulPath))
{
MulPath = null;
}
}
}
if ((idxPath != null) && (MulPath != null))
{
using (var index = new FileStream(idxPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Stream = new FileStream(MulPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var count = (int)(index.Length / 12);
IdxLength = index.Length;
Index = new Entry3D[count];
GCHandle gc = GCHandle.Alloc(Index, GCHandleType.Pinned);
var buffer = new byte[index.Length];
index.Read(buffer, 0, (int)index.Length);
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)index.Length);
gc.Free();
}
}
else
{
Stream = null;
Index = new Entry3D[1];
return;
}
Entry5D[] patches = Verdata.Patches;
if (file > -1)
{
for (int i = 0; i < patches.Length; ++i)
{
Entry5D patch = patches[i];
if (patch.file == file && patch.index >= 0 && patch.index < Index.Length)
{
Index[patch.index].lookup = patch.lookup;
Index[patch.index].length = patch.length | (1 << 31);
Index[patch.index].extra = patch.extra;
}
}
}
}
/// <summary>
/// Method for calculating entry hash by it's name.
/// Taken from Mythic.Package.dll
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static ulong HashFileName(string s)
{
uint eax, ecx, edx, ebx, esi, edi;
eax = ecx = edx = ebx = esi = edi = 0;
ebx = edi = esi = (uint)s.Length + 0xDEADBEEF;
int i = 0;
for (i = 0; i + 12 < s.Length; i += 12)
{
edi = (uint)((s[i + 7] << 24) | (s[i + 6] << 16) | (s[i + 5] << 8) | s[i + 4]) + edi;
esi = (uint)((s[i + 11] << 24) | (s[i + 10] << 16) | (s[i + 9] << 8) | s[i + 8]) + esi;
edx = (uint)((s[i + 3] << 24) | (s[i + 2] << 16) | (s[i + 1] << 8) | s[i]) - esi;
edx = (edx + ebx) ^ (esi >> 28) ^ (esi << 4);
esi += edi;
edi = (edi - edx) ^ (edx >> 26) ^ (edx << 6);
edx += esi;
esi = (esi - edi) ^ (edi >> 24) ^ (edi << 8);
edi += edx;
ebx = (edx - esi) ^ (esi >> 16) ^ (esi << 16);
esi += edi;
edi = (edi - ebx) ^ (ebx >> 13) ^ (ebx << 19);
ebx += esi;
esi = (esi - edi) ^ (edi >> 28) ^ (edi << 4);
edi += ebx;
}
if (s.Length - i > 0)
{
switch (s.Length - i)
{
case 12:
esi += (uint)s[i + 11] << 24;
goto case 11;
case 11:
esi += (uint)s[i + 10] << 16;
goto case 10;
case 10:
esi += (uint)s[i + 9] << 8;
goto case 9;
case 9:
esi += s[i + 8];
goto case 8;
case 8:
edi += (uint)s[i + 7] << 24;
goto case 7;
case 7:
edi += (uint)s[i + 6] << 16;
goto case 6;
case 6:
edi += (uint)s[i + 5] << 8;
goto case 5;
case 5:
edi += s[i + 4];
goto case 4;
case 4:
ebx += (uint)s[i + 3] << 24;
goto case 3;
case 3:
ebx += (uint)s[i + 2] << 16;
goto case 2;
case 2:
ebx += (uint)s[i + 1] << 8;
goto case 1;
case 1:
ebx += s[i];
break;
}
esi = (esi ^ edi) - ((edi >> 18) ^ (edi << 14));
ecx = (esi ^ ebx) - ((esi >> 21) ^ (esi << 11));
edi = (edi ^ ecx) - ((ecx >> 7) ^ (ecx << 25));
esi = (esi ^ edi) - ((edi >> 16) ^ (edi << 16));
edx = (esi ^ ecx) - ((esi >> 28) ^ (esi << 4));
edi = (edi ^ edx) - ((edx >> 18) ^ (edx << 14));
eax = (esi ^ edi) - ((edi >> 8) ^ (edi << 24));
return ((ulong)edi << 32) | eax;
}
return ((ulong)esi << 32) | eax;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Entry3D
{
public int lookup;
public int length;
public int extra;
}
}

410
Ultima/Files.cs Normal file
View File

@@ -0,0 +1,410 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using Microsoft.Win32;
#endregion
namespace Ultima
{
public sealed class Files
{
public delegate void FileSaveHandler();
public static event FileSaveHandler FileSaveEvent;
public static void FireFileSaveEvent()
{
if (FileSaveEvent != null)
{
FileSaveEvent();
}
}
private static bool m_CacheData = true;
private static Dictionary<string, string> m_MulPath;
private static string m_Directory;
private static string m_RootDir;
/// <summary>
/// Should loaded Data be cached
/// </summary>
public static bool CacheData { get { return m_CacheData; } set { m_CacheData = value; } }
/// <summary>
/// Should a Hashfile be used to speed up loading
/// </summary>
public static bool UseHashFile { get; set; }
/// <summary>
/// Contains the path infos
/// </summary>
public static Dictionary<string, string> MulPath { get { return m_MulPath; } set { m_MulPath = value; } }
/// <summary>
/// Gets a list of paths to the Client's data files.
/// </summary>
public static string Directory { get { return m_Directory; } }
/// <summary>
/// Contains the rootDir (so relative values are possible for <see cref="MulPath" />
/// </summary>
public static string RootDir { get { return m_RootDir; } set { m_RootDir = value; } }
private static readonly string[] m_Files = new[]
{
"anim.idx", "anim.mul", "anim2.idx", "anim2.mul", "anim3.idx", "anim3.mul", "anim4.idx", "anim4.mul", "anim5.idx",
"anim5.mul", "animdata.mul", "art.mul", "artidx.mul", "artlegacymul.uop", "body.def", "bodyconv.def", "client.exe",
"cliloc.custom1", "cliloc.custom2", "cliloc.deu", "cliloc.enu", "equipconv.def", "facet00.mul", "facet01.mul",
"facet02.mul", "facet03.mul", "facet04.mul", "facet05.mul", "fonts.mul", "gump.def", "gumpart.mul", "gumpidx.mul",
"gumpartlegacymul.uop", "hues.mul", "light.mul", "lightidx.mul", "map0.mul", "map1.mul", "map2.mul", "map3.mul",
"map4.mul", "map5.mul", "map0legacymul.uop", "map1legacymul.uop", "map2legacymul.uop", "map3legacymul.uop",
"map4legacymul.uop", "map5legacymul.uop", "mapdif0.mul", "mapdif1.mul", "mapdif2.mul", "mapdif3.mul", "mapdif4.mul",
"mapdifl0.mul", "mapdifl1.mul", "mapdifl2.mul", "mapdifl3.mul", "mapdifl4.mul", "mobtypes.txt", "multi.idx",
"multi.mul", "multimap.rle", "radarcol.mul", "skillgrp.mul", "skills.idx", "skills.mul", "sound.def", "sound.mul",
"soundidx.mul", "soundlegacymul.uop", "speech.mul", "stadif0.mul", "stadif1.mul", "stadif2.mul", "stadif3.mul",
"stadif4.mul", "stadifi0.mul", "stadifi1.mul", "stadifi2.mul", "stadifi3.mul", "stadifi4.mul", "stadifl0.mul",
"stadifl1.mul", "stadifl2.mul", "stadifl3.mul", "stadifl4.mul", "staidx0.mul", "staidx1.mul", "staidx2.mul",
"staidx3.mul", "staidx4.mul", "staidx5.mul", "statics0.mul", "statics1.mul", "statics2.mul", "statics3.mul",
"statics4.mul", "statics5.mul", "texidx.mul", "texmaps.mul", "tiledata.mul", "unifont.mul", "unifont1.mul",
"unifont2.mul", "unifont3.mul", "unifont4.mul", "unifont5.mul", "unifont6.mul", "unifont7.mul", "unifont8.mul",
"unifont9.mul", "unifont10.mul", "unifont11.mul", "unifont12.mul", "uotd.exe", "verdata.mul"
};
static Files()
{
m_Directory = LoadDirectory();
LoadMulPath();
}
/// <summary>
/// ReReads Registry Client dir
/// </summary>
public static void ReLoadDirectory()
{
m_Directory = LoadDirectory();
}
/// <summary>
/// Fills <see cref="MulPath" /> with <see cref="Files.Directory" />
/// </summary>
public static void LoadMulPath()
{
m_MulPath = new Dictionary<string, string>();
m_RootDir = Directory;
if (m_RootDir == null)
{
m_RootDir = "";
}
foreach (string file in m_Files)
{
string filePath = Path.Combine(m_RootDir, file);
if (File.Exists(filePath))
{
m_MulPath[file] = file;
}
else
{
m_MulPath[file] = "";
}
}
}
/// <summary>
/// ReSets <see cref="MulPath" /> with given path
/// </summary>
/// <param name="path"></param>
public static void SetMulPath(string path)
{
m_RootDir = path;
foreach (string file in m_Files)
{
string filePath;
if (!String.IsNullOrEmpty(m_MulPath[file])) //file was set
{
if (String.IsNullOrEmpty(Path.GetDirectoryName(m_MulPath[file]))) //and relative
{
filePath = Path.Combine(m_RootDir, m_MulPath[file]);
if (File.Exists(filePath)) // exists in new Root?
{
m_MulPath[file] = filePath;
continue;
}
}
else // absolut dir ignore
{
continue;
}
}
filePath = Path.Combine(m_RootDir, file); //file was not set, or relative and non existent
if (File.Exists(filePath))
{
m_MulPath[file] = file;
}
else
{
m_MulPath[file] = "";
}
}
}
/// <summary>
/// Sets <see cref="MulPath" /> key to path
/// </summary>
/// <param name="path"></param>
/// <param name="key"></param>
public static void SetMulPath(string path, string key)
{
MulPath[key] = path;
}
/// <summary>
/// Looks up a given <paramref name="file" /> in <see cref="Files.MulPath" />
/// </summary>
/// <returns>
/// The absolute path to <paramref name="file" /> -or- <c>null</c> if <paramref name="file" /> was not found.
/// </returns>
public static string GetFilePath(string file)
{
if (MulPath.Count > 0)
{
string path = "";
if (MulPath.ContainsKey(file.ToLower()))
{
path = MulPath[file.ToLower()];
}
if (String.IsNullOrEmpty(path))
{
return null;
}
if (String.IsNullOrEmpty(Path.GetDirectoryName(path)))
{
path = Path.Combine(m_RootDir, path);
}
if (File.Exists(path))
{
return path;
}
}
return null;
}
internal static string GetFilePath(string format, params object[] args)
{
return GetFilePath(String.Format(format, args));
}
private static readonly string[] knownRegkeys = new[]
{
@"Electronic Arts\EA Games\Ultima Online Classic", @"Electronic Arts\EA Games\Ultima Online Stygian Abyss Classic",
@"Origin Worlds Online\Ultima Online\KR Legacy Beta", @"Origin Worlds Online\Ultima Online Samurai Empire\3d\1.0",
@"Origin Worlds Online\Ultima Online Samurai Empire\2d\1.0",
@"Origin Worlds Online\Ultima Online Samurai Empire BETA\3d\1.0",
@"Origin Worlds Online\Ultima Online Samurai Empire BETA\2d\1.0",
@"EA Games\Ultima Online: Mondain's Legacy\1.0", @"EA Games\Ultima Online: Mondain's Legacy\1.00.0000",
@"EA GAMES\Ultima Online: Samurai Empire\1.00.0000", @"EA Games\Ultima Online: Mondain's Legacy",
@"EA GAMES\Ultima Online Samurai Empire\1.00.0000", @"EA GAMES\Ultima Online: Samurai Empire\1.0",
@"EA GAMES\Ultima Online Samurai Empire", @"EA GAMES\Ultima Online Samurai Empire\1.0",
@"Origin Worlds Online\Ultima Online\1.0", @"Origin Worlds Online\Ultima Online Third Dawn\1.0",
};
private static readonly string[] knownRegPathkeys = new[] {"ExePath", "Install Dir", "InstallDir"};
public static string LoadDirectory()
{
string dir = null;
for (int i = knownRegkeys.Length - 1; i >= 0; i--)
{
string exePath;
if (Environment.Is64BitOperatingSystem)
{
exePath = GetPath(string.Format(@"Wow6432Node\{0}", knownRegkeys[i]));
}
else
{
exePath = GetPath(knownRegkeys[i]);
}
if (exePath != null)
{
dir = exePath;
break;
}
}
return dir;
}
private static string GetPath(string regkey)
{
try
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(string.Format(@"SOFTWARE\{0}", regkey));
if (key == null)
{
key = Registry.CurrentUser.OpenSubKey(string.Format(@"SOFTWARE\{0}", regkey));
if (key == null)
{
return null;
}
}
string path = null;
foreach (string pathkey in knownRegPathkeys)
{
path = key.GetValue(pathkey) as string;
if ((path == null) || (path.Length <= 0))
{
continue;
}
if (pathkey == "InstallDir")
{
path = path + @"\";
}
if (!System.IO.Directory.Exists(path) && !File.Exists(path))
{
continue;
}
break;
}
if (path == null)
{
return null;
}
if (!System.IO.Directory.Exists(path))
{
path = Path.GetDirectoryName(path);
}
if ((path == null) || (!System.IO.Directory.Exists(path)))
{
return null;
}
return path;
}
catch
{
return null;
}
}
/// <summary>
/// Compares given MD5 hash with hash of given file
/// </summary>
/// <param name="file"></param>
/// <param name="hash"></param>
/// <returns></returns>
public static bool CompareMD5(string file, string hash)
{
if (file == null)
{
return false;
}
FileStream FileCheck = File.OpenRead(file);
using (MD5 md5 = new MD5CryptoServiceProvider())
{
byte[] md5Hash = md5.ComputeHash(FileCheck);
FileCheck.Close();
string md5string = BitConverter.ToString(md5Hash).Replace("-", "").ToLower();
if (md5string == hash)
{
return true;
}
else
{
return false;
}
}
}
/// <summary>
/// Returns MD5 hash from given file
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static byte[] GetMD5(string file)
{
if (file == null)
{
return null;
}
FileStream FileCheck = File.OpenRead(file);
using (MD5 md5 = new MD5CryptoServiceProvider())
{
byte[] md5Hash = md5.ComputeHash(FileCheck);
FileCheck.Close();
return md5Hash;
}
}
/// <summary>
/// Compares MD5 hash from given mul file with hash in responsible hash-file
/// </summary>
/// <param name="what"></param>
/// <returns></returns>
public static bool CompareHashFile(string what, string path)
{
string FileName = Path.Combine(path, String.Format("UOFiddler{0}.hash", what));
if (File.Exists(FileName))
{
try
{
using (var bin = new BinaryReader(new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
int length = bin.ReadInt32();
var buffer = new byte[length];
bin.Read(buffer, 0, length);
string hashold = BitConverter.ToString(buffer).Replace("-", "").ToLower();
return CompareMD5(GetFilePath(String.Format("{0}.mul", what)), hashold);
}
}
catch
{
return false;
}
}
return false;
}
/// <summary>
/// Checks if map1.mul exists and sets <see cref="Ultima.Map" />
/// </summary>
public static void CheckForNewMapSize()
{
if (GetFilePath("map1.mul") != null)
{
if (Map.Trammel.Width == 7168)
{
Map.Trammel = new Map(1, 1, 7168, 4096);
}
else
{
Map.Trammel = new Map(1, 1, 6144, 4096);
}
}
else
{
if (Map.Trammel.Width == 7168)
{
Map.Trammel = new Map(0, 1, 7168, 4096);
}
else
{
Map.Trammel = new Map(0, 1, 6144, 4096);
}
}
}
}
}

531
Ultima/Gumps.cs Normal file
View File

@@ -0,0 +1,531 @@
#region References
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
#endregion
namespace Ultima
{
public sealed class Gumps
{
private static FileIndex m_FileIndex = new FileIndex(
"Gumpidx.mul", "Gumpart.mul", "gumpartLegacyMUL.uop", 0xFFFF, 12, ".tga", -1, true);
private static Bitmap[] m_Cache;
private static bool[] m_Removed;
private static readonly Hashtable m_patched = new Hashtable();
private static byte[] m_PixelBuffer;
private static byte[] m_StreamBuffer;
private static byte[] m_ColorTable;
static Gumps()
{
if (m_FileIndex != null)
{
m_Cache = new Bitmap[m_FileIndex.Index.Length];
m_Removed = new bool[m_FileIndex.Index.Length];
}
else
{
m_Cache = new Bitmap[0xFFFF];
m_Removed = new bool[0xFFFF];
}
}
/// <summary>
/// ReReads gumpart
/// </summary>
public static void Reload()
{
try
{
m_FileIndex = new FileIndex("Gumpidx.mul", "Gumpart.mul", "gumpartLegacyMUL.uop", 12, -1, ".tga", -1, true);
m_Cache = new Bitmap[m_FileIndex.Index.Length];
m_Removed = new bool[m_FileIndex.Index.Length];
}
catch
{
m_FileIndex = null;
m_Cache = new Bitmap[0xFFFF];
m_Removed = new bool[0xFFFF];
}
m_PixelBuffer = null;
m_StreamBuffer = null;
m_ColorTable = null;
m_patched.Clear();
}
public static int GetCount()
{
return m_Cache.Length;
}
/// <summary>
/// Replaces Gump <see cref="m_Cache" />
/// </summary>
/// <param name="index"></param>
/// <param name="bmp"></param>
public static void ReplaceGump(int index, Bitmap bmp)
{
m_Cache[index] = bmp;
m_Removed[index] = false;
if (m_patched.Contains(index))
{
m_patched.Remove(index);
}
}
/// <summary>
/// Removes Gumpindex <see cref="m_Removed" />
/// </summary>
/// <param name="index"></param>
public static void RemoveGump(int index)
{
m_Removed[index] = true;
}
/// <summary>
/// Tests if index is definied
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static bool IsValidIndex(int index)
{
if (m_FileIndex == null)
{
return false;
}
if (index > m_Cache.Length - 1)
{
return false;
}
if (m_Removed[index])
{
return false;
}
if (m_Cache[index] != null)
{
return true;
}
int length, extra;
bool patched;
if (!m_FileIndex.Valid(index, out length, out extra, out patched))
{
return false;
}
if (extra == -1)
{
return false;
}
int width = (extra >> 16) & 0xFFFF;
int height = extra & 0xFFFF;
if (width <= 0 || height <= 0)
{
return false;
}
return true;
}
public static byte[] GetRawGump(int index, out int width, out int height)
{
width = -1;
height = -1;
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (extra == -1)
{
return null;
}
width = (extra >> 16) & 0xFFFF;
height = extra & 0xFFFF;
if (width <= 0 || height <= 0)
{
return null;
}
var buffer = new byte[length];
stream.Read(buffer, 0, length);
stream.Close();
return buffer;
}
/// <summary>
/// Returns Bitmap of index and applies Hue
/// </summary>
/// <param name="index"></param>
/// <param name="hue"></param>
/// <param name="onlyHueGrayPixels"></param>
/// <returns></returns>
public static unsafe Bitmap GetGump(int index, Hue hue, bool onlyHueGrayPixels, out bool patched)
{
int length, extra;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (extra == -1)
{
stream.Close();
return null;
}
int width = (extra >> 16) & 0xFFFF;
int height = extra & 0xFFFF;
if (width <= 0 || height <= 0)
{
stream.Close();
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)
{
var pHueColors = (ushort*)psHueColors;
ushort* pHueColorsEnd = pHueColors + 32;
var pColorTable = (ushort*)pbColorTable;
ushort* pColorTableOpaque = pColorTable;
while (pHueColors < pHueColorsEnd)
{
*pColorTableOpaque++ = *pHueColors++;
}
var pPixelDataStart = (ushort*)pbPixels;
var 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;
}
}
stream.Close();
return new Bitmap(width, height, bytesPerStride, Settings.PixelFormat, (IntPtr)pPixelDataStart);
}
}
}
}
}
/// <summary>
/// Returns Bitmap of index
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static Bitmap GetGump(int index)
{
bool patched;
return GetGump(index, out patched);
}
/// <summary>
/// Returns Bitmap of index and if verdata patched
/// </summary>
/// <param name="index"></param>
/// <param name="patched"></param>
/// <returns></returns>
public static unsafe Bitmap GetGump(int index, out bool patched)
{
if (m_patched.Contains(index))
{
patched = (bool)m_patched[index];
}
else
{
patched = false;
}
if (index > m_Cache.Length - 1)
{
return null;
}
if (m_Removed[index])
{
return null;
}
if (m_Cache[index] != null)
{
return m_Cache[index];
}
int length, extra;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (extra == -1)
{
stream.Close();
return null;
}
if (patched)
{
m_patched[index] = true;
}
int width = (extra >> 16) & 0xFFFF;
int height = extra & 0xFFFF;
if (width <= 0 || height <= 0)
{
return null;
}
var bmp = new Bitmap(width, height, Settings.PixelFormat);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, Settings.PixelFormat);
if (m_StreamBuffer == null || m_StreamBuffer.Length < length)
{
m_StreamBuffer = new byte[length];
}
stream.Read(m_StreamBuffer, 0, length);
fixed (byte* data = m_StreamBuffer)
{
var lookup = (int*)data;
var dat = (ushort*)data;
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
int count = 0;
for (int y = 0; y < height; ++y, line += delta)
{
count = (*lookup++ * 2);
ushort* cur = line;
ushort* end = line + bd.Width;
while (cur < end)
{
ushort color = dat[count++];
ushort* next = cur + dat[count++];
if (color == 0)
{
cur = next;
}
else
{
color ^= 0x8000;
while (cur < next)
{
*cur++ = color;
}
}
}
}
}
bmp.UnlockBits(bd);
if (Files.CacheData)
{
return m_Cache[index] = bmp;
}
else
{
return bmp;
}
}
public static unsafe void Save(string path)
{
string idx = Path.Combine(path, "Gumpidx.mul");
string mul = Path.Combine(path, "Gumpart.mul");
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (BinaryWriter binidx = new BinaryWriter(fsidx), binmul = new BinaryWriter(fsmul))
{
for (int index = 0; index < m_Cache.Length; index++)
{
if (m_Cache[index] == null)
{
m_Cache[index] = GetGump(index);
}
Bitmap bmp = m_Cache[index];
if ((bmp == null) || (m_Removed[index]))
{
binidx.Write(-1); // lookup
binidx.Write(-1); // length
binidx.Write(-1); // extra
}
else
{
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
binidx.Write((int)fsmul.Position); //lookup
var length = (int)fsmul.Position;
int fill = 0;
for (int i = 0; i < bmp.Height; ++i)
{
binmul.Write(fill);
}
for (int Y = 0; Y < bmp.Height; ++Y, line += delta)
{
ushort* cur = line;
int X = 0;
var current = (int)fsmul.Position;
fsmul.Seek(length + Y * 4, SeekOrigin.Begin);
int offset = (current - length) / 4;
binmul.Write(offset);
fsmul.Seek(length + offset * 4, SeekOrigin.Begin);
while (X < bd.Width)
{
int Run = 1;
ushort c = cur[X];
while ((X + Run) < bd.Width)
{
if (c != cur[X + Run])
{
break;
}
++Run;
}
if (c == 0)
{
binmul.Write(c);
}
else
{
binmul.Write((ushort)(c ^ 0x8000));
}
binmul.Write((short)Run);
X += Run;
}
}
length = (int)fsmul.Position - length;
binidx.Write(length);
binidx.Write((bmp.Width << 16) + bmp.Height);
bmp.UnlockBits(bd);
}
}
}
}
}
}
}

464
Ultima/Hues.cs Normal file
View File

@@ -0,0 +1,464 @@
#region References
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
#endregion
namespace Ultima
{
public sealed class Hues
{
private static int[] m_Header;
public static Hue[] List { get; private set; }
static Hues()
{
Initialize();
}
/// <summary>
/// Reads hues.mul and fills <see cref="List" />
/// </summary>
public static void Initialize()
{
string path = Files.GetFilePath("hues.mul");
int index = 0;
List = new Hue[3000];
if (path != null)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
int blockCount = (int)fs.Length / 708;
if (blockCount > 375)
{
blockCount = 375;
}
m_Header = new int[blockCount];
int structsize = Marshal.SizeOf(typeof(HueDataMul));
var buffer = new byte[blockCount * (4 + 8 * structsize)];
GCHandle gc = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
fs.Read(buffer, 0, buffer.Length);
long currpos = 0;
for (int i = 0; i < blockCount; ++i)
{
var ptrheader = new IntPtr((long)gc.AddrOfPinnedObject() + currpos);
currpos += 4;
m_Header[i] = (int)Marshal.PtrToStructure(ptrheader, typeof(int));
for (int j = 0; j < 8; ++j, ++index)
{
var ptr = new IntPtr((long)gc.AddrOfPinnedObject() + currpos);
currpos += structsize;
var cur = (HueDataMul)Marshal.PtrToStructure(ptr, typeof(HueDataMul));
List[index] = new Hue(index, cur);
}
}
}
finally
{
gc.Free();
}
}
}
for (; index < List.Length; ++index)
{
List[index] = new Hue(index);
}
}
public static void Save(string path)
{
string mul = Path.Combine(path, "hues.mul");
using (var fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var binmul = new BinaryWriter(fsmul))
{
int index = 0;
for (int i = 0; i < m_Header.Length; ++i)
{
binmul.Write(m_Header[i]);
for (int j = 0; j < 8; ++j, ++index)
{
for (int c = 0; c < 32; ++c)
{
binmul.Write((short)(List[index].Colors[c] ^ 0x8000));
}
binmul.Write((short)(List[index].TableStart ^ 0x8000));
binmul.Write((short)(List[index].TableEnd ^ 0x8000));
var b = new byte[20];
if (List[index].Name != null)
{
byte[] bb = Encoding.Default.GetBytes(List[index].Name);
if (bb.Length > 20)
{
Array.Resize(ref bb, 20);
}
bb.CopyTo(b, 0);
}
binmul.Write(b);
}
}
}
}
}
/// <summary>
/// Returns <see cref="Hue" />
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static Hue GetHue(int index)
{
index &= 0x3FFF;
if (index >= 0 && index < 3000)
{
return List[index];
}
return List[0];
}
/// <summary>
/// Converts RGB value to Huecolor
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static short ColorToHue(Color c)
{
ushort origred = c.R;
ushort origgreen = c.G;
ushort origblue = c.B;
const double scale = 31.0 / 255;
var newred = (ushort)(origred * scale);
if (newred == 0 && origred != 0)
{
newred = 1;
}
var newgreen = (ushort)(origgreen * scale);
if (newgreen == 0 && origgreen != 0)
{
newgreen = 1;
}
var newblue = (ushort)(origblue * scale);
if (newblue == 0 && origblue != 0)
{
newblue = 1;
}
return (short)((newred << 10) | (newgreen << 5) | (newblue));
}
/// <summary>
/// Converts Huecolor to RGBColor
/// </summary>
/// <param name="hue"></param>
/// <returns></returns>
public static Color HueToColor(short hue)
{
const int scale = 255 / 31;
return Color.FromArgb((((hue & 0x7c00) >> 10) * scale), (((hue & 0x3e0) >> 5) * scale), ((hue & 0x1f) * scale));
}
public static int HueToColorR(short hue)
{
return (((hue & 0x7c00) >> 10) * (255 / 31));
}
public static int HueToColorG(short hue)
{
return (((hue & 0x3e0) >> 5) * (255 / 31));
}
public static int HueToColorB(short hue)
{
return ((hue & 0x1f) * (255 / 31));
}
public static unsafe void ApplyTo(Bitmap bmp, short[] Colors, bool onlyHueGrayPixels)
{
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, Settings.PixelFormat);
int stride = bd.Stride >> 1;
int width = bd.Width;
int height = bd.Height;
int delta = stride - width;
var pBuffer = (ushort*)bd.Scan0;
ushort* pLineEnd = pBuffer + width;
ushort* pImageEnd = pBuffer + (stride * height);
if (onlyHueGrayPixels)
{
int c;
int r;
int g;
int b;
while (pBuffer < pImageEnd)
{
while (pBuffer < pLineEnd)
{
c = *pBuffer;
if (c != 0)
{
r = (c >> 10) & 0x1F;
g = (c >> 5) & 0x1F;
b = c & 0x1F;
if (r == g && r == b)
{
*pBuffer = (ushort)Colors[(c >> 10) & 0x1F];
}
}
++pBuffer;
}
pBuffer += delta;
pLineEnd += stride;
}
}
else
{
while (pBuffer < pImageEnd)
{
while (pBuffer < pLineEnd)
{
if (*pBuffer != 0)
{
*pBuffer = (ushort)Colors[(*pBuffer >> 10) & 0x1F];
}
++pBuffer;
}
pBuffer += delta;
pLineEnd += stride;
}
}
bmp.UnlockBits(bd);
}
}
public sealed class Hue
{
public int Index { get; private set; }
public short[] Colors { get; set; }
public string Name { get; set; }
public short TableStart { get; set; }
public short TableEnd { get; set; }
public Hue(int index)
{
Name = "Null";
Index = index;
Colors = new short[32];
TableStart = 0;
TableEnd = 0;
}
public Color GetColor(int index)
{
return Hues.HueToColor(Colors[index]);
}
private static readonly byte[] m_StringBuffer = new byte[20];
private static byte[] m_Buffer = new byte[88];
public Hue(int index, BinaryReader bin)
{
Index = index;
Colors = new short[32];
m_Buffer = bin.ReadBytes(88);
unsafe
{
fixed (byte* buffer = m_Buffer)
{
var buf = (ushort*)buffer;
for (int i = 0; i < 32; ++i)
{
Colors[i] = (short)(*buf++ | 0x8000);
}
TableStart = (short)(*buf++ | 0x8000);
TableEnd = (short)(*buf++ | 0x8000);
var sbuf = (byte*)buf;
int count;
for (count = 0; count < 20 && *sbuf != 0; ++count)
{
m_StringBuffer[count] = *sbuf++;
}
Name = Encoding.Default.GetString(m_StringBuffer, 0, count);
Name = Name.Replace("\n", " ");
}
}
}
public Hue(int index, HueDataMul mulstruct)
{
Index = index;
Colors = new short[32];
for (int i = 0; i < 32; ++i)
{
Colors[i] = (short)(mulstruct.colors[i] | 0x8000);
}
TableStart = (short)(mulstruct.tablestart | 0x8000);
TableEnd = (short)(mulstruct.tableend | 0x8000);
Name = NativeMethods.ReadNameString(mulstruct.name, 20);
Name = Name.Replace("\n", " ");
}
/// <summary>
/// Applies Hue to Bitmap
/// </summary>
/// <param name="bmp"></param>
/// <param name="onlyHueGrayPixels"></param>
public unsafe void ApplyTo(Bitmap bmp, bool onlyHueGrayPixels)
{
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, Settings.PixelFormat);
int stride = bd.Stride >> 1;
int width = bd.Width;
int height = bd.Height;
int delta = stride - width;
var pBuffer = (ushort*)bd.Scan0;
ushort* pLineEnd = pBuffer + width;
ushort* pImageEnd = pBuffer + (stride * height);
if (onlyHueGrayPixels)
{
int c;
int r;
int g;
int b;
while (pBuffer < pImageEnd)
{
while (pBuffer < pLineEnd)
{
c = *pBuffer;
if (c != 0)
{
r = (c >> 10) & 0x1F;
g = (c >> 5) & 0x1F;
b = c & 0x1F;
if (r == g && r == b)
{
*pBuffer = (ushort)Colors[(c >> 10) & 0x1F];
}
}
++pBuffer;
}
pBuffer += delta;
pLineEnd += stride;
}
}
else
{
while (pBuffer < pImageEnd)
{
while (pBuffer < pLineEnd)
{
if (*pBuffer != 0)
{
*pBuffer = (ushort)Colors[(*pBuffer >> 10) & 0x1F];
}
++pBuffer;
}
pBuffer += delta;
pLineEnd += stride;
}
}
bmp.UnlockBits(bd);
}
public void Export(string FileName)
{
using (
var Tex = new StreamWriter(
new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite), Encoding.GetEncoding(1252)))
{
Tex.WriteLine(Name);
Tex.WriteLine(((short)(TableStart ^ 0x8000)).ToString());
Tex.WriteLine(((short)(TableEnd ^ 0x8000)).ToString());
for (int i = 0; i < Colors.Length; ++i)
{
Tex.WriteLine(((short)(Colors[i] ^ 0x8000)).ToString());
}
}
}
public void Import(string FileName)
{
if (!File.Exists(FileName))
{
return;
}
using (var sr = new StreamReader(FileName))
{
string line;
int i = -3;
while ((line = sr.ReadLine()) != null)
{
line = line.Trim();
try
{
if (i >= Colors.Length)
{
break;
}
if (i == -3)
{
Name = line;
}
else if (i == -2)
{
TableStart = (short)(ushort.Parse(line) | 0x8000);
}
else if (i == -1)
{
TableEnd = (short)(ushort.Parse(line) | 0x8000);
}
else
{
Colors[i] = (short)(ushort.Parse(line) | 0x8000);
}
++i;
}
catch
{ }
}
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct HueDataMul
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public ushort[] colors;
public ushort tablestart;
public ushort tableend;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] name;
}
}

253
Ultima/Light.cs Normal file
View File

@@ -0,0 +1,253 @@
#region References
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
#endregion
namespace Ultima
{
public sealed class Light
{
private static FileIndex m_FileIndex = new FileIndex("lightidx.mul", "light.mul", 100, -1);
private static Bitmap[] m_Cache = new Bitmap[100];
private static bool[] m_Removed = new bool[100];
private static byte[] m_StreamBuffer;
/// <summary>
/// ReReads light.mul
/// </summary>
public static void Reload()
{
m_FileIndex = new FileIndex("lightidx.mul", "light.mul", 100, -1);
m_Cache = new Bitmap[100];
m_Removed = new bool[100];
}
/// <summary>
/// Gets count of definied lights
/// </summary>
/// <returns></returns>
public static int GetCount()
{
string idxPath = Files.GetFilePath("lightidx.mul");
if (idxPath == null)
{
return 0;
}
using (var index = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return (int)(index.Length / 12);
}
}
/// <summary>
/// Tests if given index is valid
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static bool TestLight(int index)
{
if (m_Removed[index])
{
return false;
}
if (m_Cache[index] != null)
{
return true;
}
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return false;
}
stream.Close();
int width = (extra & 0xFFFF);
int height = ((extra >> 16) & 0xFFFF);
if ((width > 0) && (height > 0))
{
return true;
}
return false;
}
/// <summary>
/// Removes Light <see cref="m_Removed" />
/// </summary>
/// <param name="index"></param>
public static void Remove(int index)
{
m_Removed[index] = true;
}
/// <summary>
/// Replaces Light
/// </summary>
/// <param name="index"></param>
/// <param name="bmp"></param>
public static void Replace(int index, Bitmap bmp)
{
m_Cache[index] = bmp;
m_Removed[index] = false;
}
public static byte[] GetRawLight(int index, out int width, out int height)
{
width = 0;
height = 0;
if (m_Removed[index])
{
return null;
}
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
width = (extra & 0xFFFF);
height = ((extra >> 16) & 0xFFFF);
var buffer = new byte[length];
stream.Read(buffer, 0, length);
stream.Close();
return buffer;
}
/// <summary>
/// Returns Bitmap of given index
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static unsafe Bitmap GetLight(int index)
{
if (m_Removed[index])
{
return null;
}
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;
}
int width = (extra & 0xFFFF);
int height = ((extra >> 16) & 0xFFFF);
if (m_StreamBuffer == null || m_StreamBuffer.Length < length)
{
m_StreamBuffer = new byte[length];
}
stream.Read(m_StreamBuffer, 0, length);
var bmp = new Bitmap(width, height, Settings.PixelFormat);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
fixed (byte* data = m_StreamBuffer)
{
var bindat = (sbyte*)data;
for (int y = 0; y < height; ++y, line += delta)
{
ushort* cur = line;
ushort* end = cur + width;
while (cur < end)
{
sbyte value = *bindat++;
*cur++ = (ushort)(((0x1f + value) << 10) + ((0x1F + value) << 5) + (0x1F + value));
}
}
}
bmp.UnlockBits(bd);
stream.Close();
if (!Files.CacheData)
{
return m_Cache[index] = bmp;
}
else
{
return bmp;
}
}
public static unsafe void Save(string path)
{
string idx = Path.Combine(path, "lightidx.mul");
string mul = Path.Combine(path, "light.mul");
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (BinaryWriter binidx = new BinaryWriter(fsidx), binmul = new BinaryWriter(fsmul))
{
for (int index = 0; index < m_Cache.Length; index++)
{
if (m_Cache[index] == null)
{
m_Cache[index] = GetLight(index);
}
Bitmap bmp = m_Cache[index];
if ((bmp == null) || (m_Removed[index]))
{
binidx.Write(-1); // lookup
binidx.Write(-1); // length
binidx.Write(-1); // extra
}
else
{
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
binidx.Write((int)fsmul.Position); //lookup
var length = (int)fsmul.Position;
for (int Y = 0; Y < bmp.Height; ++Y, line += delta)
{
ushort* cur = line;
ushort* end = cur + bmp.Width;
while (cur < end)
{
var value = (sbyte)(((*cur++ >> 10) & 0xffff) - 0x1f);
if (value > 0) // wtf? but it works...
{
--value;
}
binmul.Write(value);
}
}
length = (int)fsmul.Position - length;
binidx.Write(length);
binidx.Write((bmp.Width << 16) + bmp.Height);
bmp.UnlockBits(bd);
}
}
}
}
}
}
}

26
Ultima/LocationPointer.cs Normal file
View File

@@ -0,0 +1,26 @@
namespace Ultima
{
public sealed class LocationPointer
{
public int PointerX { get; set; }
public int PointerY { get; set; }
public int PointerZ { get; set; }
public int PointerF { get; set; }
public int SizeX { get; set; }
public int SizeY { get; set; }
public int SizeZ { get; set; }
public int SizeF { get; set; }
public LocationPointer(int ptrX, int ptrY, int ptrZ, int ptrF, int sizeX, int sizeY, int sizeZ, int sizeF)
{
PointerX = ptrX;
PointerY = ptrY;
PointerZ = ptrZ;
PointerF = ptrF;
SizeX = sizeX;
SizeY = sizeY;
SizeZ = sizeZ;
SizeF = sizeF;
}
}
}

951
Ultima/Map.cs Normal file
View File

@@ -0,0 +1,951 @@
#region References
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
#endregion
namespace Ultima
{
public sealed class Map
{
private TileMatrix m_Tiles;
private readonly int m_FileIndex;
private readonly int m_MapID;
private int m_Width;
private readonly int m_Height;
private readonly string m_path;
private static bool m_UseDiff;
public static bool UseDiff
{
get { return m_UseDiff; }
set
{
m_UseDiff = value;
Reload();
}
}
public static Map Felucca = new Map(0, 0, 6144, 4096);
public static Map Trammel = new Map(0, 1, 6144, 4096);
public static readonly Map Ilshenar = new Map(2, 2, 2304, 1600);
public static readonly Map Malas = new Map(3, 3, 2560, 2048);
public static readonly Map Tokuno = new Map(4, 4, 1448, 1448);
public static readonly Map TerMur = new Map(5, 5, 1280, 4096);
public static Map Custom;
public static void StartUpSetDiff(bool value)
{
m_UseDiff = value;
}
public Map(int fileIndex, int mapID, int width, int height)
{
m_FileIndex = fileIndex;
m_MapID = mapID;
m_Width = width;
m_Height = height;
m_path = null;
}
public Map(string path, int fileIndex, int mapID, int width, int height)
{
m_FileIndex = fileIndex;
m_MapID = mapID;
m_Width = width;
m_Height = height;
m_path = path;
}
/// <summary>
/// Sets cache-vars to null
/// </summary>
public static void Reload()
{
Felucca.Tiles.Dispose();
Trammel.Tiles.Dispose();
Ilshenar.Tiles.Dispose();
Malas.Tiles.Dispose();
Tokuno.Tiles.Dispose();
TerMur.Tiles.Dispose();
Felucca.Tiles.StaticIndexInit = false;
Trammel.Tiles.StaticIndexInit = false;
Ilshenar.Tiles.StaticIndexInit = false;
Malas.Tiles.StaticIndexInit = false;
Tokuno.Tiles.StaticIndexInit = false;
TerMur.Tiles.StaticIndexInit = false;
Felucca.m_Cache = Trammel.m_Cache = Ilshenar.m_Cache = Malas.m_Cache = Tokuno.m_Cache = TerMur.m_Cache = null;
Felucca.m_Tiles = Trammel.m_Tiles = Ilshenar.m_Tiles = Malas.m_Tiles = Tokuno.m_Tiles = TerMur.m_Tiles = null;
Felucca.m_Cache_NoStatics =
Trammel.m_Cache_NoStatics =
Ilshenar.m_Cache_NoStatics = Malas.m_Cache_NoStatics = Tokuno.m_Cache_NoStatics = TerMur.m_Cache_NoStatics = null;
Felucca.m_Cache_NoPatch =
Trammel.m_Cache_NoPatch =
Ilshenar.m_Cache_NoPatch = Malas.m_Cache_NoPatch = Tokuno.m_Cache_NoPatch = TerMur.m_Cache_NoPatch = null;
Felucca.m_Cache_NoStatics_NoPatch =
Trammel.m_Cache_NoStatics_NoPatch =
Ilshenar.m_Cache_NoStatics_NoPatch =
Malas.m_Cache_NoStatics_NoPatch = Tokuno.m_Cache_NoStatics_NoPatch = TerMur.m_Cache_NoStatics_NoPatch = null;
}
public void ResetCache()
{
m_Cache = null;
m_Cache_NoPatch = null;
m_Cache_NoStatics = null;
m_Cache_NoStatics_NoPatch = null;
IsCached_Default = false;
IsCached_NoStatics = false;
IsCached_NoPatch = false;
IsCached_NoStatics_NoPatch = false;
}
public bool LoadedMatrix { get { return (m_Tiles != null); } }
public TileMatrix Tiles
{
get
{
if (m_Tiles == null)
{
m_Tiles = new TileMatrix(m_FileIndex, m_MapID, m_Width, m_Height, m_path);
}
return m_Tiles;
}
}
public int Width { get { return m_Width; } set { m_Width = value; } }
public int Height { get { return m_Height; } }
public int FileIndex { get { return m_FileIndex; } }
/// <summary>
/// Returns Bitmap with Statics
/// </summary>
/// <param name="x">8x8 Block</param>
/// <param name="y">8x8 Block</param>
/// <param name="width">8x8 Block</param>
/// <param name="height">8x8 Block</param>
/// <returns></returns>
public Bitmap GetImage(int x, int y, int width, int height)
{
return GetImage(x, y, width, height, true);
}
/// <summary>
/// Returns Bitmap
/// </summary>
/// <param name="x">8x8 Block</param>
/// <param name="y">8x8 Block</param>
/// <param name="width">8x8 Block</param>
/// <param name="height">8x8 Block</param>
/// <param name="statics">8x8 Block</param>
/// <returns></returns>
public Bitmap GetImage(int x, int y, int width, int height, bool statics)
{
var bmp = new Bitmap(width << 3, height << 3, PixelFormat.Format16bppRgb555);
GetImage(x, y, width, height, bmp, statics);
return bmp;
}
private bool IsCached_Default;
private bool IsCached_NoStatics;
private bool IsCached_NoPatch;
private bool IsCached_NoStatics_NoPatch;
private short[][][] m_Cache;
private short[][][] m_Cache_NoStatics;
private short[][][] m_Cache_NoPatch;
private short[][][] m_Cache_NoStatics_NoPatch;
private short[] m_Black;
public bool IsCached(bool statics)
{
if (UseDiff)
{
if (!statics)
{
return IsCached_NoStatics;
}
else
{
return IsCached_Default;
}
}
else
{
if (!statics)
{
return IsCached_NoStatics_NoPatch;
}
else
{
return IsCached_NoPatch;
}
}
}
public void PreloadRenderedBlock(int x, int y, bool statics)
{
TileMatrix matrix = Tiles;
if (x < 0 || y < 0 || x >= matrix.BlockWidth || y >= matrix.BlockHeight)
{
if (m_Black == null)
{
m_Black = new short[64];
}
return;
}
short[][][] cache;
if (UseDiff)
{
if (statics)
{
IsCached_Default = true;
}
else
{
IsCached_NoStatics = true;
}
cache = (statics ? m_Cache : m_Cache_NoStatics);
}
else
{
if (statics)
{
IsCached_NoPatch = true;
}
else
{
IsCached_NoStatics_NoPatch = true;
}
cache = (statics ? m_Cache_NoPatch : m_Cache_NoStatics_NoPatch);
}
if (cache == null)
{
if (UseDiff)
{
if (statics)
{
m_Cache = cache = new short[m_Tiles.BlockHeight][][];
}
else
{
m_Cache_NoStatics = cache = new short[m_Tiles.BlockHeight][][];
}
}
else
{
if (statics)
{
m_Cache_NoPatch = cache = new short[m_Tiles.BlockHeight][][];
}
else
{
m_Cache_NoStatics_NoPatch = cache = new short[m_Tiles.BlockHeight][][];
}
}
}
if (cache[y] == null)
{
cache[y] = new short[m_Tiles.BlockWidth][];
}
if (cache[y][x] == null)
{
cache[y][x] = RenderBlock(x, y, statics, UseDiff);
}
m_Tiles.CloseStreams();
}
private short[] GetRenderedBlock(int x, int y, bool statics)
{
TileMatrix matrix = Tiles;
if (x < 0 || y < 0 || x >= matrix.BlockWidth || y >= matrix.BlockHeight)
{
if (m_Black == null)
{
m_Black = new short[64];
}
return m_Black;
}
short[][][] cache;
if (UseDiff)
{
cache = (statics ? m_Cache : m_Cache_NoStatics);
}
else
{
cache = (statics ? m_Cache_NoPatch : m_Cache_NoStatics_NoPatch);
}
if (cache == null)
{
if (UseDiff)
{
if (statics)
{
m_Cache = cache = new short[m_Tiles.BlockHeight][][];
}
else
{
m_Cache_NoStatics = cache = new short[m_Tiles.BlockHeight][][];
}
}
else
{
if (statics)
{
m_Cache_NoPatch = cache = new short[m_Tiles.BlockHeight][][];
}
else
{
m_Cache_NoStatics_NoPatch = cache = new short[m_Tiles.BlockHeight][][];
}
}
}
if (cache[y] == null)
{
cache[y] = new short[m_Tiles.BlockWidth][];
}
short[] data = cache[y][x];
if (data == null)
{
cache[y][x] = data = RenderBlock(x, y, statics, UseDiff);
}
return data;
}
private unsafe short[] RenderBlock(int x, int y, bool drawStatics, bool diff)
{
var data = new short[64];
Tile[] tiles = m_Tiles.GetLandBlock(x, y, diff);
fixed (short* pColors = RadarCol.Colors)
{
fixed (int* pHeight = TileData.HeightTable)
{
fixed (Tile* ptTiles = tiles)
{
Tile* pTiles = ptTiles;
fixed (short* pData = data)
{
short* pvData = pData;
if (drawStatics)
{
HuedTile[][][] statics = drawStatics ? m_Tiles.GetStaticBlock(x, y, diff) : null;
for (int k = 0, v = 0; k < 8; ++k, v += 8)
{
for (int p = 0; p < 8; ++p)
{
int highTop = -255;
int highZ = -255;
int highID = 0;
int highHue = 0;
int z, top;
bool highstatic = false;
HuedTile[] curStatics = statics[p][k];
if (curStatics.Length > 0)
{
fixed (HuedTile* phtStatics = curStatics)
{
HuedTile* pStatics = phtStatics;
HuedTile* pStaticsEnd = pStatics + curStatics.Length;
while (pStatics < pStaticsEnd)
{
z = pStatics->m_Z;
top = z + pHeight[pStatics->ID];
if (top > highTop || (z > highZ && top >= highTop))
{
highTop = top;
highZ = z;
highID = pStatics->ID;
highHue = pStatics->Hue;
highstatic = true;
}
++pStatics;
}
}
}
StaticTile[] pending = m_Tiles.GetPendingStatics(x, y);
if (pending != null)
{
foreach (StaticTile penS in pending)
{
if (penS.m_X == p)
{
if (penS.m_Y == k)
{
z = penS.m_Z;
top = z + pHeight[penS.m_ID];
if (top > highTop || (z > highZ && top >= highTop))
{
highTop = top;
highZ = z;
highID = penS.m_ID;
highHue = penS.m_Hue;
highstatic = true;
}
}
}
}
}
top = pTiles->m_Z;
if (top > highTop)
{
highID = pTiles->m_ID;
highHue = 0;
highstatic = false;
}
if (highHue == 0)
{
try
{
if (highstatic)
{
*pvData++ = pColors[highID + 0x4000];
}
else
{
*pvData++ = pColors[highID];
}
}
catch
{ }
}
else
{
*pvData++ = Hues.GetHue(highHue - 1).Colors[(pColors[highID + 0x4000] >> 10) & 0x1F];
}
++pTiles;
}
}
}
else
{
Tile* pEnd = pTiles + 64;
while (pTiles < pEnd)
{
*pvData++ = pColors[(pTiles++)->m_ID];
}
}
}
}
}
}
return data;
}
/// <summary>
/// Draws in given Bitmap with Statics
/// </summary>
/// <param name="x">8x8 Block</param>
/// <param name="y">8x8 Block</param>
/// <param name="width">8x8 Block</param>
/// <param name="height">8x8 Block</param>
/// <param name="bmp">8x8 Block</param>
public void GetImage(int x, int y, int width, int height, Bitmap bmp)
{
GetImage(x, y, width, height, bmp, true);
}
/// <summary>
/// Draws in given Bitmap
/// </summary>
/// <param name="x">8x8 Block</param>
/// <param name="y">8x8 Block</param>
/// <param name="width">8x8 Block</param>
/// <param name="height">8x8 Block</param>
/// <param name="bmp"></param>
/// <param name="statics"></param>
public unsafe void GetImage(int x, int y, int width, int height, Bitmap bmp, bool statics)
{
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, width << 3, height << 3), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb555);
int stride = bd.Stride;
int blockStride = stride << 3;
var pStart = (byte*)bd.Scan0;
for (int oy = 0, by = y; oy < height; ++oy, ++by, pStart += blockStride)
{
var pRow0 = (int*)(pStart + (0 * stride));
var pRow1 = (int*)(pStart + (1 * stride));
var pRow2 = (int*)(pStart + (2 * stride));
var pRow3 = (int*)(pStart + (3 * stride));
var pRow4 = (int*)(pStart + (4 * stride));
var pRow5 = (int*)(pStart + (5 * stride));
var pRow6 = (int*)(pStart + (6 * stride));
var pRow7 = (int*)(pStart + (7 * stride));
for (int ox = 0, bx = x; ox < width; ++ox, ++bx)
{
short[] data = GetRenderedBlock(bx, by, statics);
fixed (short* pData = data)
{
var pvData = (int*)pData;
*pRow0++ = *pvData++;
*pRow0++ = *pvData++;
*pRow0++ = *pvData++;
*pRow0++ = *pvData++;
*pRow1++ = *pvData++;
*pRow1++ = *pvData++;
*pRow1++ = *pvData++;
*pRow1++ = *pvData++;
*pRow2++ = *pvData++;
*pRow2++ = *pvData++;
*pRow2++ = *pvData++;
*pRow2++ = *pvData++;
*pRow3++ = *pvData++;
*pRow3++ = *pvData++;
*pRow3++ = *pvData++;
*pRow3++ = *pvData++;
*pRow4++ = *pvData++;
*pRow4++ = *pvData++;
*pRow4++ = *pvData++;
*pRow4++ = *pvData++;
*pRow5++ = *pvData++;
*pRow5++ = *pvData++;
*pRow5++ = *pvData++;
*pRow5++ = *pvData++;
*pRow6++ = *pvData++;
*pRow6++ = *pvData++;
*pRow6++ = *pvData++;
*pRow6++ = *pvData++;
*pRow7++ = *pvData++;
*pRow7++ = *pvData++;
*pRow7++ = *pvData++;
*pRow7++ = *pvData++;
}
}
}
bmp.UnlockBits(bd);
m_Tiles.CloseStreams();
}
public static void DefragStatics(string path, Map map, int width, int height, bool remove)
{
string indexPath = Files.GetFilePath("staidx{0}.mul", map.FileIndex);
FileStream m_Index;
BinaryReader m_IndexReader;
if (indexPath != null)
{
m_Index = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_IndexReader = new BinaryReader(m_Index);
}
else
{
return;
}
string staticsPath = Files.GetFilePath("statics{0}.mul", map.FileIndex);
FileStream m_Statics;
BinaryReader m_StaticsReader;
if (staticsPath != null)
{
m_Statics = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_StaticsReader = new BinaryReader(m_Statics);
}
else
{
return;
}
int blockx = width >> 3;
int blocky = height >> 3;
string idx = Path.Combine(path, String.Format("staidx{0}.mul", map.FileIndex));
string mul = Path.Combine(path, String.Format("statics{0}.mul", map.FileIndex));
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
var memidx = new MemoryStream();
var memmul = new MemoryStream();
using (BinaryWriter binidx = new BinaryWriter(memidx), binmul = new BinaryWriter(memmul))
{
for (int x = 0; x < blockx; ++x)
{
for (int y = 0; y < blocky; ++y)
{
try
{
m_IndexReader.BaseStream.Seek(((x * blocky) + y) * 12, SeekOrigin.Begin);
int lookup = m_IndexReader.ReadInt32();
int length = m_IndexReader.ReadInt32();
int extra = m_IndexReader.ReadInt32();
if (((lookup < 0 || length <= 0) && (!map.Tiles.PendingStatic(x, y))) || (map.Tiles.IsStaticBlockRemoved(x, y)))
{
binidx.Write(-1); // lookup
binidx.Write(-1); // length
binidx.Write(-1); // extra
}
else
{
if ((lookup >= 0) && (length > 0))
{
m_Statics.Seek(lookup, SeekOrigin.Begin);
}
var fsmullength = (int)binmul.BaseStream.Position;
int count = length / 7;
if (!remove) //without duplicate remove
{
bool firstitem = true;
for (int i = 0; i < count; ++i)
{
ushort graphic = m_StaticsReader.ReadUInt16();
byte sx = m_StaticsReader.ReadByte();
byte sy = m_StaticsReader.ReadByte();
sbyte sz = m_StaticsReader.ReadSByte();
short shue = m_StaticsReader.ReadInt16();
if ((graphic >= 0) && (graphic <= Art.GetMaxItemID()))
{
if (shue < 0)
{
shue = 0;
}
if (firstitem)
{
binidx.Write((int)binmul.BaseStream.Position); //lookup
firstitem = false;
}
binmul.Write(graphic);
binmul.Write(sx);
binmul.Write(sy);
binmul.Write(sz);
binmul.Write(shue);
}
}
StaticTile[] tilelist = map.Tiles.GetPendingStatics(x, y);
if (tilelist != null)
{
for (int i = 0; i < tilelist.Length; ++i)
{
if ((tilelist[i].m_ID >= 0) && (tilelist[i].m_ID <= Art.GetMaxItemID()))
{
if (tilelist[i].m_Hue < 0)
{
tilelist[i].m_Hue = 0;
}
if (firstitem)
{
binidx.Write((int)binmul.BaseStream.Position); //lookup
firstitem = false;
}
binmul.Write(tilelist[i].m_ID);
binmul.Write(tilelist[i].m_X);
binmul.Write(tilelist[i].m_Y);
binmul.Write(tilelist[i].m_Z);
binmul.Write(tilelist[i].m_Hue);
}
}
}
}
else //with duplicate remove
{
var tilelist = new StaticTile[count];
int j = 0;
for (int i = 0; i < count; ++i)
{
var tile = new StaticTile();
tile.m_ID = m_StaticsReader.ReadUInt16();
tile.m_X = m_StaticsReader.ReadByte();
tile.m_Y = m_StaticsReader.ReadByte();
tile.m_Z = m_StaticsReader.ReadSByte();
tile.m_Hue = m_StaticsReader.ReadInt16();
if ((tile.m_ID >= 0) && (tile.m_ID <= Art.GetMaxItemID()))
{
if (tile.m_Hue < 0)
{
tile.m_Hue = 0;
}
bool first = true;
for (int k = 0; k < j; ++k)
{
if ((tilelist[k].m_ID == tile.m_ID) && ((tilelist[k].m_X == tile.m_X) && (tilelist[k].m_Y == tile.m_Y)) &&
(tilelist[k].m_Z == tile.m_Z) && (tilelist[k].m_Hue == tile.m_Hue))
{
first = false;
break;
}
}
if (first)
{
tilelist[j] = tile;
j++;
}
}
}
if (map.Tiles.PendingStatic(x, y))
{
StaticTile[] pending = map.Tiles.GetPendingStatics(x, y);
StaticTile[] old = tilelist;
tilelist = new StaticTile[old.Length + pending.Length];
old.CopyTo(tilelist, 0);
for (int i = 0; i < pending.Length; ++i)
{
if ((pending[i].m_ID >= 0) && (pending[i].m_ID <= Art.GetMaxItemID()))
{
if (pending[i].m_Hue < 0)
{
pending[i].m_Hue = 0;
}
bool first = true;
for (int k = 0; k < j; ++k)
{
if ((tilelist[k].m_ID == pending[i].m_ID) &&
((tilelist[k].m_X == pending[i].m_X) && (tilelist[k].m_Y == pending[i].m_Y)) &&
(tilelist[k].m_Z == pending[i].m_Z) && (tilelist[k].m_Hue == pending[i].m_Hue))
{
first = false;
break;
}
}
if (first)
{
tilelist[j++] = pending[i];
}
}
}
}
if (j > 0)
{
binidx.Write((int)binmul.BaseStream.Position); //lookup
for (int i = 0; i < j; ++i)
{
binmul.Write(tilelist[i].m_ID);
binmul.Write(tilelist[i].m_X);
binmul.Write(tilelist[i].m_Y);
binmul.Write(tilelist[i].m_Z);
binmul.Write(tilelist[i].m_Hue);
}
}
}
fsmullength = (int)binmul.BaseStream.Position - fsmullength;
if (fsmullength > 0)
{
binidx.Write(fsmullength); //length
if (extra == -1)
{
extra = 0;
}
binidx.Write(extra); //extra
}
else
{
binidx.Write(-1); //lookup
binidx.Write(-1); //length
binidx.Write(-1); //extra
}
}
}
catch // fill the rest
{
binidx.BaseStream.Seek(((x * blocky) + y) * 12, SeekOrigin.Begin);
for (; x < blockx; ++x)
{
for (; y < blocky; ++y)
{
binidx.Write(-1); //lookup
binidx.Write(-1); //length
binidx.Write(-1); //extra
}
y = 0;
}
}
}
}
memidx.WriteTo(fsidx);
memmul.WriteTo(fsmul);
}
}
m_IndexReader.Close();
m_StaticsReader.Close();
}
public static void RewriteMap(string path, int map, int width, int height)
{
string mapPath = Files.GetFilePath("map{0}.mul", map);
FileStream m_map;
BinaryReader m_mapReader;
if (mapPath != null)
{
m_map = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_mapReader = new BinaryReader(m_map);
}
else
{
return;
}
int blockx = width >> 3;
int blocky = height >> 3;
string mul = Path.Combine(path, String.Format("map{0}.mul", map));
using (var fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
var memmul = new MemoryStream();
using (var binmul = new BinaryWriter(memmul))
{
for (int x = 0; x < blockx; ++x)
{
for (int y = 0; y < blocky; ++y)
{
try
{
m_mapReader.BaseStream.Seek(((x * blocky) + y) * 196, SeekOrigin.Begin);
int header = m_mapReader.ReadInt32();
binmul.Write(header);
for (int i = 0; i < 64; ++i)
{
short tileid = m_mapReader.ReadInt16();
sbyte z = m_mapReader.ReadSByte();
if ((tileid < 0) || (tileid >= 0x4000))
{
tileid = 0;
}
if (z < -128)
{
z = -128;
}
if (z > 127)
{
z = 127;
}
binmul.Write(tileid);
binmul.Write(z);
}
}
catch //fill rest
{
binmul.BaseStream.Seek(((x * blocky) + y) * 196, SeekOrigin.Begin);
for (; x < blockx; ++x)
{
for (; y < blocky; ++y)
{
binmul.Write(0);
for (int i = 0; i < 64; ++i)
{
binmul.Write((short)0);
binmul.Write((sbyte)0);
}
}
y = 0;
}
}
}
}
memmul.WriteTo(fsmul);
}
}
m_mapReader.Close();
}
public void ReportInvisStatics(string reportfile)
{
reportfile = Path.Combine(reportfile, String.Format("staticReport-{0}.csv", m_MapID));
using (
var Tex = new StreamWriter(
new FileStream(reportfile, FileMode.Create, FileAccess.ReadWrite), Encoding.GetEncoding(1252)))
{
Tex.WriteLine("x;y;z;Static");
for (int x = 0; x < m_Width; ++x)
{
for (int y = 0; y < m_Height; ++y)
{
Tile currtile = Tiles.GetLandTile(x, y);
foreach (HuedTile currstatic in Tiles.GetStaticTiles(x, y))
{
if (currstatic.Z < currtile.Z)
{
if (TileData.ItemTable[currstatic.ID].Height + currstatic.Z < currtile.Z)
{
Tex.WriteLine(String.Format("{0};{1};{2};0x{3:X}", x, y, currstatic.Z, currstatic.ID));
}
}
}
}
}
}
}
public void ReportInvalidMapIDs(string reportfile)
{
reportfile = Path.Combine(reportfile, String.Format("ReportInvalidMapIDs-{0}.csv", m_MapID));
using (
var Tex = new StreamWriter(
new FileStream(reportfile, FileMode.Create, FileAccess.ReadWrite), Encoding.GetEncoding(1252)))
{
Tex.WriteLine("x;y;z;Static;LandTile");
for (int x = 0; x < m_Width; ++x)
{
for (int y = 0; y < m_Height; ++y)
{
Tile currtile = Tiles.GetLandTile(x, y);
if (!Art.IsValidLand(currtile.ID))
{
Tex.WriteLine(String.Format("{0};{1};{2};0;0x{3:X}", x, y, currtile.Z, currtile.ID));
}
foreach (HuedTile currstatic in Tiles.GetStaticTiles(x, y))
{
if (!Art.IsValidStatic(currstatic.ID))
{
Tex.WriteLine(String.Format("{0};{1};{2};0x{3:X};0", x, y, currstatic.Z, currstatic.ID));
}
}
}
}
}
}
}
}

253
Ultima/MultiMap.cs Normal file
View File

@@ -0,0 +1,253 @@
#region References
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
#endregion
namespace Ultima
{
public sealed class MultiMap
{
private static byte[] m_StreamBuffer;
/// <summary>
/// Returns Bitmap
/// </summary>
/// <returns></returns>
public static unsafe Bitmap GetMultiMap()
{
string path = Files.GetFilePath("Multimap.rle");
if (path != null)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var bin = new BinaryReader(fs))
{
int width, height;
byte pixel;
int count;
int x, i;
x = 0;
ushort c = 0;
width = bin.ReadInt32();
height = bin.ReadInt32();
var multimap = new Bitmap(width, height, Settings.PixelFormat);
BitmapData bd = multimap.LockBits(
new Rectangle(0, 0, multimap.Width, multimap.Height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
ushort* cur = line;
var len = (int)(bin.BaseStream.Length - bin.BaseStream.Position);
if (m_StreamBuffer == null || m_StreamBuffer.Length < len)
{
m_StreamBuffer = new byte[len];
}
bin.Read(m_StreamBuffer, 0, len);
int j = 0;
while (j != len)
{
pixel = m_StreamBuffer[j++];
count = (pixel & 0x7f);
if ((pixel & 0x80) != 0)
{
c = 0x8000; //Color.Black;
}
else
{
c = 0xffff; //Color.White;
}
for (i = 0; i < count; ++i)
{
cur[x++] = c;
if (x >= width)
{
cur += delta;
x = 0;
}
}
}
multimap.UnlockBits(bd);
return multimap;
}
}
}
return null;
}
/// <summary>
/// Saves Bitmap to rle Format
/// </summary>
/// <param name="image"></param>
/// <param name="bin"></param>
public static unsafe void SaveMultiMap(Bitmap image, BinaryWriter bin)
{
bin.Write(2560); // width
bin.Write(2048); // height
byte data = 1;
byte mask = 0x0;
ushort curcolor = 0;
BitmapData bd = image.LockBits(
new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
ushort* cur = line;
curcolor = cur[0]; //init
for (int y = 0; y < image.Height; ++y, line += delta)
{
cur = line;
for (int x = 0; x < image.Width; ++x)
{
ushort c = cur[x];
if (c == curcolor)
{
++data;
if (data == 0x7f)
{
if (curcolor == 0xffff)
{
mask = 0x0;
}
else
{
mask = 0x80;
}
data |= mask;
bin.Write(data);
data = 1;
}
}
else
{
if (curcolor == 0xffff)
{
mask = 0x0;
}
else
{
mask = 0x80;
}
data |= mask;
bin.Write(data);
curcolor = c;
data = 1;
}
}
}
if (curcolor == 0xffff)
{
mask = 0x0;
}
else
{
mask = 0x80;
}
data |= mask;
bin.Write(data);
image.UnlockBits(bd);
}
/// <summary>
/// reads facet0*.mul into Bitmap
/// </summary>
/// <param name="id">facet id</param>
/// <returns>Bitmap</returns>
public static unsafe Bitmap GetFacetImage(int id)
{
Bitmap bmp;
string path = Files.GetFilePath(String.Format("facet0{0}.mul", id));
if (path != null)
{
using (var reader = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
int width = reader.ReadInt16();
int height = reader.ReadInt16();
bmp = new Bitmap(width, height);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < height; y++, line += delta)
{
int colorsCount = reader.ReadInt32() / 3;
ushort* endline = line + delta;
ushort* cur = line;
ushort* end;
for (int c = 0; c < colorsCount; c++)
{
byte count = reader.ReadByte();
short color = reader.ReadInt16();
end = cur + count;
while (cur < end)
{
if (cur > endline)
{
break;
}
*cur++ = (ushort)(color ^ 0x8000);
}
}
}
bmp.UnlockBits(bd);
}
return bmp;
}
return null;
}
/// <summary>
/// Stores Image into facet.mul format
/// </summary>
/// <param name="path"></param>
/// <param name="sourceBitmap"></param>
public static unsafe void SaveFacetImage(string path, Bitmap sourceBitmap)
{
int width = sourceBitmap.Width;
int height = sourceBitmap.Height;
using (
var writer = new BinaryWriter(new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
writer.Write((short)width);
writer.Write((short)height);
BitmapData bd = sourceBitmap.LockBits(
new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < height; y++, line += delta)
{
long pos = writer.BaseStream.Position;
writer.Write(0); //bytes count for current line
int colorsAtLine = 0;
int colorsCount = 0;
int x = 0;
while (x < width)
{
ushort hue = line[x];
while (x < width && colorsCount < byte.MaxValue && hue == line[x])
{
++colorsCount;
++x;
}
writer.Write((byte)colorsCount);
writer.Write((ushort)(hue ^ 0x8000));
colorsAtLine++;
colorsCount = 0;
}
long currpos = writer.BaseStream.Position;
writer.BaseStream.Seek(pos, SeekOrigin.Begin);
writer.Write(colorsAtLine * 3); //byte count
writer.BaseStream.Seek(currpos, SeekOrigin.Begin);
}
}
}
}
}

1305
Ultima/Multis.cs Normal file

File diff suppressed because it is too large Load Diff

89
Ultima/NativeMethods.cs Normal file
View File

@@ -0,0 +1,89 @@
#region References
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
#endregion
namespace Ultima
{
public static class NativeMethods
{
[DllImport("User32")]
public static extern int IsWindow(ClientWindowHandle window);
[DllImport("User32")]
public static extern int GetWindowThreadProcessId(ClientWindowHandle window, ref ClientProcessHandle processID);
[DllImport("Kernel32")]
public static extern unsafe int _lread(SafeFileHandle hFile, void* lpBuffer, int wBytes);
[DllImport("Kernel32")]
public static extern ClientProcessHandle OpenProcess(
int desiredAccess, int inheritClientHandle, ClientProcessHandle processID);
[DllImport("Kernel32")]
public static extern int CloseHandle(ClientProcessHandle handle);
[DllImport("Kernel32")]
public static extern unsafe int ReadProcessMemory(
ClientProcessHandle process, int baseAddress, void* buffer, int size, ref int op);
[DllImport("Kernel32")]
public static extern unsafe int WriteProcessMemory(
ClientProcessHandle process, int baseAddress, void* buffer, int size, int nullMe);
[DllImport("User32")]
public static extern int SetForegroundWindow(ClientWindowHandle hWnd);
[DllImport("User32")]
public static extern int SendMessage(ClientWindowHandle hWnd, int wMsg, int wParam, int lParam);
[DllImport("User32")]
public static extern bool PostMessage(ClientWindowHandle hWnd, int wMsg, int wParam, int lParam);
[DllImport("User32")]
public static extern int OemKeyScan(int wOemChar);
[DllImport("user32")]
public static extern ClientWindowHandle FindWindowA(string lpClassName, string lpWindowName);
/// <summary>
/// Swaps from Big to LittleEndian and vise versa
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
public static short SwapEndian(short x)
{
var y = (ushort)x;
return (short)((y >> 8) | (y << 8));
}
private static byte[] m_StringBuffer;
public static unsafe string ReadNameString(byte* buffer, int len)
{
if ((m_StringBuffer == null) || (m_StringBuffer.Length < len))
{
m_StringBuffer = new byte[20];
}
int count;
for (count = 0; count < len && *buffer != 0; ++count)
{
m_StringBuffer[count] = *buffer++;
}
return Encoding.Default.GetString(m_StringBuffer, 0, count);
}
public static string ReadNameString(byte[] buffer, int len)
{
int count;
for (count = 0; count < 20 && buffer[count] != 0; ++count)
{
;
}
return Encoding.Default.GetString(buffer, 0, count);
}
}
}

113
Ultima/ProcessStream.cs Normal file
View File

@@ -0,0 +1,113 @@
#region References
using System;
using System.IO;
#endregion
namespace Ultima
{
public abstract unsafe class ProcessStream : Stream
{
private const int ProcessAllAccess = 0x1F0FFF;
protected bool m_Open;
protected ClientProcessHandle m_Process;
protected int m_Position;
public abstract ClientProcessHandle ProcessID { get; }
public virtual bool BeginAccess()
{
if (m_Open)
{
return false;
}
m_Process = NativeMethods.OpenProcess(ProcessAllAccess, 0, ProcessID);
m_Open = true;
return true;
}
public virtual void EndAccess()
{
if (!m_Open)
{
return;
}
m_Process.Close();
m_Open = false;
}
public override void Flush()
{ }
public override int Read(byte[] buffer, int offset, int count)
{
bool end = !BeginAccess();
int res = 0;
fixed (byte* p = buffer)
{
NativeMethods.ReadProcessMemory(m_Process, m_Position, p + offset, count, ref res);
}
m_Position += count;
if (end)
{
EndAccess();
}
return res;
}
public override void Write(byte[] buffer, int offset, int count)
{
bool end = !BeginAccess();
fixed (byte* p = buffer)
{
NativeMethods.WriteProcessMemory(m_Process, m_Position, p + offset, count, 0);
}
m_Position += count;
if (end)
{
EndAccess();
}
}
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return true; } }
public override bool CanSeek { get { return true; } }
public override long Length { get { throw new NotSupportedException(); } }
public override long Position { get { return m_Position; } set { m_Position = (int)value; } }
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
m_Position = (int)offset;
break;
case SeekOrigin.Current:
m_Position += (int)offset;
break;
case SeekOrigin.End:
throw new NotSupportedException();
}
return m_Position;
}
}
}

170
Ultima/RadarCol.cs Normal file
View File

@@ -0,0 +1,170 @@
#region References
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
#endregion
namespace Ultima
{
public sealed class RadarCol
{
static RadarCol()
{
Initialize();
}
private static short[] m_Colors;
public static short[] Colors { get { return m_Colors; } }
public static short GetItemColor(int index)
{
if (index + 0x4000 < m_Colors.Length)
{
return m_Colors[index + 0x4000];
}
return 0;
}
public static short GetLandColor(int index)
{
if (index < m_Colors.Length)
{
return m_Colors[index];
}
return 0;
}
public static void SetItemColor(int index, short value)
{
m_Colors[index + 0x4000] = value;
}
public static void SetLandColor(int index, short value)
{
m_Colors[index] = value;
}
public static void Initialize()
{
string path = Files.GetFilePath("radarcol.mul");
if (path != null)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
m_Colors = new short[fs.Length / 2];
GCHandle gc = GCHandle.Alloc(m_Colors, GCHandleType.Pinned);
var buffer = new byte[(int)fs.Length];
fs.Read(buffer, 0, (int)fs.Length);
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)fs.Length);
gc.Free();
}
}
else
{
m_Colors = new short[0x8000];
}
}
public static void Save(string FileName)
{
using (var fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
for (int i = 0; i < m_Colors.Length; ++i)
{
bin.Write(m_Colors[i]);
}
}
}
}
public static void ExportToCSV(string FileName)
{
using (
var Tex = new StreamWriter(
new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite), Encoding.GetEncoding(1252)))
{
Tex.WriteLine("ID;Color");
for (int i = 0; i < m_Colors.Length; ++i)
{
Tex.WriteLine(String.Format("0x{0:X4};{1}", i, m_Colors[i]));
}
}
}
public static void ImportFromCSV(string FileName)
{
if (!File.Exists(FileName))
{
return;
}
using (var sr = new StreamReader(FileName))
{
string line;
int count = 0;
while ((line = sr.ReadLine()) != null)
{
if ((line = line.Trim()).Length == 0 || line.StartsWith("#"))
{
continue;
}
if (line.StartsWith("ID;"))
{
continue;
}
++count;
}
m_Colors = new short[count];
}
using (var sr = new StreamReader(FileName))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if ((line = line.Trim()).Length == 0 || line.StartsWith("#"))
{
continue;
}
if (line.StartsWith("ID;"))
{
continue;
}
try
{
string[] split = line.Split(';');
if (split.Length < 2)
{
continue;
}
int id = ConvertStringToInt(split[0]);
int color = ConvertStringToInt(split[1]);
m_Colors[id] = (short)color;
}
catch
{ }
}
}
}
private static int ConvertStringToInt(string text)
{
int result;
if (text.Contains("0x"))
{
string convert = text.Replace("0x", "");
int.TryParse(convert, NumberStyles.HexNumber, null, out result);
}
else
{
int.TryParse(text, NumberStyles.Integer, null, out result);
}
return result;
}
}
}

15
Ultima/Settings.cs Normal file
View File

@@ -0,0 +1,15 @@
#region References
using System.Drawing.Imaging;
#endregion
namespace Ultima
{
public static class Settings
{
#if MONO
public const PixelFormat PixelFormat = System.Drawing.Imaging.PixelFormat.Format16bppRgb555;
#else
public const PixelFormat PixelFormat = System.Drawing.Imaging.PixelFormat.Format16bppArgb1555;
#endif
}
}

151
Ultima/SkillGroups.cs Normal file
View File

@@ -0,0 +1,151 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
#endregion
namespace Ultima
{
public sealed class SkillGroup
{
public string Name { get; set; }
public SkillGroup(string name)
{
Name = name;
}
}
public sealed class SkillGroups
{
public static List<SkillGroup> List { get; private set; }
public static List<int> SkillList { get; private set; }
private static bool unicode;
static SkillGroups()
{
Initialize();
}
public static void Initialize()
{
string path = Files.GetFilePath("skillgrp.mul");
List = new List<SkillGroup>();
SkillList = new List<int>();
if (path != null)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var bin = new BinaryReader(fs))
{
int start = 4;
int strlen = 17;
int count = bin.ReadInt32();
if (count == -1)
{
unicode = true;
count = bin.ReadInt32();
start *= 2;
strlen *= 2;
}
List.Add(new SkillGroup("Misc"));
for (int i = 0; i < count - 1; ++i)
{
int strbuild;
fs.Seek((start + (i * strlen)), SeekOrigin.Begin);
var builder2 = new StringBuilder(17);
if (unicode)
{
while ((strbuild = bin.ReadInt16()) != 0)
{
builder2.Append((char)strbuild);
}
}
else
{
while ((strbuild = bin.ReadByte()) != 0)
{
builder2.Append((char)strbuild);
}
}
List.Add(new SkillGroup(builder2.ToString()));
}
fs.Seek((start + ((count - 1) * strlen)), SeekOrigin.Begin);
try
{
while (bin.BaseStream.Length != bin.BaseStream.Position)
{
SkillList.Add(bin.ReadInt32());
}
}
catch // just for safety
{ }
}
}
}
}
public static void Save(string path)
{
string mul = Path.Combine(path, "skillgrp.mul");
using (var fs = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
if (unicode)
{
bin.Write(-1);
}
bin.Write(List.Count);
foreach (SkillGroup group in List)
{
if (group.Name == "Misc")
{
continue;
}
byte[] name;
if (unicode)
{
name = new byte[34];
}
else
{
name = new byte[17];
}
if (group.Name != null)
{
if (unicode)
{
byte[] bb = Encoding.Unicode.GetBytes(group.Name);
if (bb.Length > 34)
{
Array.Resize(ref bb, 34);
}
bb.CopyTo(name, 0);
}
else
{
byte[] bb = Encoding.Default.GetBytes(group.Name);
if (bb.Length > 17)
{
Array.Resize(ref bb, 17);
}
bb.CopyTo(name, 0);
}
}
bin.Write(name);
}
foreach (int group in SkillList)
{
bin.Write(group);
}
}
}
}
}
}

169
Ultima/Skills.cs Normal file
View File

@@ -0,0 +1,169 @@
#region References
using System.Collections.Generic;
using System.IO;
using System.Text;
#endregion
namespace Ultima
{
public sealed class Skills
{
private static FileIndex m_FileIndex = new FileIndex("skills.idx", "skills.mul", 16);
private static List<SkillInfo> m_SkillEntries;
public static List<SkillInfo> SkillEntries
{
get
{
if (m_SkillEntries == null)
{
m_SkillEntries = new List<SkillInfo>();
for (int i = 0; i < m_FileIndex.Index.Length; ++i)
{
SkillInfo info = GetSkill(i);
if (info == null)
{
break;
}
m_SkillEntries.Add(info);
}
}
return m_SkillEntries;
}
set { m_SkillEntries = value; }
}
/// <summary>
/// ReReads skills.mul
/// </summary>
public static void Reload()
{
m_FileIndex = new FileIndex("skills.idx", "skills.mul", 16);
m_SkillEntries = new List<SkillInfo>();
for (int i = 0; i < m_FileIndex.Index.Length; ++i)
{
SkillInfo info = GetSkill(i);
if (info == null)
{
break;
}
m_SkillEntries.Add(info);
}
}
/// <summary>
/// Returns <see cref="SkillInfo" /> of index
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static SkillInfo GetSkill(int index)
{
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (length == 0)
{
return null;
}
using (var bin = new BinaryReader(stream))
{
bool action = bin.ReadBoolean();
string name = ReadNameString(bin, length - 1);
return new SkillInfo(index, name, action, extra);
}
}
private static readonly byte[] m_StringBuffer = new byte[1024];
private static string ReadNameString(BinaryReader bin, int length)
{
bin.Read(m_StringBuffer, 0, length);
int count;
for (count = 0; count < length && m_StringBuffer[count] != 0; ++count)
{
;
}
return Encoding.Default.GetString(m_StringBuffer, 0, count);
}
public static void Save(string path)
{
string idx = Path.Combine(path, "skills.idx");
string mul = Path.Combine(path, "skills.mul");
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (BinaryWriter binidx = new BinaryWriter(fsidx), binmul = new BinaryWriter(fsmul))
{
for (int i = 0; i < m_FileIndex.Index.Length; ++i)
{
SkillInfo skill = (i < m_SkillEntries.Count) ? m_SkillEntries[i] : null;
if (skill == null)
{
binidx.Write(-1); // lookup
binidx.Write(0); // length
binidx.Write(0); // extra
}
else
{
binidx.Write((int)fsmul.Position); //lookup
var length = (int)fsmul.Position;
binmul.Write(skill.IsAction);
byte[] namebytes = Encoding.Default.GetBytes(skill.Name);
binmul.Write(namebytes);
binmul.Write((byte)0); //nullterminated
length = (int)fsmul.Position - length;
binidx.Write(length);
binidx.Write(skill.Extra);
}
}
}
}
}
}
public sealed class SkillInfo
{
private string m_Name;
public int Index { get; set; }
public bool IsAction { get; set; }
public string Name
{
get { return m_Name; }
set
{
if (value == null)
{
m_Name = "";
}
else
{
m_Name = value;
}
}
}
public int Extra { get; private set; }
public SkillInfo(int nr, string name, bool action, int extra)
{
Index = nr;
m_Name = name;
IsAction = action;
Extra = extra;
}
}
}

371
Ultima/Sound.cs Normal file
View File

@@ -0,0 +1,371 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
#endregion
namespace Ultima
{
public sealed class UOSound
{
public string Name;
public int ID;
public byte[] buffer;
public UOSound(string name, int id, byte[] buff)
{
Name = name;
ID = id;
buffer = buff;
}
};
public static class Sounds
{
private static Dictionary<int, int> m_Translations;
private static FileIndex m_FileIndex;
private static UOSound[] m_Cache;
private static bool[] m_Removed;
static Sounds()
{
Initialize();
}
/// <summary>
/// Reads Sounds and def
/// </summary>
public static void Initialize()
{
m_Cache = new UOSound[0xFFF];
m_Removed = new bool[0xFFF];
m_FileIndex = new FileIndex("soundidx.mul", "sound.mul", "soundLegacyMUL.uop", 0xFFF, 8, ".dat", -1, false);
var reg = new Regex(@"(\d{1,3}) \x7B(\d{1,3})\x7D (\d{1,3})", RegexOptions.Compiled);
m_Translations = new Dictionary<int, int>();
string line;
string path = Files.GetFilePath("Sound.def");
if (path == null)
{
return;
}
using (var reader = new StreamReader(path))
{
while ((line = reader.ReadLine()) != null)
{
if (((line = line.Trim()).Length != 0) && !line.StartsWith("#"))
{
Match match = reg.Match(line);
if (match.Success)
{
m_Translations.Add(int.Parse(match.Groups[1].Value), int.Parse(match.Groups[2].Value));
}
}
}
}
}
/// <summary>
/// Returns <see cref="UOSound" /> of ID
/// </summary>
/// <param name="soundID"></param>
/// <returns></returns>
public static UOSound GetSound(int soundID)
{
bool translated;
return GetSound(soundID, out translated);
}
/// <summary>
/// Returns <see cref="UOSound" /> of ID with bool translated in .def
/// </summary>
/// <param name="soundID"></param>
/// <param name="translated"></param>
/// <returns></returns>
public static UOSound GetSound(int soundID, out bool translated)
{
translated = false;
if (soundID < 0)
{
return null;
}
if (m_Cache[soundID] != null)
{
return m_Cache[soundID];
}
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(soundID, out length, out extra, out patched);
if ((m_FileIndex.Index[soundID].lookup < 0) || (length <= 0))
{
if (!m_Translations.TryGetValue(soundID, out soundID))
{
return null;
}
translated = true;
stream = m_FileIndex.Seek(soundID, out length, out extra, out patched);
}
if (stream == null)
{
return null;
}
length -= 32;
int[] waveHeader = WaveHeader(length);
var stringBuffer = new byte[32];
var buffer = new byte[length];
stream.Read(stringBuffer, 0, 32);
stream.Read(buffer, 0, length);
stream.Close();
var resultBuffer = new byte[buffer.Length + (waveHeader.Length << 2)];
Buffer.BlockCopy(waveHeader, 0, resultBuffer, 0, (waveHeader.Length << 2));
Buffer.BlockCopy(buffer, 0, resultBuffer, (waveHeader.Length << 2), buffer.Length);
string str = Encoding.ASCII.GetString(stringBuffer);
// seems that the null terminator's not being properly recognized :/
if (str.IndexOf('\0') > 0)
{
str = str.Substring(0, str.IndexOf('\0'));
}
var sound = new UOSound(str, soundID, resultBuffer);
if (Files.CacheData)
{
if (!translated) // no .def definition
{
m_Cache[soundID] = sound;
}
}
return sound;
}
private static int[] WaveHeader(int length)
{
/* ====================
* = WAVE File layout =
* ====================
* char[4] = 'RIFF' \
* int - chunk size |- Riff Header
* char[4] = 'WAVE' /
* char[4] = 'fmt ' \
* int - chunk size |
* short - format |
* short - channels |
* int - samples p/s|- Format header
* int - avg bytes |
* short - align |
* short - bits p/s /
* char[4] - data \
* int - chunk size | - Data header
* short[..] - data /
* ====================
* */
return new[]
{0x46464952, (length + 36), 0x45564157, 0x20746D66, 0x10, 0x010001, 0x5622, 0xAC44, 0x100002, 0x61746164, length};
}
/// <summary>
/// Returns Soundname and tests if valid
/// </summary>
/// <param name="soundID"></param>
/// <returns></returns>
public static bool IsValidSound(int soundID, out string name)
{
name = "";
if (soundID < 0)
{
return false;
}
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(soundID, out length, out extra, out patched);
if ((m_FileIndex.Index[soundID].lookup < 0) || (length <= 0))
{
if (!m_Translations.TryGetValue(soundID, out soundID))
{
return false;
}
stream = m_FileIndex.Seek(soundID, out length, out extra, out patched);
}
if (stream == null)
{
return false;
}
var stringBuffer = new byte[32];
stream.Read(stringBuffer, 0, 32);
stream.Close();
name = Encoding.ASCII.GetString(stringBuffer); // seems that the null terminator's not being properly recognized :/
if (name.IndexOf('\0') > 0)
{
name = name.Substring(0, name.IndexOf('\0'));
}
return true;
}
/// <summary>
/// Returns length of SoundID
/// </summary>
/// <param name="soundID"></param>
/// <returns></returns>
public static double GetSoundLength(int soundID)
{
if (soundID < 0)
{
return 0;
}
double len;
if (m_Cache[soundID] != null)
{
len = m_Cache[soundID].buffer.Length;
len -= 44; //wavheaderlength
}
else
{
int length, extra;
bool patched;
Stream stream = m_FileIndex.Seek(soundID, out length, out extra, out patched);
if ((m_FileIndex.Index[soundID].lookup < 0) || (length <= 0))
{
if (!m_Translations.TryGetValue(soundID, out soundID))
{
return 0;
}
stream = m_FileIndex.Seek(soundID, out length, out extra, out patched);
}
if (stream == null)
{
return 0;
}
stream.Close();
length -= 32; //mulheaderlength
len = length;
}
len /= 0x5622; // Sample Rate
len /= 2;
return len;
}
public static void Add(int id, string name, string file)
{
using (var wav = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var resultBuffer = new byte[wav.Length];
wav.Seek(0, SeekOrigin.Begin);
wav.Read(resultBuffer, 0, (int)wav.Length);
m_Cache[id] = new UOSound(name, id, resultBuffer);
m_Removed[id] = false;
}
}
public static void Remove(int id)
{
m_Removed[id] = true;
m_Cache[id] = null;
}
public static void Save(string path)
{
string idx = Path.Combine(path, "soundidx.mul");
string mul = Path.Combine(path, "sound.mul");
int Headerlength = 44;
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (BinaryWriter binidx = new BinaryWriter(fsidx), binmul = new BinaryWriter(fsmul))
{
for (int i = 0; i < m_Cache.Length; ++i)
{
UOSound sound = m_Cache[i];
if ((sound == null) && (!m_Removed[i]))
{
bool trans;
sound = GetSound(i, out trans);
if (!trans)
{
m_Cache[i] = sound;
}
else
{
sound = null;
}
}
if ((sound == null) || (m_Removed[i]))
{
binidx.Write(-1); // lookup
binidx.Write(-1); // length
binidx.Write(-1); // extra
}
else
{
binidx.Write((int)fsmul.Position); //lookup
var length = (int)fsmul.Position;
var b = new byte[32];
if (sound.Name != null)
{
byte[] bb = Encoding.Default.GetBytes(sound.Name);
if (bb.Length > 32)
{
Array.Resize(ref bb, 32);
}
bb.CopyTo(b, 0);
}
binmul.Write(b);
using (var m = new MemoryStream(sound.buffer))
{
m.Seek(Headerlength, SeekOrigin.Begin);
var resultBuffer = new byte[m.Length - Headerlength];
m.Read(resultBuffer, 0, (int)m.Length - Headerlength);
binmul.Write(resultBuffer);
}
length = (int)fsmul.Position - length;
binidx.Write(length);
binidx.Write(i + 1);
}
}
}
}
}
public static void SaveSoundListToCSV(string FileName)
{
using (
var Tex = new StreamWriter(
new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite), Encoding.GetEncoding(1252)))
{
Tex.WriteLine("ID;Name;Length");
string name = "";
for (int i = 1; i <= 0xFFF; ++i)
{
if (IsValidSound(i - 1, out name))
{
Tex.Write(String.Format("0x{0:X3}", i));
Tex.Write(String.Format(";{0}", name));
Tex.WriteLine(String.Format(";{0:f}", GetSoundLength(i - 1)));
}
}
}
}
}
}

249
Ultima/SpeechList.cs Normal file
View File

@@ -0,0 +1,249 @@
#region References
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
#endregion
namespace Ultima
{
public sealed class SpeechList
{
public static List<SpeechEntry> Entries { get; set; }
private static readonly byte[] m_Buffer = new byte[128];
static SpeechList()
{
Initialize();
}
/// <summary>
/// Loads speech.mul in <see cref="SpeechList.Entries" />
/// </summary>
public static void Initialize()
{
string path = Files.GetFilePath("speech.mul");
if (path == null)
{
Entries = new List<SpeechEntry>(0);
return;
}
Entries = new List<SpeechEntry>();
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var buffer = new byte[fs.Length];
unsafe
{
int order = 0;
fs.Read(buffer, 0, buffer.Length);
fixed (byte* data = buffer)
{
byte* bindat = data;
byte* bindatend = bindat + buffer.Length;
while (bindat != bindatend)
{
var id = (short)((*bindat++ >> 8) | (*bindat++)); //Swapped Endian
var length = (short)((*bindat++ >> 8) | (*bindat++));
if (length > 128)
{
length = 128;
}
for (int i = 0; i < length; ++i)
{
m_Buffer[i] = *bindat++;
}
string keyword = Encoding.UTF8.GetString(m_Buffer, 0, length);
Entries.Add(new SpeechEntry(id, keyword, order));
++order;
}
}
}
}
}
/// <summary>
/// Saves speech.mul to <see cref="FileName" />
/// </summary>
/// <param name="FileName"></param>
public static void SaveSpeechList(string FileName)
{
Entries.Sort(new OrderComparer());
using (var fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
foreach (SpeechEntry entry in Entries)
{
bin.Write(NativeMethods.SwapEndian(entry.ID));
byte[] utf8String = Encoding.UTF8.GetBytes(entry.KeyWord);
var length = (short)utf8String.Length;
bin.Write(NativeMethods.SwapEndian(length));
bin.Write(utf8String);
}
}
}
}
public static void ExportToCSV(string FileName)
{
using (var Tex = new StreamWriter(new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite), Encoding.Unicode))
{
Tex.WriteLine("Order;ID;KeyWord");
foreach (SpeechEntry entry in Entries)
{
Tex.WriteLine(String.Format("{0};{1};{2}", entry.Order, entry.ID, entry.KeyWord));
}
}
}
public static void ImportFromCSV(string FileName)
{
Entries = new List<SpeechEntry>(0);
if (!File.Exists(FileName))
{
return;
}
using (var sr = new StreamReader(FileName))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if ((line = line.Trim()).Length == 0 || line.StartsWith("#"))
{
continue;
}
if ((line.Contains("Order")) && (line.Contains("KeyWord")))
{
continue;
}
try
{
string[] split = line.Split(';');
if (split.Length < 3)
{
continue;
}
int order = ConvertStringToInt(split[0]);
int id = ConvertStringToInt(split[1]);
string word = split[2];
word = word.Replace("\"", "");
Entries.Add(new SpeechEntry((short)id, word, order));
}
catch
{ }
}
}
}
public static int ConvertStringToInt(string text)
{
int result;
if (text.Contains("0x"))
{
string convert = text.Replace("0x", "");
int.TryParse(convert, NumberStyles.HexNumber, null, out result);
}
else
{
int.TryParse(text, NumberStyles.Integer, null, out result);
}
return result;
}
#region SortComparer
public class IDComparer : IComparer<SpeechEntry>
{
private readonly bool m_desc;
public IDComparer(bool desc)
{
m_desc = desc;
}
public int Compare(SpeechEntry objA, SpeechEntry objB)
{
if (objA.ID == objB.ID)
{
return 0;
}
else if (m_desc)
{
return (objA.ID < objB.ID) ? 1 : -1;
}
else
{
return (objA.ID < objB.ID) ? -1 : 1;
}
}
}
public class KeyWordComparer : IComparer<SpeechEntry>
{
private readonly bool m_desc;
public KeyWordComparer(bool desc)
{
m_desc = desc;
}
public int Compare(SpeechEntry objA, SpeechEntry objB)
{
if (m_desc)
{
return String.Compare(objB.KeyWord, objA.KeyWord);
}
else
{
return String.Compare(objA.KeyWord, objB.KeyWord);
}
}
}
public class OrderComparer : IComparer<SpeechEntry>
{
public int Compare(SpeechEntry objA, SpeechEntry objB)
{
if (objA.Order == objB.Order)
{
return 0;
}
else
{
return (objA.Order < objB.Order) ? -1 : 1;
}
}
}
#endregion
}
public sealed class SpeechEntry
{
public short ID { get; set; }
public string KeyWord { get; set; }
[Browsable(false)]
public int Order { get; private set; }
public SpeechEntry(short id, string keyword, int order)
{
ID = id;
KeyWord = keyword;
Order = order;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SpeechMul
{
public short id;
public short length;
public byte[] keyword;
}
}

100
Ultima/StringEntry.cs Normal file
View File

@@ -0,0 +1,100 @@
#region References
using System;
using System.Text.RegularExpressions;
#endregion
namespace Ultima
{
public sealed class StringEntry
{
[Flags]
public enum CliLocFlag
{
Original = 0x0,
Custom = 0x1,
Modified = 0x2
}
private string m_Text;
public int Number { get; private set; }
public string Text
{
get { return m_Text; }
set
{
if (value == null)
{
m_Text = "";
}
else
{
m_Text = value;
}
}
}
public CliLocFlag Flag { get; set; }
public StringEntry(int number, string text, byte flag)
{
Number = number;
m_Text = text;
Flag = (CliLocFlag)flag;
}
public StringEntry(int number, string text, CliLocFlag flag)
{
Number = number;
m_Text = text;
Flag = flag;
}
// Razor
private static readonly Regex m_RegEx = new Regex(
@"~(\d+)[_\w]+~",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant);
private string m_FmtTxt;
private static readonly object[] m_Args = new object[] {"", "", "", "", "", "", "", "", "", "", ""};
public string Format(params object[] args)
{
if (m_FmtTxt == null)
{
m_FmtTxt = m_RegEx.Replace(m_Text, @"{$1}");
}
for (int i = 0; i < args.Length && i < 10; i++)
{
m_Args[i + 1] = args[i];
}
return String.Format(m_FmtTxt, m_Args);
}
public string SplitFormat(string argstr)
{
if (m_FmtTxt == null)
{
m_FmtTxt = m_RegEx.Replace(m_Text, @"{$1}");
}
string[] args = argstr.Split('\t'); // adds an extra on to the args array
for (int i = 0; i < args.Length && i < 10; i++)
{
m_Args[i + 1] = args[i];
}
return String.Format(m_FmtTxt, m_Args);
/*
{
StringBuilder sb = new StringBuilder();
sb.Append( m_FmtTxt );
for(int i=0;i<args.Length;i++)
{
sb.Append( "|" );
sb.Append( args[i] == null ? "-null-" : args[i] );
}
throw new Exception( sb.ToString() );
}*/
}
}
}

217
Ultima/StringList.cs Normal file
View File

@@ -0,0 +1,217 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
#endregion
namespace Ultima
{
public sealed class StringList
{
private int m_Header1;
private short m_Header2;
public List<StringEntry> Entries { get; set; }
public string Language { get; private set; }
private Dictionary<int, string> m_StringTable;
private Dictionary<int, StringEntry> m_EntryTable;
private static byte[] m_Buffer = new byte[1024];
/// <summary>
/// Initialize <see cref="StringList" /> of Language
/// </summary>
/// <param name="language"></param>
public StringList(string language)
{
Language = language;
LoadEntry(Files.GetFilePath(String.Format("cliloc.{0}", language)));
}
/// <summary>
/// Initialize <see cref="StringList" /> of Language from path
/// </summary>
/// <param name="language"></param>
/// <param name="path"></param>
public StringList(string language, string path)
{
Language = language;
LoadEntry(path);
}
private void LoadEntry(string path)
{
if (path == null)
{
Entries = new List<StringEntry>(0);
return;
}
Entries = new List<StringEntry>();
m_StringTable = new Dictionary<int, string>();
m_EntryTable = new Dictionary<int, StringEntry>();
using (var bin = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
m_Header1 = bin.ReadInt32();
m_Header2 = bin.ReadInt16();
while (bin.BaseStream.Length != bin.BaseStream.Position)
{
int number = bin.ReadInt32();
byte flag = bin.ReadByte();
int length = bin.ReadInt16();
if (length > m_Buffer.Length)
{
m_Buffer = new byte[(length + 1023) & ~1023];
}
bin.Read(m_Buffer, 0, length);
string text = Encoding.UTF8.GetString(m_Buffer, 0, length);
var se = new StringEntry(number, text, flag);
Entries.Add(se);
m_StringTable[number] = text;
m_EntryTable[number] = se;
}
}
}
/// <summary>
/// Saves <see cref="SaveStringList" /> to FileName
/// </summary>
/// <param name="FileName"></param>
public void SaveStringList(string FileName)
{
using (var fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
bin.Write(m_Header1);
bin.Write(m_Header2);
Entries.Sort(new NumberComparer(false));
foreach (StringEntry entry in Entries)
{
bin.Write(entry.Number);
bin.Write((byte)entry.Flag);
byte[] utf8String = Encoding.UTF8.GetBytes(entry.Text);
var length = (ushort)utf8String.Length;
bin.Write(length);
bin.Write(utf8String);
}
}
}
}
public string GetString(int number)
{
if (m_StringTable == null || !m_StringTable.ContainsKey(number))
{
return null;
}
return m_StringTable[number];
}
public StringEntry GetEntry(int number)
{
if (m_EntryTable == null || !m_EntryTable.ContainsKey(number))
{
return null;
}
return m_EntryTable[number];
}
#region SortComparer
public class NumberComparer : IComparer<StringEntry>
{
private readonly bool m_desc;
public NumberComparer(bool desc)
{
m_desc = desc;
}
public int Compare(StringEntry objA, StringEntry objB)
{
if (objA.Number == objB.Number)
{
return 0;
}
else if (m_desc)
{
return (objA.Number < objB.Number) ? 1 : -1;
}
else
{
return (objA.Number < objB.Number) ? -1 : 1;
}
}
}
public class FlagComparer : IComparer<StringEntry>
{
private readonly bool m_desc;
public FlagComparer(bool desc)
{
m_desc = desc;
}
public int Compare(StringEntry objA, StringEntry objB)
{
if ((byte)objA.Flag == (byte)objB.Flag)
{
if (objA.Number == objB.Number)
{
return 0;
}
else if (m_desc)
{
return (objA.Number < objB.Number) ? 1 : -1;
}
else
{
return (objA.Number < objB.Number) ? -1 : 1;
}
}
else if (m_desc)
{
return ((byte)objA.Flag < (byte)objB.Flag) ? 1 : -1;
}
else
{
return ((byte)objA.Flag < (byte)objB.Flag) ? -1 : 1;
}
}
}
public class TextComparer : IComparer<StringEntry>
{
private readonly bool m_desc;
public TextComparer(bool desc)
{
m_desc = desc;
}
public int Compare(StringEntry objA, StringEntry objB)
{
if (m_desc)
{
return String.Compare(objB.Text, objA.Text);
}
else
{
return String.Compare(objA.Text, objB.Text);
}
}
}
#endregion
}
}

302
Ultima/Textures.cs Normal file
View File

@@ -0,0 +1,302 @@
#region References
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Security.Cryptography;
#endregion
namespace Ultima
{
public sealed class Textures
{
private static FileIndex m_FileIndex = new FileIndex("Texidx.mul", "Texmaps.mul", 0x4000, 10);
private static Bitmap[] m_Cache = new Bitmap[0x4000];
private static bool[] m_Removed = new bool[0x4000];
private static readonly Hashtable m_patched = new Hashtable();
private static byte[] m_StreamBuffer;
private struct CheckSums
{
public byte[] checksum;
public int pos;
public int length;
public int index;
}
private static List<CheckSums> checksums;
/// <summary>
/// ReReads texmaps
/// </summary>
public static void Reload()
{
m_FileIndex = new FileIndex("Texidx.mul", "Texmaps.mul", 0x4000, 10);
m_Cache = new Bitmap[0x4000];
m_Removed = new bool[0x4000];
m_patched.Clear();
}
public static int GetIdxLength()
{
return (int)(m_FileIndex.IdxLength / 12);
}
/// <summary>
/// Removes Texture <see cref="m_Removed" />
/// </summary>
/// <param name="index"></param>
public static void Remove(int index)
{
m_Removed[index] = true;
}
/// <summary>
/// Replaces Texture
/// </summary>
/// <param name="index"></param>
/// <param name="bmp"></param>
public static void Replace(int index, Bitmap bmp)
{
m_Cache[index] = bmp;
m_Removed[index] = false;
if (m_patched.Contains(index))
{
m_patched.Remove(index);
}
}
/// <summary>
/// Tests if index is valid Texture
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static bool TestTexture(int index)
{
int length, extra;
bool patched;
if (m_Removed[index])
{
return false;
}
if (m_Cache[index] != null)
{
return true;
}
bool valid = m_FileIndex.Valid(index, out length, out extra, out patched);
if ((!valid) || (length == 0))
{
return false;
}
return true;
}
/// <summary>
/// Returns Bitmap of Texture
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public static Bitmap GetTexture(int index)
{
bool patched;
return GetTexture(index, out patched);
}
/// <summary>
/// Returns Bitmap of Texture with verdata bool
/// </summary>
/// <param name="index"></param>
/// <param name="patched"></param>
/// <returns></returns>
public static unsafe Bitmap GetTexture(int index, out bool patched)
{
if (m_patched.Contains(index))
{
patched = (bool)m_patched[index];
}
else
{
patched = false;
}
if (m_Removed[index])
{
return null;
}
if (m_Cache[index] != null)
{
return m_Cache[index];
}
int length, extra;
Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched);
if (stream == null)
{
return null;
}
if (length == 0)
{
return null;
}
if (patched)
{
m_patched[index] = true;
}
int size = extra == 0 ? 64 : 128;
var bmp = new Bitmap(size, size, Settings.PixelFormat);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, size, size), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
int max = size * size * 2;
if (m_StreamBuffer == null || m_StreamBuffer.Length < max)
{
m_StreamBuffer = new byte[max];
}
stream.Read(m_StreamBuffer, 0, max);
fixed (byte* data = m_StreamBuffer)
{
var bindat = (ushort*)data;
for (int y = 0; y < size; ++y, line += delta)
{
ushort* cur = line;
ushort* end = cur + size;
while (cur < end)
{
*cur++ = (ushort)(*bindat++ ^ 0x8000);
}
}
}
bmp.UnlockBits(bd);
stream.Close();
if (!Files.CacheData)
{
return m_Cache[index] = bmp;
}
else
{
return bmp;
}
}
public static unsafe void Save(string path)
{
string idx = Path.Combine(path, "texidx.mul");
string mul = Path.Combine(path, "texmaps.mul");
checksums = new List<CheckSums>();
using (
FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
{
var memidx = new MemoryStream();
var memmul = new MemoryStream();
using (BinaryWriter binidx = new BinaryWriter(memidx), binmul = new BinaryWriter(memmul))
{
var sha = new SHA256Managed();
//StreamWriter Tex = new StreamWriter(new FileStream("d:/texlog.txt", FileMode.Create, FileAccess.ReadWrite));
for (int index = 0; index < GetIdxLength(); ++index)
{
if (m_Cache[index] == null)
{
m_Cache[index] = GetTexture(index);
}
Bitmap bmp = m_Cache[index];
if ((bmp == null) || (m_Removed[index]))
{
binidx.Write(-1); // lookup
binidx.Write(0); // length
binidx.Write(-1); // extra
}
else
{
var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Bmp);
byte[] checksum = sha.ComputeHash(ms.ToArray());
CheckSums sum;
if (compareSaveImages(checksum, out sum))
{
binidx.Write(sum.pos); //lookup
binidx.Write(sum.length);
binidx.Write(0);
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, (int)sum.pos, (int)sum.length));
//Tex.WriteLine(System.String.Format("0x{0:X4} -> 0x{1:X4}", sum.index, index));
continue;
}
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
binidx.Write((int)binmul.BaseStream.Position); //lookup
var length = (int)binmul.BaseStream.Position;
for (int Y = 0; Y < bmp.Height; ++Y, line += delta)
{
ushort* cur = line;
for (int X = 0; X < bmp.Width; ++X)
{
binmul.Write((ushort)(cur[X] ^ 0x8000));
}
}
int start = length;
length = (int)binmul.BaseStream.Position - length;
binidx.Write(length);
binidx.Write((bmp.Width == 64 ? 0 : 1));
bmp.UnlockBits(bd);
var s = new CheckSums
{
pos = start,
length = length,
checksum = checksum,
index = index
};
//Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, start, length));
checksums.Add(s);
}
}
memidx.WriteTo(fsidx);
memmul.WriteTo(fsmul);
}
}
}
private static bool compareSaveImages(byte[] newchecksum, out CheckSums sum)
{
sum = new CheckSums();
for (int i = 0; i < checksums.Count; ++i)
{
byte[] cmp = checksums[i].checksum;
if (((cmp == null) || (newchecksum == null)) || (cmp.Length != newchecksum.Length))
{
return false;
}
bool valid = true;
for (int j = 0; j < cmp.Length; ++j)
{
if (cmp[j] != newchecksum[j])
{
valid = false;
break;
}
}
if (valid)
{
sum = checksums[i];
return true;
}
}
return false;
}
}
}

1295
Ultima/TileData.cs Normal file

File diff suppressed because it is too large Load Diff

139
Ultima/TileList.cs Normal file
View File

@@ -0,0 +1,139 @@
#region References
using System.Collections.Generic;
#endregion
namespace Ultima
{
public sealed class HuedTileList
{
private readonly List<HuedTile> m_Tiles;
public HuedTileList()
{
m_Tiles = new List<HuedTile>();
}
public int Count { get { return m_Tiles.Count; } }
public void Add(ushort id, short hue, sbyte z)
{
m_Tiles.Add(new HuedTile(id, hue, z));
}
public HuedTile[] ToArray()
{
var tiles = new HuedTile[Count];
if (m_Tiles.Count > 0)
{
m_Tiles.CopyTo(tiles);
}
m_Tiles.Clear();
return tiles;
}
}
public sealed class TileList
{
private readonly List<Tile> m_Tiles;
public TileList()
{
m_Tiles = new List<Tile>();
}
public int Count { get { return m_Tiles.Count; } }
public void Add(ushort id, sbyte z)
{
m_Tiles.Add(new Tile(id, z));
}
public void Add(ushort id, sbyte z, sbyte flag)
{
m_Tiles.Add(new Tile(id, z, flag));
}
public Tile[] ToArray()
{
var tiles = new Tile[Count];
if (m_Tiles.Count > 0)
{
m_Tiles.CopyTo(tiles);
}
m_Tiles.Clear();
return tiles;
}
public Tile Get(int i)
{
return m_Tiles[i];
}
}
public sealed class MTileList
{
private readonly List<MTile> m_Tiles;
public MTileList()
{
m_Tiles = new List<MTile>();
}
public int Count { get { return m_Tiles.Count; } }
public void Add(ushort id, sbyte z)
{
m_Tiles.Add(new MTile(id, z));
}
public void Add(ushort id, sbyte z, TileFlag flag)
{
m_Tiles.Add(new MTile(id, z, flag));
}
public MTile[] ToArray()
{
var tiles = new MTile[Count];
if (m_Tiles.Count > 0)
{
m_Tiles.CopyTo(tiles);
}
m_Tiles.Clear();
return tiles;
}
public MTile Get(int i)
{
return m_Tiles[i];
}
public void Set(int i, ushort id, sbyte z)
{
if (i < Count)
{
m_Tiles[i].Set(id, z);
}
}
public void Set(int i, ushort id, sbyte z, TileFlag flag)
{
if (i < Count)
{
m_Tiles[i].Set(id, z, flag);
}
}
public void Remove(int i)
{
if (i < Count)
{
m_Tiles.RemoveAt(i);
}
}
}
}

902
Ultima/TileMatrix.cs Normal file
View File

@@ -0,0 +1,902 @@
#region References
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
#endregion
namespace Ultima
{
public sealed class TileMatrix
{
private readonly HuedTile[][][][][] m_StaticTiles;
private readonly Tile[][][] m_LandTiles;
private bool[][] m_RemovedStaticBlock;
private List<StaticTile>[][] m_StaticTiles_ToAdd;
public static Tile[] InvalidLandBlock { get; private set; }
public static HuedTile[][][] EmptyStaticBlock { get; private set; }
private FileStream m_Map;
private BinaryReader m_UOPReader;
private FileStream m_Statics;
private Entry3D[] m_StaticIndex;
public Entry3D[] StaticIndex
{
get
{
if (!StaticIndexInit)
{
InitStatics();
}
return m_StaticIndex;
}
}
public bool StaticIndexInit;
public TileMatrixPatch Patch { get; private set; }
public int BlockWidth { get; private set; }
public int BlockHeight { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
private readonly string mapPath;
private readonly string indexPath;
private readonly string staticsPath;
public void CloseStreams()
{
if (m_Map != null)
{
m_Map.Close();
}
if (m_UOPReader != null)
{
m_UOPReader.Close();
}
if (m_Statics != null)
{
m_Statics.Close();
}
}
public TileMatrix(int fileIndex, int mapID, int width, int height, string path)
{
Width = width;
Height = height;
BlockWidth = width >> 3;
BlockHeight = height >> 3;
if (path == null)
{
mapPath = Files.GetFilePath("map{0}.mul", fileIndex);
if (String.IsNullOrEmpty(mapPath) || !File.Exists(mapPath))
{
mapPath = Files.GetFilePath("map{0}LegacyMUL.uop", fileIndex);
}
if (mapPath != null && mapPath.EndsWith(".uop"))
{
IsUOPFormat = true;
}
}
else
{
mapPath = Path.Combine(path, String.Format("map{0}.mul", fileIndex));
if (!File.Exists(mapPath))
{
mapPath = Path.Combine(path, String.Format("map{0}LegacyMUL.uop", fileIndex));
}
if (!File.Exists(mapPath))
{
mapPath = null;
}
else if (mapPath != null && mapPath.EndsWith(".uop"))
{
IsUOPFormat = true;
}
}
if (path == null)
{
indexPath = Files.GetFilePath("staidx{0}.mul", fileIndex);
}
else
{
indexPath = Path.Combine(path, String.Format("staidx{0}.mul", fileIndex));
if (!File.Exists(indexPath))
{
indexPath = null;
}
}
if (path == null)
{
staticsPath = Files.GetFilePath("statics{0}.mul", fileIndex);
}
else
{
staticsPath = Path.Combine(path, String.Format("statics{0}.mul", fileIndex));
if (!File.Exists(staticsPath))
{
staticsPath = null;
}
}
EmptyStaticBlock = new HuedTile[8][][];
for (int i = 0; i < 8; ++i)
{
EmptyStaticBlock[i] = new HuedTile[8][];
for (int j = 0; j < 8; ++j)
{
EmptyStaticBlock[i][j] = new HuedTile[0];
}
}
InvalidLandBlock = new Tile[196];
m_LandTiles = new Tile[BlockWidth][][];
m_StaticTiles = new HuedTile[BlockWidth][][][][];
Patch = new TileMatrixPatch(this, mapID, path);
}
public void SetStaticBlock(int x, int y, HuedTile[][][] value)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return;
}
if (m_StaticTiles[x] == null)
{
m_StaticTiles[x] = new HuedTile[BlockHeight][][][];
}
m_StaticTiles[x][y] = value;
}
public HuedTile[][][] GetStaticBlock(int x, int y)
{
return GetStaticBlock(x, y, true);
}
public HuedTile[][][] GetStaticBlock(int x, int y, bool patch)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return EmptyStaticBlock;
}
if (m_StaticTiles[x] == null)
{
m_StaticTiles[x] = new HuedTile[BlockHeight][][][];
}
HuedTile[][][] tiles = m_StaticTiles[x][y];
if (tiles == null)
{
tiles = m_StaticTiles[x][y] = ReadStaticBlock(x, y);
}
if ((Map.UseDiff) && (patch))
{
if (Patch.StaticBlocksCount > 0)
{
if (Patch.StaticBlocks[x] != null)
{
if (Patch.StaticBlocks[x][y] != null)
{
tiles = Patch.StaticBlocks[x][y];
}
}
}
}
return tiles;
}
public HuedTile[] GetStaticTiles(int x, int y, bool patch)
{
return GetStaticBlock(x >> 3, y >> 3, patch)[x & 0x7][y & 0x7];
}
public HuedTile[] GetStaticTiles(int x, int y)
{
return GetStaticBlock(x >> 3, y >> 3)[x & 0x7][y & 0x7];
}
public void SetLandBlock(int x, int y, Tile[] value)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return;
}
if (m_LandTiles[x] == null)
{
m_LandTiles[x] = new Tile[BlockHeight][];
}
m_LandTiles[x][y] = value;
}
public Tile[] GetLandBlock(int x, int y)
{
return GetLandBlock(x, y, true);
}
public Tile[] GetLandBlock(int x, int y, bool patch)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return InvalidLandBlock;
}
if (m_LandTiles[x] == null)
{
m_LandTiles[x] = new Tile[BlockHeight][];
}
Tile[] tiles = m_LandTiles[x][y];
if (tiles == null)
{
tiles = m_LandTiles[x][y] = ReadLandBlock(x, y);
}
if ((Map.UseDiff) && (patch))
{
if (Patch.LandBlocksCount > 0)
{
if (Patch.LandBlocks[x] != null)
{
if (Patch.LandBlocks[x][y] != null)
{
tiles = Patch.LandBlocks[x][y];
}
}
}
}
return tiles;
}
public Tile GetLandTile(int x, int y, bool patch)
{
return GetLandBlock(x >> 3, y >> 3, patch)[((y & 0x7) << 3) + (x & 0x7)];
}
public Tile GetLandTile(int x, int y)
{
return GetLandBlock(x >> 3, y >> 3)[((y & 0x7) << 3) + (x & 0x7)];
}
private void InitStatics()
{
m_StaticIndex = new Entry3D[BlockHeight * BlockWidth];
if (indexPath == null)
{
return;
}
using (var index = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
m_Statics = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.Read);
var count = (int)(index.Length / 12);
GCHandle gc = GCHandle.Alloc(m_StaticIndex, GCHandleType.Pinned);
var buffer = new byte[index.Length];
index.Read(buffer, 0, (int)index.Length);
Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)Math.Min(index.Length, BlockHeight * BlockWidth * 12));
gc.Free();
for (var i = (int)Math.Min(index.Length, BlockHeight * BlockWidth); i < BlockHeight * BlockWidth; ++i)
{
m_StaticIndex[i].lookup = -1;
m_StaticIndex[i].length = -1;
m_StaticIndex[i].extra = -1;
}
StaticIndexInit = true;
}
}
private static HuedTileList[][] m_Lists;
private static byte[] m_Buffer;
private unsafe HuedTile[][][] ReadStaticBlock(int x, int y)
{
try
{
if (!StaticIndexInit)
{
InitStatics();
}
if (m_Statics == null || !m_Statics.CanRead || !m_Statics.CanSeek)
{
if (staticsPath == null)
{
m_Statics = null;
}
else
{
m_Statics = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
}
if (m_Statics == null)
{
return EmptyStaticBlock;
}
int lookup = m_StaticIndex[(x * BlockHeight) + y].lookup;
int length = m_StaticIndex[(x * BlockHeight) + y].length;
if (lookup < 0 || length <= 0)
{
return EmptyStaticBlock;
}
else
{
int count = length / 7;
m_Statics.Seek(lookup, SeekOrigin.Begin);
if (m_Buffer == null || m_Buffer.Length < length)
{
m_Buffer = new byte[length];
}
GCHandle gc = GCHandle.Alloc(m_Buffer, GCHandleType.Pinned);
try
{
m_Statics.Read(m_Buffer, 0, length);
if (m_Lists == null)
{
m_Lists = new HuedTileList[8][];
for (int i = 0; i < 8; ++i)
{
m_Lists[i] = new HuedTileList[8];
for (int j = 0; j < 8; ++j)
{
m_Lists[i][j] = new HuedTileList();
}
}
}
HuedTileList[][] lists = m_Lists;
for (int i = 0; i < count; ++i)
{
var ptr = new IntPtr((long)gc.AddrOfPinnedObject() + i * sizeof(StaticTile));
var cur = (StaticTile)Marshal.PtrToStructure(ptr, typeof(StaticTile));
lists[cur.m_X & 0x7][cur.m_Y & 0x7].Add(Art.GetLegalItemID(cur.m_ID), cur.m_Hue, cur.m_Z);
}
var tiles = new HuedTile[8][][];
for (int i = 0; i < 8; ++i)
{
tiles[i] = new HuedTile[8][];
for (int j = 0; j < 8; ++j)
{
tiles[i][j] = lists[i][j].ToArray();
}
}
return tiles;
}
finally
{
gc.Free();
}
}
}
finally
{
//if (m_Statics != null)
// m_Statics.Close();
}
}
/* UOP map files support code, written by Wyatt (c) www.ruosi.org
* It's not possible if some entry has unknown hash. Throwed exception
* means that EA changed maps UOPs again.
*/
#region UOP
public bool IsUOPFormat { get; set; }
public bool IsUOPAlreadyRead { get; set; }
private struct UOPFile
{
public readonly long Offset;
public readonly int Length;
public UOPFile(long offset, int length)
{
Offset = offset;
Length = length;
}
}
private UOPFile[] UOPFiles { get; set; }
private long UOPLength { get { return m_Map.Length; } }
private void ReadUOPFiles(string pattern)
{
m_UOPReader = new BinaryReader(m_Map);
m_UOPReader.BaseStream.Seek(0, SeekOrigin.Begin);
if (m_UOPReader.ReadInt32() != 0x50594D)
{
throw new ArgumentException("Bad UOP file.");
}
m_UOPReader.ReadInt64(); // version + signature
long nextBlock = m_UOPReader.ReadInt64();
m_UOPReader.ReadInt32(); // block capacity
int count = m_UOPReader.ReadInt32();
UOPFiles = new UOPFile[count];
var hashes = new Dictionary<ulong, int>();
for (int i = 0; i < count; i++)
{
string file = string.Format("build/{0}/{1:D8}.dat", pattern, i);
ulong hash = FileIndex.HashFileName(file);
if (!hashes.ContainsKey(hash))
{
hashes.Add(hash, i);
}
}
m_UOPReader.BaseStream.Seek(nextBlock, SeekOrigin.Begin);
do
{
int filesCount = m_UOPReader.ReadInt32();
nextBlock = m_UOPReader.ReadInt64();
for (int i = 0; i < filesCount; i++)
{
long offset = m_UOPReader.ReadInt64();
int headerLength = m_UOPReader.ReadInt32();
int compressedLength = m_UOPReader.ReadInt32();
int decompressedLength = m_UOPReader.ReadInt32();
ulong hash = m_UOPReader.ReadUInt64();
m_UOPReader.ReadUInt32(); // Adler32
short flag = m_UOPReader.ReadInt16();
int length = flag == 1 ? compressedLength : decompressedLength;
if (offset == 0)
{
continue;
}
int idx;
if (hashes.TryGetValue(hash, out idx))
{
if (idx < 0 || idx > UOPFiles.Length)
{
throw new IndexOutOfRangeException("hashes dictionary and files collection have different count of entries!");
}
UOPFiles[idx] = new UOPFile(offset + headerLength, length);
}
else
{
throw new ArgumentException(
string.Format("File with hash 0x{0:X8} was not found in hashes dictionary! EA Mythic changed UOP format!", hash));
}
}
}
while (m_UOPReader.BaseStream.Seek(nextBlock, SeekOrigin.Begin) != 0);
}
private long CalculateOffsetFromUOP(long offset)
{
long pos = 0;
foreach (UOPFile t in UOPFiles)
{
long currPos = pos + t.Length;
if (offset < currPos)
{
return t.Offset + (offset - pos);
}
pos = currPos;
}
return UOPLength;
}
#endregion
private Tile[] ReadLandBlock(int x, int y)
{
if (m_Map == null || !m_Map.CanRead || !m_Map.CanSeek)
{
if (mapPath == null)
{
m_Map = null;
}
else
{
m_Map = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
if (IsUOPFormat && mapPath != null && !IsUOPAlreadyRead)
{
var fi = new FileInfo(mapPath);
string uopPattern = fi.Name.Replace(fi.Extension, "").ToLowerInvariant();
ReadUOPFiles(uopPattern);
IsUOPAlreadyRead = true;
}
}
var tiles = new Tile[64];
if (m_Map != null)
{
long offset = ((x * BlockHeight) + y) * 196 + 4;
if (IsUOPFormat)
{
offset = CalculateOffsetFromUOP(offset);
}
m_Map.Seek(offset, SeekOrigin.Begin);
GCHandle gc = GCHandle.Alloc(tiles, GCHandleType.Pinned);
try
{
if (m_Buffer == null || m_Buffer.Length < 192)
{
m_Buffer = new byte[192];
}
m_Map.Read(m_Buffer, 0, 192);
Marshal.Copy(m_Buffer, 0, gc.AddrOfPinnedObject(), 192);
}
finally
{
gc.Free();
}
//m_Map.Close();
}
return tiles;
}
public void RemoveStaticBlock(int blockx, int blocky)
{
if (m_RemovedStaticBlock == null)
{
m_RemovedStaticBlock = new bool[BlockWidth][];
}
if (m_RemovedStaticBlock[blockx] == null)
{
m_RemovedStaticBlock[blockx] = new bool[BlockHeight];
}
m_RemovedStaticBlock[blockx][blocky] = true;
if (m_StaticTiles[blockx] == null)
{
m_StaticTiles[blockx] = new HuedTile[BlockHeight][][][];
}
m_StaticTiles[blockx][blocky] = EmptyStaticBlock;
}
public bool IsStaticBlockRemoved(int blockx, int blocky)
{
if (m_RemovedStaticBlock == null)
{
return false;
}
if (m_RemovedStaticBlock[blockx] == null)
{
return false;
}
return m_RemovedStaticBlock[blockx][blocky];
}
public bool PendingStatic(int blockx, int blocky)
{
if (m_StaticTiles_ToAdd == null)
{
return false;
}
if (m_StaticTiles_ToAdd[blocky] == null)
{
return false;
}
if (m_StaticTiles_ToAdd[blocky][blockx] == null)
{
return false;
}
return true;
}
public void AddPendingStatic(int blockx, int blocky, StaticTile toadd)
{
if (m_StaticTiles_ToAdd == null)
{
m_StaticTiles_ToAdd = new List<StaticTile>[BlockHeight][];
}
if (m_StaticTiles_ToAdd[blocky] == null)
{
m_StaticTiles_ToAdd[blocky] = new List<StaticTile>[BlockWidth];
}
if (m_StaticTiles_ToAdd[blocky][blockx] == null)
{
m_StaticTiles_ToAdd[blocky][blockx] = new List<StaticTile>();
}
m_StaticTiles_ToAdd[blocky][blockx].Add(toadd);
}
public StaticTile[] GetPendingStatics(int blockx, int blocky)
{
if (m_StaticTiles_ToAdd == null)
{
return null;
}
if (m_StaticTiles_ToAdd[blocky] == null)
{
return null;
}
if (m_StaticTiles_ToAdd[blocky][blockx] == null)
{
return null;
}
return m_StaticTiles_ToAdd[blocky][blockx].ToArray();
}
public void Dispose()
{
if (m_Map != null)
{
m_Map.Close();
}
if (m_UOPReader != null)
{
m_UOPReader.Close();
}
if (m_Statics != null)
{
m_Statics.Close();
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct StaticTile
{
public ushort m_ID;
public byte m_X;
public byte m_Y;
public sbyte m_Z;
public short m_Hue;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct HuedTile
{
internal sbyte m_Z;
internal ushort m_ID;
internal int m_Hue;
public ushort ID { get { return m_ID; } set { m_ID = value; } }
public int Hue { get { return m_Hue; } set { m_Hue = value; } }
public int Z { get { return m_Z; } set { m_Z = (sbyte)value; } }
public HuedTile(ushort id, short hue, sbyte z)
{
m_ID = id;
m_Hue = hue;
m_Z = z;
}
public void Set(ushort id, short hue, sbyte z)
{
m_ID = id;
m_Hue = hue;
m_Z = z;
}
}
public struct MTile : IComparable
{
internal ushort m_ID;
internal sbyte m_Z;
internal TileFlag m_Flag;
internal int m_Solver;
public ushort ID { get { return m_ID; } }
public int Z { get { return m_Z; } set { m_Z = (sbyte)value; } }
public TileFlag Flag { get { return m_Flag; } set { m_Flag = value; } }
public int Solver { get { return m_Solver; } set { m_Solver = value; } }
public MTile(ushort id, sbyte z)
{
m_ID = Art.GetLegalItemID(id);
m_Z = z;
m_Flag = TileFlag.Background;
m_Solver = 0;
}
public MTile(ushort id, sbyte z, TileFlag flag)
{
m_ID = Art.GetLegalItemID(id);
m_Z = z;
m_Flag = flag;
m_Solver = 0;
}
public void Set(ushort id, sbyte z)
{
m_ID = Art.GetLegalItemID(id);
m_Z = z;
}
public void Set(ushort id, sbyte z, TileFlag flag)
{
m_ID = Art.GetLegalItemID(id);
m_Z = z;
m_Flag = flag;
}
public int CompareTo(object x)
{
if (x == null)
{
return 1;
}
if (!(x is MTile))
{
throw new ArgumentNullException();
}
var a = (MTile)x;
ItemData ourData = TileData.ItemTable[m_ID];
ItemData theirData = TileData.ItemTable[a.ID];
int ourTreshold = 0;
if (ourData.Height > 0)
{
++ourTreshold;
}
if (!ourData.Background)
{
++ourTreshold;
}
int ourZ = Z;
int theirTreshold = 0;
if (theirData.Height > 0)
{
++theirTreshold;
}
if (!theirData.Background)
{
++theirTreshold;
}
int theirZ = a.Z;
ourZ += ourTreshold;
theirZ += theirTreshold;
int res = ourZ - theirZ;
if (res == 0)
{
res = ourTreshold - theirTreshold;
}
if (res == 0)
{
res = m_Solver - a.Solver;
}
return res;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Tile : IComparable
{
internal ushort m_ID;
internal sbyte m_Z;
public ushort ID { get { return m_ID; } }
public int Z { get { return m_Z; } set { m_Z = (sbyte)value; } }
public Tile(ushort id, sbyte z)
{
m_ID = id;
m_Z = z;
}
public Tile(ushort id, sbyte z, sbyte flag)
{
m_ID = id;
m_Z = z;
}
public void Set(ushort id, sbyte z)
{
m_ID = id;
m_Z = z;
}
public void Set(ushort id, sbyte z, sbyte flag)
{
m_ID = id;
m_Z = z;
}
public int CompareTo(object x)
{
if (x == null)
{
return 1;
}
if (!(x is Tile))
{
throw new ArgumentNullException();
}
var a = (Tile)x;
if (m_Z > a.m_Z)
{
return 1;
}
else if (a.m_Z > m_Z)
{
return -1;
}
ItemData ourData = TileData.ItemTable[m_ID];
ItemData theirData = TileData.ItemTable[a.m_ID];
if (ourData.Height > theirData.Height)
{
return 1;
}
else if (theirData.Height > ourData.Height)
{
return -1;
}
if (ourData.Background && !theirData.Background)
{
return -1;
}
else if (theirData.Background && !ourData.Background)
{
return 1;
}
return 0;
}
}
}

308
Ultima/TileMatrixPatch.cs Normal file
View File

@@ -0,0 +1,308 @@
#region References
using System;
using System.IO;
using System.Runtime.InteropServices;
#endregion
namespace Ultima
{
public sealed class TileMatrixPatch
{
public int LandBlocksCount { get; private set; }
public int StaticBlocksCount { get; private set; }
public Tile[][][] LandBlocks { get; private set; }
public HuedTile[][][][][] StaticBlocks { get; private set; }
private readonly int BlockWidth;
private readonly int BlockHeight;
private static byte[] m_Buffer;
private static StaticTile[] m_TileBuffer = new StaticTile[128];
public bool IsLandBlockPatched(int x, int y)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return false;
}
if (LandBlocks[x] == null)
{
return false;
}
if (LandBlocks[x][y] == null)
{
return false;
}
return true;
}
public Tile[] GetLandBlock(int x, int y)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return TileMatrix.InvalidLandBlock;
}
if (LandBlocks[x] == null)
{
return TileMatrix.InvalidLandBlock;
}
return LandBlocks[x][y];
}
public Tile GetLandTile(int x, int y)
{
return GetLandBlock(x >> 3, y >> 3)[((y & 0x7) << 3) + (x & 0x7)];
}
public bool IsStaticBlockPatched(int x, int y)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return false;
}
if (StaticBlocks[x] == null)
{
return false;
}
if (StaticBlocks[x][y] == null)
{
return false;
}
return true;
}
public HuedTile[][][] GetStaticBlock(int x, int y)
{
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight)
{
return TileMatrix.EmptyStaticBlock;
}
if (StaticBlocks[x] == null)
{
return TileMatrix.EmptyStaticBlock;
}
return StaticBlocks[x][y];
}
public HuedTile[] GetStaticTiles(int x, int y)
{
return GetStaticBlock(x >> 3, y >> 3)[x & 0x7][y & 0x7];
}
public TileMatrixPatch(TileMatrix matrix, int index, string path)
{
BlockWidth = matrix.BlockWidth;
BlockHeight = matrix.BlockWidth;
LandBlocksCount = StaticBlocksCount = 0;
string mapDataPath, mapIndexPath;
if (path == null)
{
mapDataPath = Files.GetFilePath("mapdif{0}.mul", index);
mapIndexPath = Files.GetFilePath("mapdifl{0}.mul", index);
}
else
{
mapDataPath = Path.Combine(path, String.Format("mapdif{0}.mul", index));
if (!File.Exists(mapDataPath))
{
mapDataPath = null;
}
mapIndexPath = Path.Combine(path, String.Format("mapdifl{0}.mul", index));
if (!File.Exists(mapIndexPath))
{
mapIndexPath = null;
}
}
if (mapDataPath != null && mapIndexPath != null)
{
LandBlocks = new Tile[matrix.BlockWidth][][];
LandBlocksCount = PatchLand(matrix, mapDataPath, mapIndexPath);
}
string staDataPath, staIndexPath, staLookupPath;
if (path == null)
{
staDataPath = Files.GetFilePath("stadif{0}.mul", index);
staIndexPath = Files.GetFilePath("stadifl{0}.mul", index);
staLookupPath = Files.GetFilePath("stadifi{0}.mul", index);
}
else
{
staDataPath = Path.Combine(path, String.Format("stadif{0}.mul", index));
if (!File.Exists(staDataPath))
{
staDataPath = null;
}
staIndexPath = Path.Combine(path, String.Format("stadifl{0}.mul", index));
if (!File.Exists(staIndexPath))
{
staIndexPath = null;
}
staLookupPath = Path.Combine(path, String.Format("stadifi{0}.mul", index));
if (!File.Exists(staLookupPath))
{
staLookupPath = null;
}
}
if (staDataPath != null && staIndexPath != null && staLookupPath != null)
{
StaticBlocks = new HuedTile[matrix.BlockWidth][][][][];
StaticBlocksCount = PatchStatics(matrix, staDataPath, staIndexPath, staLookupPath);
}
}
private int PatchLand(TileMatrix matrix, string dataPath, string indexPath)
{
using (
FileStream fsData = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read),
fsIndex = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var indexReader = new BinaryReader(fsIndex))
{
var count = (int)(indexReader.BaseStream.Length / 4);
for (int i = 0; i < count; ++i)
{
int blockID = indexReader.ReadInt32();
int x = blockID / matrix.BlockHeight;
int y = blockID % matrix.BlockHeight;
fsData.Seek(4, SeekOrigin.Current);
var tiles = new Tile[64];
GCHandle gc = GCHandle.Alloc(tiles, GCHandleType.Pinned);
try
{
if (m_Buffer == null || m_Buffer.Length < 192)
{
m_Buffer = new byte[192];
}
fsData.Read(m_Buffer, 0, 192);
Marshal.Copy(m_Buffer, 0, gc.AddrOfPinnedObject(), 192);
}
finally
{
gc.Free();
}
if (LandBlocks[x] == null)
{
LandBlocks[x] = new Tile[matrix.BlockHeight][];
}
LandBlocks[x][y] = tiles;
}
return count;
}
}
}
private int PatchStatics(TileMatrix matrix, string dataPath, string indexPath, string lookupPath)
{
using (
FileStream fsData = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read),
fsIndex = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read),
fsLookup = new FileStream(lookupPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader indexReader = new BinaryReader(fsIndex), lookupReader = new BinaryReader(fsLookup))
{
int count = Math.Min((int)(indexReader.BaseStream.Length / 4), (int)(lookupReader.BaseStream.Length / 12));
var lists = new HuedTileList[8][];
for (int x = 0; x < 8; ++x)
{
lists[x] = new HuedTileList[8];
for (int y = 0; y < 8; ++y)
{
lists[x][y] = new HuedTileList();
}
}
for (int i = 0; i < count; ++i)
{
int blockID = indexReader.ReadInt32();
int blockX = blockID / matrix.BlockHeight;
int blockY = blockID % matrix.BlockHeight;
int offset = lookupReader.ReadInt32();
int length = lookupReader.ReadInt32();
lookupReader.ReadInt32(); // Extra
if (offset < 0 || length <= 0)
{
if (StaticBlocks[blockX] == null)
{
StaticBlocks[blockX] = new HuedTile[matrix.BlockHeight][][][];
}
StaticBlocks[blockX][blockY] = TileMatrix.EmptyStaticBlock;
continue;
}
fsData.Seek(offset, SeekOrigin.Begin);
int tileCount = length / 7;
if (m_TileBuffer.Length < tileCount)
{
m_TileBuffer = new StaticTile[tileCount];
}
StaticTile[] staTiles = m_TileBuffer;
GCHandle gc = GCHandle.Alloc(staTiles, GCHandleType.Pinned);
try
{
if (m_Buffer == null || m_Buffer.Length < length)
{
m_Buffer = new byte[length];
}
fsData.Read(m_Buffer, 0, length);
Marshal.Copy(m_Buffer, 0, gc.AddrOfPinnedObject(), length);
for (int j = 0; j < tileCount; ++j)
{
StaticTile cur = staTiles[j];
lists[cur.m_X & 0x7][cur.m_Y & 0x7].Add(Art.GetLegalItemID(cur.m_ID), cur.m_Hue, cur.m_Z);
}
var tiles = new HuedTile[8][][];
for (int x = 0; x < 8; ++x)
{
tiles[x] = new HuedTile[8][];
for (int y = 0; y < 8; ++y)
{
tiles[x][y] = lists[x][y].ToArray();
}
}
if (StaticBlocks[blockX] == null)
{
StaticBlocks[blockX] = new HuedTile[matrix.BlockHeight][][][];
}
StaticBlocks[blockX][blockY] = tiles;
}
finally
{
gc.Free();
}
}
return count;
}
}
}
}
}

231
Ultima/Ultima.csproj Normal file
View File

@@ -0,0 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<ProjectType>Local</ProjectType>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{E08CFBE4-E013-44EE-8829-426D05BC083F}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ApplicationIcon>
</ApplicationIcon>
<AssemblyKeyContainerName>
</AssemblyKeyContainerName>
<AssemblyName>Ultima</AssemblyName>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<DefaultClientScript>JScript</DefaultClientScript>
<DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
<DefaultTargetSchema>IE50</DefaultTargetSchema>
<DelaySign>false</DelaySign>
<OutputType>Library</OutputType>
<RootNamespace>Ultima</RootNamespace>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<StartupObject>
</StartupObject>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<RuntimeIdentifiers>win7-x64;win7-x86;ubuntu.16.10-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>..\</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE;DEBUG;NEWTIMERS;ServUO</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>..\</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE;NEWTIMERS;ServUO</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<Prefer32Bit>false</Prefer32Bit>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\</OutputPath>
<DefineConstants>TRACE;DEBUG;NEWTIMERS;ServUO</DefineConstants>
<BaseAddress>285212672</BaseAddress>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>false</Prefer32Bit>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<OutputPath>..\</OutputPath>
<BaseAddress>285212672</BaseAddress>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>
</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>false</Prefer32Bit>
<DefineConstants>TRACE;NEWTIMERS;ServUO</DefineConstants>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\</OutputPath>
<DefineConstants>TRACE;DEBUG;NEWTIMERS;ServUO</DefineConstants>
<BaseAddress>285212672</BaseAddress>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>false</Prefer32Bit>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>..\</OutputPath>
<BaseAddress>285212672</BaseAddress>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>
</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>false</Prefer32Bit>
<DefineConstants>TRACE;NEWTIMERS;ServUO</DefineConstants>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing">
<Name>System.Drawing</Name>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AnimationEdit.cs" />
<Compile Include="Animations.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Animdata.cs" />
<Compile Include="Art.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ASCIIFont.cs" />
<Compile Include="AssemblyInfo.cs" />
<Compile Include="CalibrationInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Client.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ClientHandles.cs" />
<Compile Include="FileIndex.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Files.cs" />
<Compile Include="Gumps.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Hues.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Light.cs" />
<Compile Include="LocationPointer.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Map.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="MultiMap.cs" />
<Compile Include="Multis.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="NativeMethods.cs" />
<Compile Include="ProcessStream.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="RadarCol.cs" />
<Compile Include="Settings.cs" />
<Compile Include="SkillGroups.cs" />
<Compile Include="Skills.cs" />
<Compile Include="Sound.cs" />
<Compile Include="SpeechList.cs" />
<Compile Include="StringEntry.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="StringList.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Textures.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="TileData.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="TileList.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="TileMatrix.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="TileMatrixPatch.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="UnicodeFont.cs" />
<Compile Include="Verdata.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="WindowProcessStream.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>

272
Ultima/UnicodeFont.cs Normal file
View File

@@ -0,0 +1,272 @@
#region References
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
#endregion
namespace Ultima
{
public sealed class UnicodeFont
{
public UnicodeChar[] Chars { get; set; }
public UnicodeFont()
{
Chars = new UnicodeChar[0x10000];
}
/// <summary>
/// Returns width of text
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public int GetWidth(string text)
{
if (text == null || text.Length == 0)
{
return 0;
}
int width = 0;
for (int i = 0; i < text.Length; ++i)
{
int c = text[i] % 0x10000;
width += Chars[c].Width;
width += Chars[c].XOffset;
}
return width;
}
/// <summary>
/// Returns max height of text
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public int GetHeight(string text)
{
if (text == null || text.Length == 0)
{
return 0;
}
int height = 0;
for (int i = 0; i < text.Length; ++i)
{
int c = text[i] % 0x10000;
height = Math.Max(height, Chars[c].Height + Chars[c].YOffset);
}
return height;
}
}
public sealed class UnicodeChar
{
public byte[] Bytes { get; set; }
public sbyte XOffset { get; set; }
public sbyte YOffset { get; set; }
public int Height { get; set; }
public int Width { get; set; }
/// <summary>
/// Gets Bitmap of Char
/// </summary>
/// <returns></returns>
public Bitmap GetImage()
{
return GetImage(false);
}
/// <summary>
/// Gets Bitmap of Char with Background -1
/// </summary>
/// <param name="fill"></param>
/// <returns></returns>
public unsafe Bitmap GetImage(bool fill)
{
if ((Width == 0) || (Height == 0))
{
return null;
}
var bmp = new Bitmap(Width, Height, Settings.PixelFormat);
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
int delta = bd.Stride >> 1;
for (int y = 0; y < Height; ++y, line += delta)
{
ushort* cur = line;
for (int x = 0; x < Width; ++x)
{
if (IsPixelSet(Bytes, Width, x, y))
{
cur[x] = 0x8000;
}
else if (fill)
{
cur[x] = 0xffff;
}
}
}
bmp.UnlockBits(bd);
return bmp;
}
private static bool IsPixelSet(byte[] data, int width, int x, int y)
{
int offset = x / 8 + y * ((width + 7) / 8);
if (offset > data.Length)
{
return false;
}
return (data[offset] & (1 << (7 - (x % 8)))) != 0;
}
/// <summary>
/// Resets Buffer with Bitmap
/// </summary>
/// <param name="bmp"></param>
public unsafe void SetBuffer(Bitmap bmp)
{
Bytes = new byte[bmp.Height * (((bmp.Width - 1) / 8) + 1)];
BitmapData bd = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, Settings.PixelFormat);
var line = (ushort*)bd.Scan0;
//int delta = bd.Stride >> 1;
for (int y = 0; y < bmp.Height; ++y)
{
ushort* cur = line;
for (int x = 0; x < bmp.Width; ++x)
{
if (cur[x] == 0x8000)
{
int offset = x / 8 + y * ((bmp.Width + 7) / 8);
Bytes[offset] |= (byte)(1 << (7 - (x % 8)));
}
}
}
bmp.UnlockBits(bd);
}
}
public static class UnicodeFonts
{
private static readonly string[] m_files = new[]
{
"unifont.mul", "unifont1.mul", "unifont2.mul", "unifont3.mul", "unifont4.mul", "unifont5.mul", "unifont6.mul",
"unifont7.mul", "unifont8.mul", "unifont9.mul", "unifont10.mul", "unifont11.mul", "unifont12.mul"
};
public static UnicodeFont[] Fonts = new UnicodeFont[13];
static UnicodeFonts()
{
Initialize();
}
/// <summary>
/// Reads unifont*.mul
/// </summary>
public static void Initialize()
{
for (int i = 0; i < m_files.Length; i++)
{
string filePath = Files.GetFilePath(m_files[i]);
if (filePath == null)
{
continue;
}
Fonts[i] = new UnicodeFont();
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var bin = new BinaryReader(fs))
{
for (int c = 0; c < 0x10000; ++c)
{
Fonts[i].Chars[c] = new UnicodeChar();
fs.Seek(((c) * 4), SeekOrigin.Begin);
int num2 = bin.ReadInt32();
if ((num2 >= fs.Length) || (num2 <= 0))
{
continue;
}
fs.Seek(num2, SeekOrigin.Begin);
sbyte xOffset = bin.ReadSByte();
sbyte yOffset = bin.ReadSByte();
int Width = bin.ReadByte();
int Height = bin.ReadByte();
Fonts[i].Chars[c].XOffset = xOffset;
Fonts[i].Chars[c].YOffset = yOffset;
Fonts[i].Chars[c].Width = Width;
Fonts[i].Chars[c].Height = Height;
if (!((Width == 0) || (Height == 0)))
{
Fonts[i].Chars[c].Bytes = bin.ReadBytes(Height * (((Width - 1) / 8) + 1));
}
}
}
}
}
}
/// <summary>
/// Draws Text with font in Bitmap and returns
/// </summary>
/// <param name="fontId"></param>
/// <param name="text"></param>
/// <returns></returns>
public static Bitmap WriteText(int fontId, string text)
{
var result = new Bitmap(Fonts[fontId].GetWidth(text) + 2, Fonts[fontId].GetHeight(text) + 2);
int dx = 2;
int dy = 2;
using (Graphics graph = Graphics.FromImage(result))
{
for (int i = 0; i < text.Length; ++i)
{
int c = text[i] % 0x10000;
Bitmap bmp = Fonts[fontId].Chars[c].GetImage();
dx += Fonts[fontId].Chars[c].XOffset;
graph.DrawImage(bmp, dx, dy + Fonts[fontId].Chars[c].YOffset);
dx += bmp.Width;
}
}
return result;
}
/// <summary>
/// Saves Font and returns string Filename
/// </summary>
/// <param name="path"></param>
/// <param name="filetype"></param>
/// <returns></returns>
public static string Save(string path, int filetype)
{
string FileName = Path.Combine(path, m_files[filetype]);
using (var fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var bin = new BinaryWriter(fs))
{
fs.Seek(0x10000 * 4, SeekOrigin.Begin);
bin.Write(0);
// Set first data
for (int c = 0; c < 0x10000; ++c)
{
if (Fonts[filetype].Chars[c].Bytes == null)
{
continue;
}
fs.Seek(((c) * 4), SeekOrigin.Begin);
bin.Write((int)fs.Length);
fs.Seek(fs.Length, SeekOrigin.Begin);
bin.Write(Fonts[filetype].Chars[c].XOffset);
bin.Write(Fonts[filetype].Chars[c].YOffset);
bin.Write((byte)Fonts[filetype].Chars[c].Width);
bin.Write((byte)Fonts[filetype].Chars[c].Height);
bin.Write(Fonts[filetype].Chars[c].Bytes);
}
}
}
return FileName;
}
}
}

92
Ultima/Verdata.cs Normal file
View File

@@ -0,0 +1,92 @@
#region References
using System.IO;
#endregion
// FileIDs
//0 - map0.mul
//1 - staidx0.mul
//2 - statics0.mul
//3 - artidx.mul
//4 - art.mul
//5 - anim.idx
//6 - anim.mul
//7 - soundidx.mul
//8 - sound.mul
//9 - texidx.mul
//10 - texmaps.mul
//11 - gumpidx.mul
//12 - gumpart.mul
//13 - multi.idx
//14 - multi.mul
//15 - skills.idx
//16 - skills.mul
//30 - tiledata.mul
//31 - animdata.mul
namespace Ultima
{
public sealed class Verdata
{
public static Stream Stream { get; private set; }
public static Entry5D[] Patches { get; private set; }
private static string path;
static Verdata()
{
Initialize();
}
public static void Initialize()
{
path = Files.GetFilePath("verdata.mul");
if (path == null)
{
Patches = new Entry5D[0];
Stream = Stream.Null;
}
else
{
using (Stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var bin = new BinaryReader(Stream))
{
Patches = new Entry5D[bin.ReadInt32()];
for (int i = 0; i < Patches.Length; ++i)
{
Patches[i].file = bin.ReadInt32();
Patches[i].index = bin.ReadInt32();
Patches[i].lookup = bin.ReadInt32();
Patches[i].length = bin.ReadInt32();
Patches[i].extra = bin.ReadInt32();
}
}
}
Stream.Close();
}
}
public static void Seek(int lookup)
{
if (Stream == null || !Stream.CanRead || !Stream.CanSeek)
{
if (path != null)
{
Stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
}
Stream.Seek(lookup, SeekOrigin.Begin);
}
}
public struct Entry5D
{
public int file;
public int index;
public int lookup;
public int length;
public int extra;
}
}

View File

@@ -0,0 +1,31 @@
namespace Ultima
{
public class WindowProcessStream : ProcessStream
{
private ClientWindowHandle m_Window;
private ClientProcessHandle m_ProcessID;
public ClientWindowHandle Window { get { return m_Window; } set { m_Window = value; } }
public WindowProcessStream(ClientWindowHandle window)
{
m_Window = window;
m_ProcessID = ClientProcessHandle.Invalid;
}
public override ClientProcessHandle ProcessID
{
get
{
if (NativeMethods.IsWindow(m_Window) != 0 && !m_ProcessID.IsInvalid)
{
return m_ProcessID;
}
NativeMethods.GetWindowThreadProcessId(m_Window, ref m_ProcessID);
return m_ProcessID;
}
}
}
}