194 lines
5.5 KiB
C#

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;
}
}
}