Add plugin for Microsoft Merlin game engine (Hover!)

This commit is contained in:
Michael Becker 2014-06-09 16:06:59 -04:00
parent 58077533a9
commit 430ffe8e67
13 changed files with 734 additions and 0 deletions

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniversalEditor.IO;
namespace UniversalEditor.DataFormats.Multimedia.FileSystem.Microsoft.Merlin.Mfc
{
public class CMerlinObject : MfcObject
{
public string Name { get; set; }
public override void LoadObject(Reader reader)
{
Name = reader.ReadMfcString();
reader.ReadMfcObjectWithoutHeader<TrailingBytes>();
}
public override void SaveObject(Writer writer)
{
// writer.WriteMfcString(Name);
// writer.WriteMfcObjectWithoutHeader(new TrailingBytes());
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.Multimedia.FileSystem.Microsoft.Merlin.Mfc
{
public class MipMap
{
public Size ImageDimensionsMinusOne { get; set; }
public Size ImageDimensions { get; set; }
public uint Level { get; set; }
public byte[] ImageData { get; set; }
public List<List<PixelSpan>> PixelSpans { get; set; }
}
public struct PixelSpan
{
public ushort StartIndex;
public ushort EndIndex;
public PixelSpan(ushort startIndex, ushort endIndex)
{
StartIndex = startIndex;
EndIndex = endIndex;
}
};
[MfcSerialisable("CMerlinTexture")]
internal class CMerlinTexture : CMerlinObject
{
public bool HasTransparency { get; private set; }
public List<MipMap> Mipmaps { get; private set; }
public override void LoadObject(IO.Reader reader)
{
base.LoadObject(reader);
var flags = reader.ReadUInt16();
this.HasTransparency = (flags & 1) != 0;
if ((flags & ~1) != 0)
{
throw new NotImplementedException("Unexpected flag set in texture header");
}
ushort mipmapCount = reader.ReadUInt16();
this.Mipmaps = new List<MipMap>(mipmapCount);
for (int i = 0; i < mipmapCount; i++)
{
MipMap mipmap = new MipMap();
var nextLargestHeight = reader.ReadUInt16();
var imageHeight = reader.ReadUInt16();
var nextLargestWidth = reader.ReadUInt16();
var imageWidth = reader.ReadUInt16();
mipmap.ImageDimensionsMinusOne = new Size(imageWidth, imageHeight);
mipmap.ImageDimensions = new Size(nextLargestWidth, nextLargestHeight);
mipmap.Level = reader.ReadUInt16();
var imageDataLength = reader.ReadUInt32();
mipmap.ImageData = reader.ReadBytes((int)imageDataLength);
int totalSpanCount = (int)reader.ReadUInt32();
mipmap.PixelSpans = new List<List<PixelSpan>>(totalSpanCount);
for (int y = 0; y < nextLargestHeight; y++)
{
ushort rowSpanCount = reader.ReadUInt16();
var rowSpans = new List<PixelSpan>(rowSpanCount);
for (int k = 0; k < rowSpanCount; k++)
{
ushort startIndex = reader.ReadUInt16();
ushort endIndex = reader.ReadUInt16();
rowSpans.Add(new PixelSpan(startIndex, endIndex));
}
mipmap.PixelSpans.Add(rowSpans);
}
reader.ReadMfcObjectWithoutHeader<TrailingBytes>();
this.Mipmaps.Add(mipmap);
}
}
public override void SaveObject(IO.Writer reader)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.Multimedia.FileSystem.Microsoft.Merlin.Mfc
{
[MfcSerialisable]
internal class TrailingBytes : MfcObject
{
private byte[] trailingBytes = new byte[0];
public override void LoadObject(IO.Reader reader)
{
ushort byteCount = reader.ReadUInt16();
trailingBytes = reader.ReadBytes(byteCount);
}
public override void SaveObject(IO.Writer writer)
{
writer.WriteUInt16(0);
}
}
}

View File

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniversalEditor.Accessors;
using UniversalEditor.DataFormats.Multimedia.FileSystem.Microsoft.Merlin.Mfc;
using UniversalEditor.IO;
using UniversalEditor.ObjectModels.FileSystem;
using UniversalEditor.ObjectModels.Multimedia.Palette;
namespace UniversalEditor.DataFormats.Multimedia.FileSystem.Microsoft.Merlin
{
/// <summary>
/// Represents a Microsoft Merlin Game Engine (Hover!) .TEX file,
/// containing a shared palette and up to 65535 textures.
/// </summary>
public class TEXDataFormat : DataFormat
{
private static DataFormatReference _dfr = null;
public override DataFormatReference MakeReference()
{
if (_dfr == null)
{
_dfr = base.MakeReference();
_dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All);
_dfr.Filters.Add("Microsoft Merlin Game Engine (Hover!) texture pack", new string[] { "*.tex" });
}
return _dfr;
}
private int mvarPaletteEntryCount = 256;
private PaletteObjectModel mvarPalette = new PaletteObjectModel();
protected override void LoadInternal(ref ObjectModel objectModel)
{
FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel);
if (fsom == null) throw new ObjectModelNotSupportedException();
Reader reader = base.Accessor.Reader;
reader.Accessor.Position = 0;
fsom.Clear();
mvarPalette.Clear();
for (int i = 0; i < mvarPaletteEntryCount; i++)
{
byte r = reader.ReadByte();
byte g = reader.ReadByte();
byte b = reader.ReadByte();
byte a = reader.ReadByte();
mvarPalette.Entries.Add(Color.FromRGBA(r, g, b, a));
}
MemoryAccessor ma = new MemoryAccessor();
Document.Save(mvarPalette, new DataFormats.Multimedia.Palette.Adobe.ACODataFormat(), ma, true);
fsom.Files.Add("PALETTE.ACO", ma.ToArray());
// Read a List<CMerlinTexture>
List<CMerlinTexture> list = reader.ReadMfcList<CMerlinTexture>();
for (ushort i = 0; i < list.Count; i++)
{
/*
ushort tag = reader.ReadUInt16();
uint objectTag = ((uint)(tag & ClassTag) << 16) | (uint)(tag & ~ClassTag);
if (tag == BigObjectTag)
{
objectTag = reader.ReadUInt32();
}
if (tag == NewClassTag)
{
// Not a class we've seen before; read it in
ushort schemaVersion = reader.ReadUInt16();
string className = reader.ReadUInt16String();
}
Folder textureI = new Folder();
string name = ReadMFCString(reader);
textureI.Name = name;
TEXTextureFlags flags = (TEXTextureFlags)reader.ReadUInt16();
ushort mipmapCount = reader.ReadUInt16();
for (ushort j = 0; j < mipmapCount; j++)
{
// TextureMipmap mipmap = new TextureMipmap();
var nextLargestHeight = reader.ReadUInt16();
var imageHeight = reader.ReadUInt16();
var nextLargestWidth = reader.ReadUInt16();
var imageWidth = reader.ReadUInt16();
// mipmap.ImageDimensionsMinusOne = new Size(imageWidth, imageHeight);
// mipmap.ImageDimensions = new Size(nextLargestWidth, nextLargestHeight);
// mipmap.Level = archive.DeserialiseUInt16();
ushort mipmapLevel = reader.ReadUInt16();
var imageDataLength = reader.ReadUInt32();
byte[] ImageData = reader.ReadBytes((int)imageDataLength);
textureI.Files.Add("MIPMAP" + j.ToString() + ".BMP", ImageData);
int totalSpanCount = (int)reader.ReadUInt32();
// mipmap.PixelSpans = new List<List<PixelSpan>>(totalSpanCount);
for (int y = 0; y < nextLargestHeight; y++)
{
ushort rowSpanCount = reader.ReadUInt16();
// var rowSpans = new List<PixelSpan>(rowSpanCount);
for (int k = 0; k < rowSpanCount; k++)
{
ushort startIndex = reader.ReadUInt16();
ushort endIndex = reader.ReadUInt16();
// rowSpans.Add(new PixelSpan(startIndex, endIndex));
}
// mipmap.PixelSpans.Add(rowSpans);
}
ushort trailingByteCount = reader.ReadUInt16();
byte[] trailingBytes = reader.ReadBytes(trailingByteCount);
}
fsom.Folders.Add(textureI);
*/
}
}
protected override void SaveInternal(ObjectModel objectModel)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.Multimedia.FileSystem.Microsoft.Merlin
{
[Flags()]
public enum TEXTextureFlags : ushort
{
None = 0,
HasTransparency = 1
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniversalEditor.IO;
namespace UniversalEditor
{
public interface IMfcSerialisable<T> where T : IMfcSerialisable<T>
{
void LoadObject(Reader reader);
void SaveObject(Writer writer);
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor
{
public class MfcClass
{
public string Name { get; set; }
public int SchemaVersion { get; set; }
public Type RealType { get; set; }
internal T CreateNewObject<T>() where T : IMfcSerialisable<T>
{
if (RealType.IsAssignableFrom(typeof(T)))
{
throw new ArgumentException("Cannot assign " + RealType.Name + " from " + typeof(T).Name);
}
return (T)RealType.Assembly.CreateInstance(RealType.FullName);
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace UniversalEditor
{
public class MfcClassRegistry
{
private Dictionary<string, MfcClass> _registry;
public MfcClassRegistry()
{
this._registry = new Dictionary<string, MfcClass>();
}
/// <summary>
/// Registers a single serialised class name.
/// </summary>
/// <param name="mfcName">The serialised class name.</param>
/// <param name="realType">The .Net type capable of deserialising the class data.</param>
public void RegisterClass(string mfcName, Type realType)
{
_registry.Add(mfcName, new MfcClass
{
Name = mfcName,
SchemaVersion = 1,
RealType = realType
});
}
/// <summary>
/// Registers all exported classes from the specified assembly
/// that are marked with the MfcSerialisable attribute.
/// </summary>
public void AutoRegisterClasses(Assembly assembly)
{
foreach (var type in assembly.GetExportedTypes())
{
var markers = type.GetCustomAttributes(typeof(MfcSerialisableAttribute), false).Cast<MfcSerialisableAttribute>();
foreach (var marker in markers)
{
RegisterClass(marker.SerialisedName, type);
}
}
}
public MfcClass GetMfcClass(string mfcName)
{
return _registry[mfcName];
}
public MfcClass GetMfcClass(Type realType)
{
var x = from c in _registry.Values where c.RealType == realType select c;
return x.Single();
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniversalEditor.IO;
namespace UniversalEditor
{
public abstract class MfcObject : IMfcSerialisable<MfcObject>
{
public abstract void LoadObject(Reader reader);
public abstract void SaveObject(Writer reader);
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
class MfcSerialisableAttribute : Attribute
{
private string _serialisedName;
/// <summary>
/// Marks a type as serialisable with an explicit name.
/// </summary>
public MfcSerialisableAttribute(string serialisedName)
{
this._serialisedName = serialisedName;
}
/// <summary>
/// Marks a type as serialisable but with no name.
/// Such types cannot be automatically deserialised;
/// you must explicitly name the type for each serialise call.
/// </summary>
public MfcSerialisableAttribute()
{
_serialisedName = string.Format("Anonymous_{0}", Guid.NewGuid().ToString());
}
public string SerialisedName
{
get
{
return _serialisedName;
}
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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("UniversalEditor.Plugins.Microsoft.Merlin")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("City of Orlando")]
[assembly: AssemblyProduct("UniversalEditor.Plugins.Microsoft.Merlin")]
[assembly: AssemblyCopyright("Copyright © City of Orlando 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("559ea7e5-9dcf-42c4-a7be-82af087449ae")]
// 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 Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniversalEditor.IO;
namespace UniversalEditor
{
public static class ReaderExtensions
{
const UInt16 NullTag = 0;
const UInt16 NewClassTag = 0xffff;
const UInt16 ClassTag = 0x8000;
const UInt32 BigClassTag = 0x80000000;
const UInt16 BigObjectTag = 0x7fff;
const UInt32 MaxMapCountTag = 0x3ffffffe;
private static MfcClassRegistry _classRegistry;
private static List<MfcClass> _loadedClasses;
private static List<MfcObject> _loadedObjects;
static ReaderExtensions()
{
_classRegistry = new MfcClassRegistry();
_loadedClasses = new List<MfcClass>();
_loadedObjects = new List<MfcObject>();
// Class index zero isn't used/represents an error
_loadedClasses.Add(null);
// Object index zero isn't used/represents a null pointer
_loadedObjects.Add(null);
_classRegistry.AutoRegisterClasses(typeof(ReaderExtensions).Assembly);
}
public static MfcClass ReadNewMfcClass(this Reader reader)
{
ushort schemaVersion = reader.ReadUInt16();
string className = reader.ReadUInt16String();
MfcClass mfcClass = _classRegistry.GetMfcClass(className);
if (mfcClass == null)
{
throw new System.IO.InvalidDataException("No registered class for MfcObject " + className);
}
if (mfcClass.SchemaVersion != schemaVersion)
{
throw new System.IO.InvalidDataException("Schema mismatch: file = " + schemaVersion + ", registered = " + mfcClass.SchemaVersion);
}
return mfcClass;
}
public static string ReadMfcString(this Reader reader)
{
int stringLength = 0;
byte bLength = reader.ReadByte();
if (bLength < 0xff)
{
stringLength = bLength;
}
else
{
ushort wLength = reader.ReadUInt16();
if (wLength < 0xfffe)
{
stringLength = wLength;
}
else
{
if (wLength == 0xfffe)
{
// Unicode string prefix -- not currently handled. See
// CArchive::operator>>(CArchive& ar, CString& string)
// for details on how to implement this when needed.
}
stringLength = reader.ReadInt32();
}
}
return reader.ReadFixedLengthString(stringLength);
}
/// <summary>
/// Tries to read in and return the MfcClass that is next in the stream.
/// If the next object has already been loaded then null is returned and
/// alreadyLoadedTag is set to the object ID of the object.
/// </summary>
public static MfcClass ReadMfcClass(this Reader reader, out uint alreadyLoadedTag)
{
ushort tag = reader.ReadUInt16();
uint objectTag = ((uint)(tag & ClassTag) << 16) | (uint)(tag & ~ClassTag);
if (tag == BigObjectTag)
{
objectTag = reader.ReadUInt32();
}
// If it is an object tag and not a class tag then bail out -- caller will handle it
alreadyLoadedTag = 0;
if ((objectTag & BigClassTag) == 0)
{
alreadyLoadedTag = objectTag;
return null;
}
MfcClass mfcClass;
if (tag == NewClassTag)
{
// Not a class we've seen before; read it in
mfcClass = reader.ReadNewMfcClass();
_loadedClasses.Add(mfcClass);
return mfcClass;
}
// A class we've seen before, look it up by index
uint classIndex = objectTag & ~BigClassTag;
if (classIndex == 0)
{
throw new System.IO.InvalidDataException("Got a invalid class index: 0");
}
if (classIndex > _loadedClasses.Count)
{
throw new System.IO.InvalidDataException("Got a class index larger than the currently loaded count: " + classIndex);
}
return _loadedClasses[(int)classIndex];
}
public static T ReadMfcObject<T>(this Reader reader) where T : MfcObject
{
uint objectTag;
MfcClass mfcClass = reader.ReadMfcClass(out objectTag);
if (mfcClass == null)
{
// An object we've already loaded
if (objectTag > _loadedObjects.Count)
{
throw new System.IO.InvalidDataException("Got an object tag larger than the count of loaded objects: " + objectTag);
}
return (T)_loadedObjects[(int)objectTag];
}
// An object we haven't yet loaded. Create a new instance and deserialise it.
// Make sure to add it to the list of loaded objects before deserialising in
// case it has (possibly indirect) references to itself.
return reader.ReadNewMfcObject<T>(mfcClass);
}
public static T ReadNewMfcObject<T>(this Reader reader, MfcClass mfcClass) where T : MfcObject
{
MfcObject newObject = mfcClass.CreateNewObject<MfcObject>();
_loadedObjects.Add(newObject);
newObject.LoadObject(reader);
return (T)newObject;
}
/// <summary>
/// Deserialises an object of type T without reading in the header.
/// This implies it must be a new object and not one that has already
/// been loaded since there is no object tag to reference the loaded list.
/// </summary>
public static T ReadMfcObjectWithoutHeader<T>(this Reader reader) where T : MfcObject
{
MfcClass mfcClass = _classRegistry.GetMfcClass(typeof(T));
return reader.ReadNewMfcObject<T>(mfcClass);
}
public static List<T> ReadMfcList<T>(this Reader reader) where T : MfcObject
{
List<T> result = new List<T>();
ushort listLength = reader.ReadUInt16();
if (listLength >= 1)
{
// First object has a valid runtime class header
result.Add(reader.ReadMfcObject<T>());
}
for (int i = 1; i < listLength; i++)
{
// Subsequent objects are missing the runtime class header but have
// a UInt16 preceding them that looks like an invalid runtime class
// header.
uint unknown1 = reader.ReadUInt16();
result.Add(reader.ReadMfcObjectWithoutHeader<T>());
}
return result;
}
}
}

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9F1FDC26-5F1C-4C2A-BBBF-3A597A72802D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UniversalEditor</RootNamespace>
<AssemblyName>UniversalEditor.Plugins.Microsoft.Merlin</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Output\Release\Plugins\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
</ItemGroup>
<ItemGroup>
<Compile Include="DataFormats\Multimedia\FileSystem\Microsoft\Merlin\Mfc\CMerlinObject.cs" />
<Compile Include="DataFormats\Multimedia\FileSystem\Microsoft\Merlin\Mfc\CMerlinTexture.cs" />
<Compile Include="DataFormats\Multimedia\FileSystem\Microsoft\Merlin\Mfc\TrailingBytes.cs" />
<Compile Include="DataFormats\Multimedia\FileSystem\Microsoft\Merlin\TEXDataFormat.cs" />
<Compile Include="DataFormats\Multimedia\FileSystem\Microsoft\Merlin\TEXTextureFlags.cs" />
<Compile Include="IMfcSerializable.cs" />
<Compile Include="MfcClass.cs" />
<Compile Include="MfcClassRegistry.cs" />
<Compile Include="MfcObject.cs" />
<Compile Include="MfcSerializableAttribute.cs" />
<Compile Include="ReaderExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Core\UniversalEditor.Core.csproj">
<Project>{2d4737e6-6d95-408a-90db-8dff38147e85}</Project>
<Name>UniversalEditor.Core</Name>
</ProjectReference>
<ProjectReference Include="..\UniversalEditor.Essential\UniversalEditor.Essential.csproj">
<Project>{30467e5c-05bc-4856-aadc-13906ef4cadd}</Project>
<Name>UniversalEditor.Essential</Name>
</ProjectReference>
<ProjectReference Include="..\UniversalEditor.Plugins.Multimedia\UniversalEditor.Plugins.Multimedia.csproj">
<Project>{be4d0ba3-0888-42a5-9c09-fc308a4509d2}</Project>
<Name>UniversalEditor.Plugins.Multimedia</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>