Deleted obsolete code New additions: base for an updater WIP: Making the editor read FLW1 and FLI1 from provided file
354 lines
13 KiB
C#
354 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace BMGEditor
|
|
{
|
|
public class BMG
|
|
{
|
|
private FileBase m_File;
|
|
private Bcsv m_File_Tbl;
|
|
private const Int32 m_Signature = 0x4D455347;
|
|
private const Int32 m_FileType = 0x626D6731;
|
|
private const Byte m_ExpectedEncoding = 0x02;
|
|
|
|
private const Int32 INF1magic = 0x494E4631;
|
|
private const Int32 DAT1magic = 0x44415431;
|
|
private const Int32 FLW1magic = 0x464C5731;
|
|
private const Int32 FLI1magic = 0x464C4931;
|
|
|
|
//Header
|
|
private Int32 fileMagic1;
|
|
private Int32 fileMagic2;
|
|
private UInt32 fileSize;
|
|
private UInt32 numberOfSections;
|
|
private Byte fileEncoding;
|
|
|
|
//INF1
|
|
private Int32 INF1sectionMagic;
|
|
private UInt32 INF1sectionSize;
|
|
private UInt16 INF1itemNumber;
|
|
private UInt16 INF1itemLength;
|
|
|
|
//DAT1
|
|
private Int64 DAT1sectionStart;
|
|
private Int32 DAT1sectionMagic;
|
|
private UInt32 DAT1sectionSize;
|
|
private Int64 strPoolStart;
|
|
|
|
//FLW1
|
|
private Int64 FLW1sectionStart;
|
|
private Int32 FLW1sectionMagic;
|
|
private UInt32 FLW1sectionSize;
|
|
private List<Byte> FLW1sectionContent;
|
|
|
|
//FLI1
|
|
private Int64 FLI1sectionStart;
|
|
private Int32 FLI1sectionMagic;
|
|
private UInt32 FLI1sectionSize;
|
|
private List<Byte> FLI1sectionContent;
|
|
|
|
public BMG(FileBase file, Bcsv tbl)
|
|
{
|
|
m_File_Tbl = tbl;
|
|
m_File = file;
|
|
m_File.BigEndian = true;
|
|
m_File.Encoding = Encoding.BigEndianUnicode;
|
|
m_File.Stream.Position = 0;
|
|
|
|
fileMagic1 = m_File.Reader.ReadInt32();
|
|
fileMagic2 = m_File.Reader.ReadInt32();
|
|
if (fileMagic1 == m_Signature && fileMagic2 == m_FileType) { }
|
|
else
|
|
throw new Exception("Not a valid BMG file.");
|
|
|
|
fileSize = m_File.Reader.ReadUInt32();
|
|
numberOfSections = m_File.Reader.ReadUInt32();
|
|
fileEncoding = m_File.Reader.ReadByte();
|
|
if (fileEncoding != m_ExpectedEncoding) throw new Exception("sorry but no");
|
|
m_File.Stream.Position = 0x20;
|
|
|
|
//INF1
|
|
INF1sectionMagic = m_File.Reader.ReadInt32();
|
|
if (INF1sectionMagic != INF1magic)
|
|
{
|
|
throw new Exception("BMG File exists but isn't in the expected format");
|
|
}
|
|
INF1sectionSize = m_File.Reader.ReadUInt32();
|
|
INF1itemNumber = m_File.Reader.ReadUInt16();
|
|
INF1itemLength = m_File.Reader.ReadUInt16();
|
|
m_File.Stream.Position += 0x04;
|
|
|
|
Entries = new List<TextEntry>();
|
|
for (int i = 0; i < INF1itemNumber; i++)
|
|
{
|
|
TextEntry txtEntry = new TextEntry();
|
|
txtEntry.entryNo = i;
|
|
txtEntry.offset = m_File.Reader.ReadUInt32();
|
|
txtEntry.unk1 = m_File.Reader.ReadByte();
|
|
txtEntry.cameraOpt = m_File.Reader.ReadByte();
|
|
txtEntry.sndEffectOpt = m_File.Reader.ReadByte();
|
|
txtEntry.unk2 = m_File.Reader.ReadByte();
|
|
txtEntry.messageTriggerOpt = m_File.Reader.ReadByte();
|
|
txtEntry.messageLayoutOpt = m_File.Reader.ReadByte();
|
|
txtEntry.messageAreaOpt = m_File.Reader.ReadByte();
|
|
|
|
Entries.Add(txtEntry);
|
|
m_File.Stream.Position += 0x01;
|
|
}
|
|
|
|
while (m_File.Reader.ReadByte() != 0x44)
|
|
m_File.Stream.Position += 0x01;
|
|
m_File.Stream.Position -= 1;
|
|
|
|
//DAT1
|
|
DAT1sectionStart = m_File.Stream.Position;
|
|
DAT1sectionMagic = m_File.Reader.ReadInt32();
|
|
DAT1sectionSize = m_File.Reader.ReadUInt32();
|
|
|
|
strPoolStart = m_File.Stream.Position;
|
|
if (DAT1sectionMagic != DAT1magic) throw new Exception("BMG exists but isn\'t in the expected format");
|
|
for (int j = 0; j < INF1itemNumber; j++)
|
|
{
|
|
m_File.Stream.Position = strPoolStart + Entries[j].offset;
|
|
Entries[j].text = ReadWideCharString();
|
|
}
|
|
|
|
int l = 0;
|
|
foreach (Bcsv.Entry bcsvEntry in m_File_Tbl.Entries)
|
|
{
|
|
string entName = bcsvEntry[563954530].ToString();
|
|
Entries[l].entryName = entName;
|
|
l++;
|
|
}
|
|
|
|
//FLW1
|
|
m_File.Stream.Position = DAT1sectionStart + DAT1sectionSize;
|
|
FLW1sectionStart = m_File.Stream.Position;
|
|
FLW1sectionMagic = m_File.Reader.ReadInt32();
|
|
if (FLW1sectionMagic != FLW1magic) throw new Exception("FLW1 section missing. Check your BMG file");
|
|
FLW1sectionSize = m_File.Reader.ReadUInt32();
|
|
|
|
|
|
//FLI1
|
|
m_File.Stream.Position = FLW1sectionStart + FLW1sectionSize;
|
|
FLI1sectionStart = m_File.Stream.Position;
|
|
FLI1sectionMagic = m_File.Reader.ReadInt32();
|
|
if (FLI1sectionMagic != FLI1magic) throw new Exception("FLI1 section missing. Check your BMG file");
|
|
FLI1sectionSize = m_File.Reader.ReadUInt32();
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
m_File.Close();
|
|
}
|
|
|
|
public class TextEntry
|
|
{
|
|
public int entryNo;
|
|
public string text;
|
|
public UInt32 offset;
|
|
public string entryName;
|
|
|
|
//Properties
|
|
public byte unk1;
|
|
public byte cameraOpt;
|
|
public byte sndEffectOpt;
|
|
public byte unk2;
|
|
public byte messageTriggerOpt;
|
|
public byte messageLayoutOpt;
|
|
public byte messageAreaOpt;
|
|
}
|
|
|
|
public class EscapeSequence
|
|
{
|
|
public byte length;
|
|
public byte unk1;
|
|
public List<Byte> binValue = new List<Byte>();
|
|
}
|
|
|
|
public List<Byte> BytesFromEscapeSequence(EscapeSequence escSeq)
|
|
{
|
|
List<Byte> ret = new List<byte>();
|
|
ret.Add(0x00);
|
|
ret.Add(0x1A);
|
|
ret.Add(escSeq.length);
|
|
ret.Add(escSeq.unk1);
|
|
|
|
foreach (Byte b in escSeq.binValue)
|
|
{
|
|
ret.Add(b);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public string ReadWideCharString()
|
|
{
|
|
byte escSeqLength;
|
|
|
|
string ret = "";
|
|
char c;
|
|
while ((c = m_File.Reader.ReadChar()) != '\0')
|
|
{
|
|
if (c == 0x001A)
|
|
{
|
|
ret += "*" ;
|
|
escSeqLength = m_File.Reader.ReadByte();
|
|
ret += $"{String.Format("{0:X2}", escSeqLength)}";
|
|
for (int k = 3; k < escSeqLength; k++)
|
|
{
|
|
ret += String.Format("{0:X2}", m_File.Reader.ReadByte());
|
|
}
|
|
ret += " ";
|
|
}
|
|
else
|
|
ret += c;
|
|
}
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
public void AddNewEntry(string newEntryName)
|
|
{
|
|
TextEntry newEntry = new TextEntry();
|
|
newEntry.entryName = newEntryName;
|
|
newEntry.entryNo = INF1itemNumber;
|
|
INF1itemNumber++;
|
|
Entries.Add(newEntry);
|
|
}
|
|
|
|
public void DeleteEntry(Int32 entryIndex) //Problem: if custom entries, alphabetical index != in-game/tbl index
|
|
{
|
|
Entries.Remove(Entries[entryIndex]);
|
|
INF1itemNumber--;
|
|
}
|
|
|
|
public void WriteToFile()
|
|
{
|
|
//File header
|
|
m_File.Stream.Position = 0;
|
|
m_File.Writer.Write((Int32)m_Signature);
|
|
m_File.Writer.Write((Int32)m_FileType);
|
|
m_File.Writer.Write((UInt32)0x00); //Final fileSize will be written at the end
|
|
m_File.Writer.Write((UInt32)0x04); //Number of sections, always 4 in Super Mario Galaxy, this editor isn't meant to be used on anything else anyway.
|
|
m_File.Writer.Write((Byte)0x02); //Encoding
|
|
while (m_File.Stream.Position != 0x20) m_File.Writer.Write((Byte)0x00); // The 15 bytes of nothing
|
|
|
|
//INF1 section
|
|
Int64 INF1start = m_File.Stream.Position;
|
|
m_File.Writer.Write((Int32)INF1magic);
|
|
m_File.Writer.Write((UInt32)(0x10 + (INF1itemNumber * INF1itemLength)));
|
|
m_File.Writer.Write((UInt16)INF1itemNumber);
|
|
m_File.Writer.Write((UInt16)INF1itemLength);
|
|
m_File.Writer.Write((UInt32)0x00);
|
|
foreach (TextEntry entry in Entries)
|
|
{
|
|
m_File.Writer.Write((UInt32)0x00);
|
|
m_File.Writer.Write((Byte)entry.unk1);
|
|
m_File.Writer.Write((Byte)entry.cameraOpt);
|
|
m_File.Writer.Write((Byte)entry.sndEffectOpt);
|
|
m_File.Writer.Write((Byte)entry.unk2);
|
|
m_File.Writer.Write((Byte)entry.messageTriggerOpt);
|
|
m_File.Writer.Write((Byte)entry.messageLayoutOpt);
|
|
m_File.Writer.Write((Byte)entry.messageAreaOpt);
|
|
m_File.Writer.Write((Byte)0xFF);
|
|
}
|
|
while (m_File.Stream.Position % 16 != 0x00)
|
|
m_File.Writer.Write((Byte)0x00);
|
|
|
|
//DAT1
|
|
Int64 DAT1start = m_File.Stream.Position;
|
|
m_File.Writer.Write((Int32)DAT1magic);
|
|
m_File.Writer.Write((UInt32)0x00); //section size, will be defined later
|
|
//String pool
|
|
List<Int64> strPos = new List<Int64>();
|
|
foreach (TextEntry entry in Entries)
|
|
{
|
|
strPos.Add(m_File.Stream.Position - (DAT1start + 0x08));
|
|
//Doing this because for some reason there's a random char appearing at the beggining of each str
|
|
if (entry.text == "") m_File.Writer.Write((UInt16)0x00);
|
|
else
|
|
{
|
|
List<char> strToWrite = new List<char>();
|
|
foreach (char c in entry.text)
|
|
{
|
|
strToWrite.Add(c);
|
|
}
|
|
|
|
for (int i = 0; i < strToWrite.Count; i++)
|
|
{
|
|
if (strToWrite[i].Equals('*'))
|
|
{
|
|
EscapeSequence escSeq = new EscapeSequence();
|
|
escSeq.length = Byte.Parse(String.Concat(strToWrite[i + 1], strToWrite[i + 2]), NumberStyles.HexNumber);
|
|
escSeq.unk1 = Byte.Parse(String.Concat(strToWrite[i + 3], strToWrite[i + 4]), NumberStyles.HexNumber);
|
|
i += 0x05;
|
|
for (int j = 0; j < escSeq.length * 2 - 8; j += 2)
|
|
{
|
|
escSeq.binValue.Add(Byte.Parse(String.Concat(strToWrite[i + j], strToWrite[i + j + 1]), NumberStyles.HexNumber));
|
|
}
|
|
|
|
List<Byte> seqToWrite = BytesFromEscapeSequence(escSeq);
|
|
foreach (Byte b in seqToWrite)
|
|
{
|
|
m_File.Writer.Write(b);
|
|
}
|
|
i += escSeq.length * 2 - 8;
|
|
|
|
|
|
}
|
|
else
|
|
m_File.Writer.Write(strToWrite[i]);
|
|
}
|
|
|
|
m_File.Writer.Write((UInt16)0x00);
|
|
|
|
}
|
|
}
|
|
|
|
while (m_File.Stream.Position % 16 != 0x00)
|
|
m_File.Stream.Position += 0x01;
|
|
Int64 DAT1size = m_File.Stream.Position - DAT1start;
|
|
Int64 DAT1end = m_File.Stream.Position;
|
|
m_File.Stream.Position = DAT1start + 0x04;
|
|
m_File.Writer.Write((UInt32)DAT1size);
|
|
|
|
//Setting offsets in INF1
|
|
m_File.Stream.Position = INF1start;
|
|
m_File.Stream.Position += 0x10;
|
|
for (int index = 0; index < INF1itemNumber; index++)
|
|
{
|
|
m_File.Writer.Write((UInt32)strPos[index]);
|
|
m_File.Stream.Position += 0x08;
|
|
}
|
|
m_File.Stream.Position = DAT1end;
|
|
|
|
m_File.Writer.Write((Int32)FLW1magic);
|
|
m_File.Writer.Write((UInt32)FLW1sectionSize);
|
|
//m_File.Writer.Write(FLW1sectionContent);
|
|
|
|
m_File.Writer.Write((Int32)FLI1magic);
|
|
m_File.Writer.Write((UInt32)FLI1sectionSize);
|
|
//m_File.Writer.Write(FLI1sectionContent);
|
|
|
|
Int64 newFileSize = m_File.Stream.Position;
|
|
m_File.Stream.Position = 0x08;
|
|
m_File.Writer.Write((UInt32)newFileSize);
|
|
|
|
m_File.Stream.SetLength(newFileSize);
|
|
|
|
m_File.Flush();
|
|
}
|
|
|
|
public void NukeFile() //Yay, its code has been moved into WriteToFile()!!
|
|
{
|
|
|
|
|
|
}
|
|
|
|
public List<TextEntry> Entries;
|
|
}
|
|
}
|