magellan/Lucidiot.Magellan.Extract/Program.cs

350 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using Lucidiot.Raima;
namespace Lucidiot.Magellan.Extract {
/// <summary>
/// Helper to temporarily switch the console's colors to others, and then restore the original colors.
/// </summary>
/// <remarks>
/// This should be used with a <c>using</c> keyword: the <see cref="ColorSwitch.Dispose" /> method will restore the original colors.
/// </remarks>
internal class ColorSwitch : IDisposable {
public readonly ConsoleColor OriginalForeground;
public readonly ConsoleColor OriginalBackground;
public ColorSwitch() {
OriginalForeground = Console.ForegroundColor;
OriginalBackground = Console.BackgroundColor;
}
public ColorSwitch(ConsoleColor Foreground)
: this() {
Console.ForegroundColor = Foreground;
}
public ColorSwitch(ConsoleColor Foreground, ConsoleColor Background)
: this(Foreground) {
Console.BackgroundColor = Background;
}
public void Dispose() {
Console.ForegroundColor = OriginalForeground;
Console.BackgroundColor = OriginalBackground;
}
}
class Program {
static int Main(string[] args) {
if (args.Length < 1) {
Console.WriteLine("Usage: thing.exe <MGI/IMI file> [<MGI/IMI file> ...]");
return 1;
}
foreach (string arg in args) {
DumpMGI(arg);
}
return 0;
}
static string HumanBytes(double bytes) {
char prefix;
if (bytes > 1E9) {
bytes /= 1E9;
prefix = 'G';
} else if (bytes > 1E6) {
bytes /= 1E6;
prefix = 'M';
} else if (bytes > 1E3) {
bytes /= 1E3;
prefix = 'K';
} else {
return String.Format("{0}B", bytes);
}
return String.Format("{0:F1}{1}B", bytes, prefix);
}
static void DumpMGI(string MGIPath) {
Console.WriteLine("Magellan Map archive contents - {0}", MGIPath);
MapArchiveEntry[] entries;
try {
entries = MapArchive.ReadHeader(MGIPath);
} catch (Exception e) {
using (new ColorSwitch(ConsoleColor.Red)) {
Console.WriteLine(e.ToString());
}
Console.WriteLine();
Console.WriteLine("--------------------------------------------------");
return;
}
List<MapArchiveEntry> databases = new List<MapArchiveEntry>();
foreach (MapArchiveEntry entry in entries) {
Console.WriteLine("{0,-16} {1} starting at offset {2}", entry.FullName, HumanBytes(entry.Length), entry.Offset);
if (entry.Extension.ToLowerInvariant() == "dbd") {
Console.WriteLine(" Looks like a Raima database, will attempt to dump its schema below.");
databases.Add(entry);
}
bool invalidChars = false;
foreach (char invalidChar in Path.GetInvalidFileNameChars()) {
if (entry.FullName.Contains(invalidChar.ToString())) {
invalidChars = true;
break;
}
}
if (invalidChars) {
using (new ColorSwitch(ConsoleColor.Yellow)) {
Console.WriteLine(" File name has invalid characters");
}
}
}
Console.WriteLine();
Console.WriteLine("--------------------------------------------------");
foreach (MapArchiveEntry db in databases) {
DumpDatabase(MGIPath, db.FullName);
}
}
static void DumpDatabase(string MGIPath, string fileName) {
Console.WriteLine("Raima Database Manager Embedded Database Schema Dump");
Console.WriteLine("Database {0} in archive {1}", fileName, MGIPath);
DatabaseSchema ds;
try {
ds = DatabaseSchema.Load(fileName, new MapArchiveStorage(MGIPath));
} catch (Exception e) {
using (new ColorSwitch(ConsoleColor.Red)) {
Console.WriteLine(e.ToString());
}
Console.WriteLine();
Console.WriteLine("--------------------------------------------------");
return;
}
Console.WriteLine("Version: {0}", ds.Version);
Console.WriteLine("PageSize: {0}", ds.PageSize);
Console.WriteLine("--- FILE ENTRIES ---------------------------------");
for (int i = 0; i < ds.FileEntries.Count; i++) {
FileEntry entry = ds.FileEntries[i];
Console.WriteLine(" #{0}: {1}", i, entry.Name);
Console.WriteLine(" {0}, {1}", entry.Type, entry.Status);
Console.WriteLine(" Descriptor {0}, page size {1}", entry.Descriptor, entry.PageSize);
Console.WriteLine(" {0} record slots of size {1}", entry.Slots, entry.SlotSize);
if ((entry.Options & FileEntryOptions.Compressed) == FileEntryOptions.Compressed)
Console.WriteLine(" Compressed");
if ((entry.Options & FileEntryOptions.Local) == FileEntryOptions.Local)
Console.WriteLine(" Local");
if ((entry.Options & FileEntryOptions.Static) == FileEntryOptions.Static)
Console.WriteLine(" Static");
}
if (ds.FileEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--- RECORD ENTRIES -------------------------------");
for (int i = 0; i < ds.RecordEntries.Count; i++) {
RecordEntry entry = ds.RecordEntries[i];
Console.WriteLine(" #{0}: Record from file #{1}", i, entry.FileEntryIndex);
Console.WriteLine(" {0} field entries starting at #{1}", entry.FieldEntryCount, entry.FirstFieldEntryIndex);
if ((entry.Options & RecordEntryOptions.Timestamped) == RecordEntryOptions.Timestamped)
Console.WriteLine(" Timestamped");
if ((entry.Options & RecordEntryOptions.Local) == RecordEntryOptions.Local)
Console.WriteLine(" Local");
if ((entry.Options & RecordEntryOptions.Static) == RecordEntryOptions.Static)
Console.WriteLine(" Static");
if ((entry.Options & RecordEntryOptions.ContainsCompoundKey) == RecordEntryOptions.ContainsCompoundKey)
Console.WriteLine(" Contains a compound key");
using (new ColorSwitch(ConsoleColor.Yellow)) {
if (entry.FileEntryIndex < 0 || entry.FileEntryIndex + 1 > ds.FileEntries.Count)
Console.WriteLine(" File #{0} does not exist", entry.FileEntryIndex);
if (entry.FirstFieldEntryIndex < 0 || entry.FirstFieldEntryIndex + 1 > ds.FieldEntries.Count)
Console.WriteLine(" Field #{0} does not exist", entry.FirstFieldEntryIndex);
// TODO: No overlapping values when using the first index / count attributes
if (entry.FirstFieldEntryIndex + entry.FieldEntryCount > ds.FieldEntries.Count)
Console.WriteLine(" Uses more fields than available in the field entries list");
}
}
if (ds.RecordEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--- FIELD ENTRIES --------------------------------");
for (int i = 0; i < ds.FieldEntries.Count; i++) {
FieldEntry entry = ds.FieldEntries[i];
Console.WriteLine(" #{0}: Field from record #{1}", i, entry.RecordEntryIndex);
Console.WriteLine(" Type: {0}[{1},{2},{3}]", entry.Type, entry.Dimension1, entry.Dimension2, entry.Dimension3);
if (entry.Type == FieldType.CompoundKey) {
Console.WriteLine(" First field of the compound key: #{0}", entry.Offset);
Console.WriteLine(" Length: {0}", entry.Length);
} else
Console.WriteLine(" Found in rows at offset {0}, length {1}", entry.Offset, entry.Length);
Console.WriteLine(" B-Tree index: {0}", entry.KeyType == FieldKeyType.None ? "No" : "Yes" + (entry.KeyType == FieldKeyType.Unique ? ", with unique constraint" : ""));
if (entry.KeyType != FieldKeyType.None)
Console.WriteLine(" Key file #{0}, key number #{1}", entry.KeyFileIndex, entry.KeyNumber);
if ((entry.Options & FieldEntryOptions.Unsigned) == FieldEntryOptions.Unsigned)
Console.WriteLine(" Type is unsigned");
if ((entry.Options & FieldEntryOptions.Optional) == FieldEntryOptions.Optional)
Console.WriteLine(" Optional");
if ((entry.Options & FieldEntryOptions.StructField) == FieldEntryOptions.StructField)
Console.WriteLine(" Part of a previously listed GroupedField");
if ((entry.Options & FieldEntryOptions.CompoundKeyMember) == FieldEntryOptions.CompoundKeyMember)
Console.WriteLine(" Part of a compound key");
if ((entry.Options & FieldEntryOptions.SortField) == FieldEntryOptions.SortField)
Console.WriteLine(" Part of a sort entry");
using (new ColorSwitch(ConsoleColor.Yellow)) {
if (entry.RecordEntryIndex < 0 || entry.RecordEntryIndex + 1 > ds.RecordEntries.Count)
Console.WriteLine(" Record #{0} does not exist", entry.RecordEntryIndex);
if (entry.Type == FieldType.CompoundKey) {
if (entry.Offset < 0 || entry.Offset + 1 > ds.FieldEntries.Count)
Console.WriteLine(" Field #{0} does not exist", entry.Offset);
if (entry.Offset + entry.Length > ds.FieldEntries.Count)
Console.WriteLine(" Uses more fields than available in the fields list");
for (
int j = Math.Max((short)0, entry.Offset);
j < Math.Min(ds.FieldEntries.Count - Math.Max((short)0, entry.Offset), entry.Length);
j++
) {
FieldEntry keyMember = ds.FieldEntries[j];
if ((entry.Options & FieldEntryOptions.CompoundKeyMember) != FieldEntryOptions.CompoundKeyMember)
Console.WriteLine(" Field #{0} is not flagged as part of a compound key", j);
}
}
if ((entry.Options & FieldEntryOptions.StructField) == FieldEntryOptions.StructField) {
bool found = false;
// Walk the field list backwards to try to find the related GroupedField
for (int j = i - 1; j >= 0; j--) {
FieldEntry previousField = ds.FieldEntries[j];
if (previousField.Type == FieldType.GroupedField) {
found = true;
break;
} else if ((entry.Options & FieldEntryOptions.StructField) != FieldEntryOptions.StructField) {
/*
* A GroupedField should only be followed by fields flagged as part of a GroupedField;
* once a field that does not have this flag is found, it means the GroupedField ends.
* If we find a field that is neither a GroupedField nor flagged as part of one, stop.
*/
break;
}
}
if (!found)
Console.WriteLine(" GroupedField not found");
}
}
}
if (ds.FieldEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--- SET ENTRIES ----------------------------------");
for (int i = 0; i < ds.SetEntries.Count; i++) {
SetEntry entry = ds.SetEntries[i];
Console.WriteLine(" #{0}: Set from record #{1}", i, entry.RecordEntryIndex);
Console.WriteLine(" {0} member entries starting at #{1}", entry.MemberCount, entry.FirstMemberEntryIndex);
Console.WriteLine(" Offset: {0}", entry.Offset);
Console.WriteLine(" Ordering: {0}", entry.Ordering);
if ((entry.Options & SetEntryOptions.Timestamped) == SetEntryOptions.Timestamped)
Console.WriteLine(" Timestamped");
using (new ColorSwitch(ConsoleColor.Yellow)) {
if (entry.RecordEntryIndex < 0 || entry.RecordEntryIndex + 1 > ds.RecordEntries.Count)
Console.WriteLine(" Record #{0} does not exist", entry.RecordEntryIndex);
if (entry.FirstMemberEntryIndex < 0 || entry.FirstMemberEntryIndex + 1 > ds.MemberEntries.Count)
Console.WriteLine(" Member entry #{0} does not exist", entry.FirstMemberEntryIndex);
// TODO: No overlapping values when using the first index / count attributes
if (entry.FirstMemberEntryIndex + entry.MemberCount > ds.MemberEntries.Count)
Console.WriteLine(" Uses more member entries than available in the member entries list");
for (
int j = Math.Max((short)0, entry.FirstMemberEntryIndex);
j < Math.Min(ds.MemberEntries.Count - Math.Max((short)0, entry.FirstMemberEntryIndex), entry.MemberCount);
j++
) {
MemberEntry member = ds.MemberEntries[j];
for (
int k = Math.Max((short)0, member.FirstSortEntryIndex);
k < Math.Min(ds.SortEntries.Count - Math.Max((short)0, member.FirstSortEntryIndex), member.SortEntryCount);
k++
) {
SortEntry sort = ds.SortEntries[k];
if (sort.SetIndex != i)
Console.WriteLine(" Sort entry #{0} in member entry #{1} references set #{2} instead", k, j, sort.SetIndex);
}
}
}
}
if (ds.SetEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--- MEMBER ENTRIES -------------------------------");
for (int i = 0; i < ds.MemberEntries.Count; i++) {
MemberEntry entry = ds.MemberEntries[i];
Console.WriteLine(" #{0}: member entry from record #{1}", i, entry.RecordEntryIndex);
Console.WriteLine(" Offset: {0}", entry.Offset);
Console.WriteLine(" {0} sort entries starting at #{1}", entry.SortEntryCount, entry.FirstSortEntryIndex);
using (new ColorSwitch(ConsoleColor.Yellow)) {
if (entry.RecordEntryIndex < 0 || entry.RecordEntryIndex + 1 > ds.RecordEntries.Count)
Console.WriteLine(" Record #{0} does not exist", entry.RecordEntryIndex);
if (entry.FirstSortEntryIndex < 0 || entry.FirstSortEntryIndex + 1 > ds.SortEntries.Count)
Console.WriteLine(" Sort entry #{0} does not exist", entry.FirstSortEntryIndex);
// TODO: No overlapping values when using the first index / count attributes
if (entry.FirstSortEntryIndex + entry.SortEntryCount > ds.SortEntries.Count)
Console.WriteLine(" Uses more sort entries than available in the sort entries list");
}
}
if (ds.MemberEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--- SORT ENTRIES ---------------------------------");
for (int i = 0; i < ds.SortEntries.Count; i++) {
SortEntry entry = ds.SortEntries[i];
Console.WriteLine(" #{0}: set #{1} - field #{2}", i, entry.SetIndex, entry.FieldIndex);
using (new ColorSwitch(ConsoleColor.Yellow)) {
if (entry.SetIndex < 0 || entry.SetIndex + 1 > ds.SetEntries.Count)
Console.WriteLine(" Set #{0} does not exist", entry.SetIndex);
if (entry.FieldIndex < 0 || entry.FieldIndex + 1 > ds.FieldEntries.Count)
Console.WriteLine(" Field #{0} does not exist", entry.FieldIndex);
else if ((ds.FieldEntries[entry.FieldIndex].Options & FieldEntryOptions.SortField) != FieldEntryOptions.SortField)
Console.WriteLine(" Field #{0} is not flagged as part of a sort entry", entry.FieldIndex);
}
}
if (ds.SortEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--- KEY ENTRIES ----------------------------------");
for (int i = 0; i < ds.KeyEntries.Count; i++) {
KeyEntry entry = ds.KeyEntries[i];
Console.WriteLine(" #{0}: field #{1} on compound key field #{2}", i, entry.FieldIndex, entry.KeyFieldIndex);
Console.WriteLine(" Offset: {0}", entry.Offset);
Console.WriteLine(" Ordering: {0}", entry.Ordering);
using (new ColorSwitch(ConsoleColor.Yellow)) {
if (entry.KeyFieldIndex < 0 || entry.KeyFieldIndex + 1 > ds.FieldEntries.Count)
Console.WriteLine(" Key field #{0} does not exist", entry.KeyFieldIndex);
else if (ds.FieldEntries[entry.KeyFieldIndex].Type != FieldType.CompoundKey)
Console.WriteLine(" Key field #{0} is not a compound key field", entry.KeyFieldIndex);
if (entry.FieldIndex < 0 || entry.FieldIndex + 1 > ds.FieldEntries.Count)
Console.WriteLine(" Field #{0} does not exist", entry.FieldIndex);
else if ((ds.FieldEntries[entry.FieldIndex].Options & FieldEntryOptions.CompoundKeyMember) != FieldEntryOptions.CompoundKeyMember)
Console.WriteLine(" Field #{0} is not flagged as part of a compound key", entry.FieldIndex);
}
}
if (ds.KeyEntries.Count < 1)
Console.WriteLine(" No entries found.");
Console.WriteLine();
Console.WriteLine("--------------------------------------------------");
}
}
}