Added files from the C# Whitehole that I can use
Added a x86_64 build conf
This commit is contained in:
@@ -8,13 +8,19 @@ EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Debug|x64.Build.0 = Debug|x64
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Release|x64.ActiveCfg = Release|x64
|
||||
{E7F50E37-2F2C-4918-80E0-E23D484875B8}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
362
BMGEditor/Bcsv.cs
Normal file
362
BMGEditor/Bcsv.cs
Normal file
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
public class Bcsv
|
||||
{
|
||||
public Bcsv(FileBase file)
|
||||
{
|
||||
m_File = file;
|
||||
m_File.BigEndian = true;
|
||||
m_File.Encoding = Encoding.GetEncoding("shift-jis");
|
||||
|
||||
Fields = new Dictionary<uint, Field>();
|
||||
Entries = new List<Entry>();
|
||||
|
||||
m_File.Stream.Position = 0;
|
||||
uint entrycount = m_File.Reader.ReadUInt32();
|
||||
uint fieldcount = m_File.Reader.ReadUInt32();
|
||||
uint dataoffset = m_File.Reader.ReadUInt32();
|
||||
uint entrydatasize = m_File.Reader.ReadUInt32();
|
||||
|
||||
uint stringtableoffset = (uint)(dataoffset + (entrycount * entrydatasize));
|
||||
|
||||
for (uint i = 0; i < fieldcount; i++)
|
||||
{
|
||||
Field field = new Field();
|
||||
m_File.Stream.Position = 0x10 + (0xC * i);
|
||||
|
||||
field.NameHash = m_File.Reader.ReadUInt32();
|
||||
field.Mask = m_File.Reader.ReadUInt32();
|
||||
field.EntryOffset = m_File.Reader.ReadUInt16();
|
||||
field.ShiftAmount = m_File.Reader.ReadByte();
|
||||
field.Type = m_File.Reader.ReadByte();
|
||||
|
||||
string fieldname = Bcsv.HashToFieldName(field.NameHash);
|
||||
field.Name = fieldname;
|
||||
Fields.Add(field.NameHash, field);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < entrycount; i++)
|
||||
{
|
||||
Entry entry = new Entry();
|
||||
|
||||
foreach (Field field in Fields.Values)
|
||||
{
|
||||
m_File.Stream.Position = dataoffset + (i * entrydatasize) + field.EntryOffset;
|
||||
|
||||
object val = null;
|
||||
switch (field.Type)
|
||||
{
|
||||
case 0:
|
||||
case 3:
|
||||
val = (uint)((m_File.Reader.ReadUInt32() & field.Mask) >> field.ShiftAmount);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
val = (ushort)((m_File.Reader.ReadUInt16() & field.Mask) >> field.ShiftAmount);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
val = (byte)((m_File.Reader.ReadByte() & field.Mask) >> field.ShiftAmount);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
val = m_File.Reader.ReadSingle();
|
||||
break;
|
||||
|
||||
case 6:
|
||||
uint str_offset = m_File.Reader.ReadUInt32();
|
||||
m_File.Stream.Position = stringtableoffset + str_offset;
|
||||
val = m_File.ReadString();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Bcsv: unsupported data type " + field.Type.ToString());
|
||||
}
|
||||
|
||||
entry.Add(field.NameHash, val);
|
||||
}
|
||||
|
||||
Entries.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
int[] datasizes = { 4, -1, 4, 4, 2, 1, 4 };
|
||||
uint entrysize = 0;
|
||||
|
||||
foreach (Field field in Fields.Values)
|
||||
{
|
||||
ushort fieldend = (ushort)(field.EntryOffset + datasizes[field.Type]);
|
||||
if (fieldend > entrysize) entrysize = fieldend;
|
||||
}
|
||||
|
||||
uint dataoffset = (uint)(0x10 + (0xC * Fields.Count));
|
||||
uint stringtableoffset = (uint)(dataoffset + (Entries.Count * entrysize));
|
||||
uint curstring = 0;
|
||||
|
||||
m_File.Stream.SetLength(stringtableoffset);
|
||||
|
||||
m_File.Stream.Position = 0;
|
||||
m_File.Writer.Write((uint)Entries.Count);
|
||||
m_File.Writer.Write((uint)Fields.Count);
|
||||
m_File.Writer.Write(dataoffset);
|
||||
m_File.Writer.Write(entrysize);
|
||||
|
||||
foreach (Field field in Fields.Values)
|
||||
{
|
||||
m_File.Writer.Write(field.NameHash);
|
||||
m_File.Writer.Write(field.Mask);
|
||||
m_File.Writer.Write(field.EntryOffset);
|
||||
m_File.Writer.Write(field.ShiftAmount);
|
||||
m_File.Writer.Write(field.Type);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
Dictionary<string, uint> stringoffsets = new Dictionary<string, uint>();
|
||||
|
||||
foreach (Entry entry in Entries)
|
||||
{
|
||||
foreach (Field field in Fields.Values)
|
||||
{
|
||||
uint valoffset = (uint)(dataoffset + (i * entrysize) + field.EntryOffset);
|
||||
m_File.Stream.Position = valoffset;
|
||||
|
||||
switch (field.Type)
|
||||
{
|
||||
case 0:
|
||||
case 3:
|
||||
{
|
||||
uint val = m_File.Reader.ReadUInt32();
|
||||
val &= ~field.Mask;
|
||||
val |= (((uint)entry[field.NameHash] << field.ShiftAmount) & field.Mask);
|
||||
|
||||
m_File.Stream.Position = valoffset;
|
||||
m_File.Writer.Write(val);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
ushort val = m_File.Reader.ReadUInt16();
|
||||
val &= (ushort)(~field.Mask);
|
||||
val |= (ushort)(((ushort)entry[field.NameHash] << field.ShiftAmount) & field.Mask);
|
||||
|
||||
m_File.Stream.Position = valoffset;
|
||||
m_File.Writer.Write(val);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
{
|
||||
byte val = m_File.Reader.ReadByte();
|
||||
val &= (byte)(~field.Mask);
|
||||
val |= (byte)(((byte)entry[field.NameHash] << field.ShiftAmount) & field.Mask);
|
||||
|
||||
m_File.Stream.Position = valoffset;
|
||||
m_File.Writer.Write(val);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
m_File.Writer.Write((float)entry[field.NameHash]);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
{
|
||||
string val = (string)entry[field.NameHash];
|
||||
if (stringoffsets.ContainsKey(val))
|
||||
m_File.Writer.Write(stringoffsets[val]);
|
||||
else
|
||||
{
|
||||
stringoffsets.Add(val, curstring);
|
||||
m_File.Writer.Write(curstring);
|
||||
m_File.Stream.Position = stringtableoffset + curstring;
|
||||
curstring += (uint)m_File.WriteString(val);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
m_File.Flush();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
m_File.Close();
|
||||
}
|
||||
|
||||
|
||||
public Field AddField(string name, int offset, byte type, uint mask, int shift, object defaultval)
|
||||
{
|
||||
int[] datasizes = { 4, -1, 4, 4, 2, 1, 4 };
|
||||
|
||||
AddHash(name); // hehe
|
||||
|
||||
int nbytes = datasizes[type];
|
||||
|
||||
if (type == 2 || type == 6)
|
||||
{
|
||||
mask = 0xFFFFFFFF;
|
||||
shift = 0;
|
||||
}
|
||||
|
||||
if (offset == -1)
|
||||
{
|
||||
foreach (Field field in Fields.Values)
|
||||
{
|
||||
ushort fieldend = (ushort)(field.EntryOffset + datasizes[field.Type]);
|
||||
if (fieldend > offset) offset = fieldend;
|
||||
}
|
||||
}
|
||||
|
||||
Field newfield = new Field();
|
||||
newfield.Name = name;
|
||||
newfield.NameHash = Bcsv.FieldNameToHash(name);
|
||||
newfield.Mask = mask;
|
||||
newfield.ShiftAmount = (byte)shift;
|
||||
newfield.Type = type;
|
||||
newfield.EntryOffset = (ushort)offset;
|
||||
Fields.Add(newfield.NameHash, newfield);
|
||||
|
||||
foreach (Entry entry in Entries)
|
||||
{
|
||||
entry.Add(name, defaultval);
|
||||
}
|
||||
|
||||
return newfield;
|
||||
}
|
||||
|
||||
public void RemoveField(string name)
|
||||
{
|
||||
uint hash = Bcsv.FieldNameToHash(name);
|
||||
Fields.Remove(hash);
|
||||
|
||||
foreach (Entry entry in Entries)
|
||||
{
|
||||
entry.Remove(hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Field
|
||||
{
|
||||
public uint NameHash;
|
||||
public uint Mask;
|
||||
public ushort EntryOffset;
|
||||
public byte ShiftAmount;
|
||||
public byte Type;
|
||||
|
||||
public string Name;
|
||||
}
|
||||
|
||||
public class Entry : Dictionary<uint, object>
|
||||
{
|
||||
public Entry()
|
||||
: base()
|
||||
{ }
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this[Bcsv.FieldNameToHash(key)];
|
||||
}
|
||||
set
|
||||
{
|
||||
this[Bcsv.FieldNameToHash(key)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string key, object val)
|
||||
{
|
||||
this.Add(Bcsv.FieldNameToHash(key), val);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return this.ContainsKey(Bcsv.FieldNameToHash(key));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string str = "BcsvEntry:";
|
||||
|
||||
foreach (KeyValuePair<uint, object> field in this)
|
||||
{
|
||||
str += " [" + field.Key.ToString("X8");
|
||||
if (Bcsv.m_HashTable.ContainsKey(field.Key))
|
||||
str += " (" + Bcsv.HashToFieldName(field.Key) + ")";
|
||||
str += "]=[" + field.Value.ToString() + "]";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private FileBase m_File;
|
||||
|
||||
public Dictionary<uint, Field> Fields;
|
||||
public List<Entry> Entries;
|
||||
|
||||
|
||||
// Field name hash support functions
|
||||
// the hash->string table is meant for debugging purposes and
|
||||
// shouldn't be used by proper code
|
||||
|
||||
public static uint FieldNameToHash(string field)
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (char ch in field)
|
||||
{
|
||||
ret *= 0x1F;
|
||||
ret += ch;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string HashToFieldName(uint hash)
|
||||
{
|
||||
if (!m_HashTable.ContainsKey(hash))
|
||||
return string.Format("[{0:X8}]", hash);
|
||||
|
||||
return m_HashTable[hash];
|
||||
}
|
||||
|
||||
public static void AddHash(string field)
|
||||
{
|
||||
uint hash = FieldNameToHash(field);
|
||||
if (!m_HashTable.ContainsKey(hash))
|
||||
m_HashTable.Add(hash, field);
|
||||
}
|
||||
|
||||
public static void PopulateHashtable()
|
||||
{
|
||||
m_HashTable = new Dictionary<uint, string>();
|
||||
|
||||
string[] lines = new string[1];
|
||||
foreach (string _line in lines)
|
||||
{
|
||||
string line = _line.Trim();
|
||||
|
||||
if (line.Length == 0) continue;
|
||||
if (line[0] == '#') continue;
|
||||
|
||||
AddHash(line);
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<uint, string> m_HashTable;
|
||||
}
|
||||
}
|
||||
113
BMGEditor/BigEndian.cs
Normal file
113
BMGEditor/BigEndian.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
public class BinaryReaderBE : BinaryReader
|
||||
{
|
||||
public BinaryReaderBE(Stream s)
|
||||
: base(s)
|
||||
{ }
|
||||
|
||||
public BinaryReaderBE(Stream s, Encoding e)
|
||||
: base(s, e)
|
||||
{ }
|
||||
|
||||
|
||||
public override short ReadInt16()
|
||||
{
|
||||
UInt16 val = base.ReadUInt16();
|
||||
return (Int16)((val >> 8) | (val << 8));
|
||||
}
|
||||
|
||||
public override int ReadInt32()
|
||||
{
|
||||
UInt32 val = base.ReadUInt32();
|
||||
return (Int32)((val >> 24) | ((val & 0xFF0000) >> 8) | ((val & 0xFF00) << 8) | (val << 24));
|
||||
}
|
||||
|
||||
|
||||
public override ushort ReadUInt16()
|
||||
{
|
||||
UInt16 val = base.ReadUInt16();
|
||||
return (UInt16)((val >> 8) | (val << 8));
|
||||
}
|
||||
|
||||
public override uint ReadUInt32()
|
||||
{
|
||||
UInt32 val = base.ReadUInt32();
|
||||
return (UInt32)((val >> 24) | ((val & 0xFF0000) >> 8) | ((val & 0xFF00) << 8) | (val << 24));
|
||||
}
|
||||
|
||||
|
||||
public override float ReadSingle()
|
||||
{
|
||||
byte[] stuff = base.ReadBytes(4);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(stuff);
|
||||
float val = BitConverter.ToSingle(stuff, 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
public override double ReadDouble()
|
||||
{
|
||||
byte[] stuff = base.ReadBytes(8);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(stuff);
|
||||
double val = BitConverter.ToDouble(stuff, 0);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class BinaryWriterBE : BinaryWriter
|
||||
{
|
||||
public BinaryWriterBE(Stream s)
|
||||
: base(s)
|
||||
{ }
|
||||
|
||||
public BinaryWriterBE(Stream s, Encoding e)
|
||||
: base(s, e)
|
||||
{ }
|
||||
|
||||
|
||||
public override void Write(short value)
|
||||
{
|
||||
ushort val = (ushort)value;
|
||||
base.Write((short)((val >> 8) | (val << 8)));
|
||||
}
|
||||
|
||||
public override void Write(int value)
|
||||
{
|
||||
uint val = (uint)value;
|
||||
base.Write((int)((val >> 24) | ((val & 0xFF0000) >> 8) | ((val & 0xFF00) << 8) | (val << 24)));
|
||||
}
|
||||
|
||||
|
||||
public override void Write(ushort value)
|
||||
{
|
||||
base.Write((ushort)((value >> 8) | (value << 8)));
|
||||
}
|
||||
|
||||
public override void Write(uint value)
|
||||
{
|
||||
base.Write((uint)((value >> 24) | ((value & 0xFF0000) >> 8) | ((value & 0xFF00) << 8) | (value << 24)));
|
||||
}
|
||||
|
||||
|
||||
public override void Write(float value)
|
||||
{
|
||||
byte[] stuff = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(stuff);
|
||||
base.Write(stuff);
|
||||
}
|
||||
|
||||
public override void Write(double value)
|
||||
{
|
||||
byte[] stuff = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(stuff);
|
||||
base.Write(stuff);
|
||||
}
|
||||
}
|
||||
}
|
||||
181
BMGEditor/Compression.cs
Normal file
181
BMGEditor/Compression.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
public static class Yaz0
|
||||
{
|
||||
// TODO: put compression in use?
|
||||
// note: compression is slow when dealing with large files (eg. 3D models)
|
||||
// it should be made optional, and show a progress dialog and all
|
||||
|
||||
private static void FindOccurence(byte[] data, int pos, ref int offset, ref int length)
|
||||
{
|
||||
offset = -1;
|
||||
length = 0;
|
||||
|
||||
if (pos >= data.Length - 2) return;
|
||||
|
||||
Dictionary<int, int> occurences = new Dictionary<int, int>();
|
||||
|
||||
int len = 0;
|
||||
int start = (pos > 4096) ? pos - 4096 : 0;
|
||||
for (int i = start; i < pos; i++)
|
||||
{
|
||||
if (i >= data.Length - 2) break;
|
||||
|
||||
if (data[i] != data[pos] || data[i + 1] != data[pos + 1] || data[i + 2] != data[pos + 2])
|
||||
continue;
|
||||
|
||||
len = 3;
|
||||
while ((i + len < data.Length) && (pos + len < data.Length) && (data[i + len] == data[pos + len]))
|
||||
len++;
|
||||
|
||||
occurences.Add(i, len);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, int> occ in occurences)
|
||||
{
|
||||
if (occ.Value > length)
|
||||
{
|
||||
offset = occ.Key;
|
||||
length = occ.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Compress(ref byte[] data)
|
||||
{
|
||||
if (data[0] == 'Y' && data[1] == 'a' && data[2] == 'z' && data[3] == '0')
|
||||
return;
|
||||
|
||||
byte[] output = new byte[16 + data.Length + (data.Length / 8)];
|
||||
|
||||
output[0] = (byte)'Y';
|
||||
output[1] = (byte)'a';
|
||||
output[2] = (byte)'z';
|
||||
output[3] = (byte)'0';
|
||||
|
||||
uint fullsize = (uint)data.Length;
|
||||
output[4] = (byte)(fullsize >> 24);
|
||||
output[5] = (byte)(fullsize >> 16);
|
||||
output[6] = (byte)(fullsize >> 8);
|
||||
output[7] = (byte)fullsize;
|
||||
|
||||
int inpos = 0, outpos = 16;
|
||||
int occ_offset = -1, occ_length = 0;
|
||||
|
||||
while (inpos < fullsize)
|
||||
{
|
||||
int datastart = outpos + 1;
|
||||
byte block = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
block <<= 1;
|
||||
|
||||
if (inpos < data.Length)
|
||||
{
|
||||
if (occ_offset == -2)
|
||||
FindOccurence(data, inpos, ref occ_offset, ref occ_length);
|
||||
|
||||
int next_offset = -1, next_length = 0;
|
||||
FindOccurence(data, inpos + 1, ref next_offset, ref next_length);
|
||||
if (next_length > occ_length + 1) occ_offset = -1;
|
||||
|
||||
if (occ_offset != -1)
|
||||
{
|
||||
int disp = inpos - occ_offset - 1;
|
||||
if (disp > 4095) throw new Exception("DISP OUT OF RANGE!");
|
||||
|
||||
if (occ_length > 17)
|
||||
{
|
||||
if (occ_length > 273) occ_length = 273;
|
||||
|
||||
output[datastart++] = (byte)(disp >> 8);
|
||||
output[datastart++] = (byte)disp;
|
||||
output[datastart++] = (byte)(occ_length - 18);
|
||||
}
|
||||
else
|
||||
{
|
||||
output[datastart++] = (byte)(((occ_length - 2) << 4) | (disp >> 8));
|
||||
output[datastart++] = (byte)disp;
|
||||
}
|
||||
|
||||
inpos += occ_length;
|
||||
occ_offset = -2;
|
||||
}
|
||||
else
|
||||
{
|
||||
output[datastart++] = data[inpos++];
|
||||
block |= 0x01;
|
||||
}
|
||||
|
||||
if (occ_offset != -2)
|
||||
{
|
||||
occ_offset = next_offset;
|
||||
occ_length = next_length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output[outpos] = block;
|
||||
outpos = datastart;
|
||||
}
|
||||
|
||||
Array.Resize(ref data, outpos);
|
||||
Array.Resize(ref output, outpos);
|
||||
output.CopyTo(data, 0);
|
||||
}
|
||||
|
||||
// inspired from http://www.amnoid.de/gc/yaz0.txt
|
||||
public static void Decompress(ref byte[] data)
|
||||
{
|
||||
if (data[0] != 'Y' || data[1] != 'a' || data[2] != 'z' || data[3] != '0')
|
||||
return;
|
||||
|
||||
int fullsize = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
|
||||
byte[] output = new byte[fullsize];
|
||||
|
||||
int inpos = 16, outpos = 0;
|
||||
while (outpos < fullsize)
|
||||
{
|
||||
byte block = data[inpos++];
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if ((block & 0x80) != 0)
|
||||
{
|
||||
// copy one plain byte
|
||||
output[outpos++] = data[inpos++];
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy N compressed bytes
|
||||
byte b1 = data[inpos++];
|
||||
byte b2 = data[inpos++];
|
||||
|
||||
int dist = ((b1 & 0xF) << 8) | b2;
|
||||
int copysrc = outpos - (dist + 1);
|
||||
|
||||
int nbytes = b1 >> 4;
|
||||
if (nbytes == 0) nbytes = data[inpos++] + 0x12;
|
||||
else nbytes += 2;
|
||||
|
||||
for (int j = 0; j < nbytes; j++)
|
||||
output[outpos++] = output[copysrc++];
|
||||
}
|
||||
|
||||
block <<= 1;
|
||||
if (outpos >= fullsize || inpos >= data.Length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Array.Resize(ref data, fullsize);
|
||||
output.CopyTo(data, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
BMGEditor/FilesystemBase.cs
Normal file
107
BMGEditor/FilesystemBase.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
public class FilesystemBase
|
||||
{
|
||||
public virtual void Close() { }
|
||||
|
||||
public virtual string[] GetDirectories(string directory)
|
||||
{ throw new NotImplementedException("FilesystemBase.GetDirectories()"); }
|
||||
|
||||
public virtual bool DirectoryExists(string directory)
|
||||
{ throw new NotImplementedException("FilesystemBase.DirectoryExists()"); }
|
||||
|
||||
|
||||
public virtual string[] GetFiles(string directory)
|
||||
{ throw new NotImplementedException("FilesystemBase.GetFiles()"); }
|
||||
|
||||
public virtual bool FileExists(string filename)
|
||||
{ throw new NotImplementedException("FilesystemBase.FileExists()"); }
|
||||
|
||||
public virtual FileBase OpenFile(string filename)
|
||||
{ throw new NotImplementedException("FilesystemBase.OpenFile()"); }
|
||||
}
|
||||
|
||||
public class FileBase
|
||||
{
|
||||
public Stream Stream
|
||||
{
|
||||
get { return m_Stream; }
|
||||
set
|
||||
{
|
||||
m_Stream = value;
|
||||
InitRW();
|
||||
}
|
||||
}
|
||||
|
||||
public bool BigEndian
|
||||
{
|
||||
get { return m_BigEndian; }
|
||||
set
|
||||
{
|
||||
m_BigEndian = value;
|
||||
InitRW();
|
||||
}
|
||||
}
|
||||
|
||||
public Encoding Encoding
|
||||
{
|
||||
get { return m_Encoding; }
|
||||
set
|
||||
{
|
||||
m_Encoding = value;
|
||||
InitRW();
|
||||
}
|
||||
}
|
||||
|
||||
public BinaryReader Reader;
|
||||
public BinaryWriter Writer;
|
||||
|
||||
private Stream m_Stream;
|
||||
private bool m_BigEndian;
|
||||
private Encoding m_Encoding = Encoding.ASCII;
|
||||
|
||||
private void InitRW()
|
||||
{
|
||||
Reader = m_BigEndian ? new BinaryReaderBE(m_Stream, m_Encoding) : new BinaryReader(m_Stream, m_Encoding);
|
||||
Writer = m_BigEndian ? new BinaryWriterBE(m_Stream, m_Encoding) : new BinaryWriter(m_Stream, m_Encoding);
|
||||
}
|
||||
|
||||
|
||||
public string ReadString()
|
||||
{
|
||||
string ret = "";
|
||||
char c;
|
||||
while ((c = Reader.ReadChar()) != '\0')
|
||||
ret += c;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public int WriteString(string str)
|
||||
{
|
||||
int oldpos = (int)Stream.Position;
|
||||
|
||||
foreach (char c in str)
|
||||
Writer.Write(c);
|
||||
Writer.Write('\0');
|
||||
|
||||
return (int)(Stream.Position - oldpos);
|
||||
}
|
||||
|
||||
|
||||
public virtual void Flush()
|
||||
{
|
||||
m_Stream.Flush();
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
m_Stream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
6
BMGEditor/MainForm.Designer.cs
generated
6
BMGEditor/MainForm.Designer.cs
generated
@@ -1,4 +1,8 @@
|
||||
namespace BMGEditor
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
|
||||
270
BMGEditor/RarcFilesystem.cs
Normal file
270
BMGEditor/RarcFilesystem.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
public class RarcFilesystem : FilesystemBase
|
||||
{
|
||||
public RarcFilesystem(FileBase file)
|
||||
{
|
||||
m_File = file;
|
||||
m_File.Stream = new Yaz0Stream(m_File.Stream);
|
||||
m_File.BigEndian = true;
|
||||
|
||||
m_File.Stream.Position = 0;
|
||||
uint tag = m_File.Reader.ReadUInt32();
|
||||
if (tag != 0x52415243) throw new Exception("File isn't a RARC (tag 0x" + tag.ToString("X8") + ", expected 0x52415243)");
|
||||
|
||||
m_File.Stream.Position = 0xC;
|
||||
m_FileDataOffset = m_File.Reader.ReadUInt32() + 0x20;
|
||||
m_File.Stream.Position = 0x20;
|
||||
m_NumDirNodes = m_File.Reader.ReadUInt32();
|
||||
m_DirNodesOffset = m_File.Reader.ReadUInt32() + 0x20;
|
||||
m_File.Stream.Position += 0x4;
|
||||
m_FileEntriesOffset = m_File.Reader.ReadUInt32() + 0x20;
|
||||
m_File.Stream.Position += 0x4;
|
||||
m_StringTableOffset = m_File.Reader.ReadUInt32() + 0x20;
|
||||
|
||||
m_DirEntries = new Dictionary<uint, DirEntry>();
|
||||
m_FileEntries = new Dictionary<uint, FileEntry>();
|
||||
|
||||
DirEntry root = new DirEntry();
|
||||
root.ID = 0;
|
||||
root.ParentDir = 0xFFFFFFFF;
|
||||
|
||||
m_File.Stream.Position = m_DirNodesOffset + 0x6;
|
||||
uint rnoffset = m_File.Reader.ReadUInt16();
|
||||
m_File.Stream.Position = m_StringTableOffset + rnoffset;
|
||||
root.Name = m_File.ReadString();
|
||||
root.FullName = "/" + root.Name;
|
||||
|
||||
m_DirEntries.Add(0, root);
|
||||
|
||||
for (uint i = 0; i < m_NumDirNodes; i++)
|
||||
{
|
||||
DirEntry parentdir = m_DirEntries[i];
|
||||
|
||||
m_File.Stream.Position = m_DirNodesOffset + (i * 0x10) + 10;
|
||||
|
||||
ushort numentries = m_File.Reader.ReadUInt16();
|
||||
uint firstentry = m_File.Reader.ReadUInt32();
|
||||
|
||||
for (uint j = 0; j < numentries; j++)
|
||||
{
|
||||
uint entryoffset = m_FileEntriesOffset + ((j + firstentry) * 0x14);
|
||||
m_File.Stream.Position = entryoffset;
|
||||
|
||||
uint fileid = m_File.Reader.ReadUInt16();
|
||||
m_File.Stream.Position += 4;
|
||||
uint nameoffset = m_File.Reader.ReadUInt16();
|
||||
uint dataoffset = m_File.Reader.ReadUInt32();
|
||||
uint datasize = m_File.Reader.ReadUInt32();
|
||||
|
||||
m_File.Stream.Position = m_StringTableOffset + nameoffset;
|
||||
string name = m_File.ReadString();
|
||||
if (name == "." || name == "..") continue;
|
||||
|
||||
string fullname = parentdir.FullName + "/" + name;
|
||||
|
||||
if (fileid == 0xFFFF)
|
||||
{
|
||||
DirEntry d = new DirEntry();
|
||||
d.EntryOffset = entryoffset;
|
||||
d.ID = dataoffset;
|
||||
d.ParentDir = i;
|
||||
d.NameOffset = nameoffset;
|
||||
d.Name = name;
|
||||
d.FullName = fullname;
|
||||
|
||||
m_DirEntries.Add(dataoffset, d);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileEntry f = new FileEntry();
|
||||
f.EntryOffset = entryoffset;
|
||||
f.ID = fileid;
|
||||
f.ParentDir = i;
|
||||
f.NameOffset = nameoffset;
|
||||
f.DataOffset = dataoffset;
|
||||
f.DataSize = datasize;
|
||||
f.Name = name;
|
||||
f.FullName = fullname;
|
||||
|
||||
m_FileEntries.Add(fileid, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
m_File.Close();
|
||||
}
|
||||
|
||||
|
||||
public override bool DirectoryExists(string directory)
|
||||
{
|
||||
DirEntry dir = m_DirEntries.Values.FirstOrDefault(de => de.FullName.ToLower() == directory.ToLower());
|
||||
return dir != null;
|
||||
}
|
||||
|
||||
public override string[] GetDirectories(string directory)
|
||||
{
|
||||
DirEntry dir = m_DirEntries.Values.FirstOrDefault(de => de.FullName.ToLower() == directory.ToLower());
|
||||
if (dir == null) return null;
|
||||
IEnumerable<DirEntry> subdirs = m_DirEntries.Values.Where(de => de.ParentDir == dir.ID);
|
||||
|
||||
List<string> ret = new List<string>();
|
||||
foreach (DirEntry de in subdirs) ret.Add(de.Name);
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public override bool FileExists(string filename)
|
||||
{
|
||||
FileEntry file = m_FileEntries.Values.FirstOrDefault(fe => fe.FullName.ToLower() == filename.ToLower());
|
||||
return file != null;
|
||||
}
|
||||
|
||||
public override string[] GetFiles(string directory)
|
||||
{
|
||||
DirEntry dir = m_DirEntries.Values.FirstOrDefault(de => de.FullName.ToLower() == directory.ToLower());
|
||||
if (dir == null) return null;
|
||||
IEnumerable<FileEntry> files = m_FileEntries.Values.Where(fe => fe.ParentDir == dir.ID);
|
||||
|
||||
List<string> ret = new List<string>();
|
||||
foreach (FileEntry fe in files) ret.Add(fe.Name);
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
public override FileBase OpenFile(string filename)
|
||||
{
|
||||
FileEntry file = m_FileEntries.Values.FirstOrDefault(fe => fe.FullName.ToLower() == filename.ToLower());
|
||||
if (file == null) return null;
|
||||
|
||||
return new RarcFile(this, file.ID);
|
||||
}
|
||||
|
||||
|
||||
// support functions for RarcFile
|
||||
public byte[] GetFileContents(RarcFile file)
|
||||
{
|
||||
FileEntry fe = m_FileEntries[file.ID];
|
||||
|
||||
m_File.Stream.Position = m_FileDataOffset + fe.DataOffset;
|
||||
return m_File.Reader.ReadBytes((int)fe.DataSize);
|
||||
}
|
||||
|
||||
public void ReinsertFile(RarcFile file)
|
||||
{
|
||||
FileEntry fe = m_FileEntries[file.ID];
|
||||
|
||||
uint fileoffset = m_FileDataOffset + fe.DataOffset;
|
||||
int oldlength = (int)fe.DataSize;
|
||||
int newlength = (int)file.Stream.Length;
|
||||
int delta = newlength - oldlength;
|
||||
|
||||
if (newlength != oldlength)
|
||||
{
|
||||
m_File.Stream.Position = fileoffset + oldlength;
|
||||
byte[] tomove = m_File.Reader.ReadBytes((int)(m_File.Stream.Length - m_File.Stream.Position));
|
||||
|
||||
m_File.Stream.Position = fileoffset + newlength;
|
||||
m_File.Stream.SetLength(m_File.Stream.Length + delta);
|
||||
m_File.Writer.Write(tomove);
|
||||
|
||||
fe.DataSize = (uint)newlength;
|
||||
m_File.Stream.Position = fe.EntryOffset + 0xC;
|
||||
m_File.Writer.Write(fe.DataSize);
|
||||
|
||||
foreach (FileEntry tofix in m_FileEntries.Values)
|
||||
{
|
||||
if (tofix.ID == fe.ID) continue;
|
||||
if (tofix.DataOffset < (fe.DataOffset + oldlength)) continue;
|
||||
|
||||
tofix.DataOffset = (uint)(tofix.DataOffset + delta);
|
||||
m_File.Stream.Position = tofix.EntryOffset + 0x8;
|
||||
m_File.Writer.Write(tofix.DataOffset);
|
||||
}
|
||||
}
|
||||
|
||||
m_File.Stream.Position = fileoffset;
|
||||
file.Stream.Position = 0;
|
||||
byte[] data = file.Reader.ReadBytes(newlength);
|
||||
m_File.Writer.Write(data);
|
||||
|
||||
m_File.Flush();
|
||||
}
|
||||
|
||||
|
||||
private class FileEntry
|
||||
{
|
||||
public uint EntryOffset;
|
||||
|
||||
public uint ID;
|
||||
public uint NameOffset;
|
||||
public uint DataOffset;
|
||||
public uint DataSize;
|
||||
|
||||
public uint ParentDir;
|
||||
|
||||
public string Name;
|
||||
public string FullName;
|
||||
}
|
||||
|
||||
private class DirEntry
|
||||
{
|
||||
public uint EntryOffset;
|
||||
|
||||
public uint ID;
|
||||
public uint NameOffset;
|
||||
|
||||
public uint ParentDir;
|
||||
|
||||
public string Name;
|
||||
public string FullName;
|
||||
}
|
||||
|
||||
|
||||
private FileBase m_File;
|
||||
|
||||
private uint m_FileDataOffset;
|
||||
private uint m_NumDirNodes;
|
||||
private uint m_DirNodesOffset;
|
||||
private uint m_FileEntriesOffset;
|
||||
private uint m_StringTableOffset;
|
||||
|
||||
private Dictionary<uint, FileEntry> m_FileEntries;
|
||||
private Dictionary<uint, DirEntry> m_DirEntries;
|
||||
}
|
||||
|
||||
|
||||
public class RarcFile : FileBase
|
||||
{
|
||||
public RarcFile(RarcFilesystem fs, uint id)
|
||||
{
|
||||
m_FS = fs;
|
||||
m_ID = id;
|
||||
|
||||
byte[] buffer = m_FS.GetFileContents(this);
|
||||
Stream = new MemoryStream(buffer.Length);
|
||||
Writer.Write(buffer);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
Stream.Flush();
|
||||
m_FS.ReinsertFile(this);
|
||||
}
|
||||
|
||||
|
||||
private RarcFilesystem m_FS;
|
||||
private uint m_ID;
|
||||
|
||||
public uint ID { get { return m_ID; } }
|
||||
}
|
||||
}
|
||||
54
BMGEditor/Yaz0Stream.cs
Normal file
54
BMGEditor/Yaz0Stream.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace BMGEditor
|
||||
{
|
||||
public class Yaz0Stream : MemoryStream
|
||||
{
|
||||
public Yaz0Stream(Stream backend)
|
||||
: base(1)
|
||||
{
|
||||
if (backend is Yaz0Stream) throw new Exception("sorry but no");
|
||||
|
||||
m_Backend = backend;
|
||||
|
||||
m_Backend.Position = 0;
|
||||
byte[] buffer = new byte[m_Backend.Length];
|
||||
m_Backend.Read(buffer, 0, (int)m_Backend.Length);
|
||||
|
||||
Yaz0.Decompress(ref buffer);
|
||||
Position = 0;
|
||||
Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
public void Flush(bool recompress)
|
||||
{
|
||||
byte[] buffer = new byte[Length];
|
||||
Position = 0;
|
||||
Read(buffer, 0, (int)Length);
|
||||
if (recompress) Yaz0.Compress(ref buffer);
|
||||
|
||||
m_Backend.Position = 0;
|
||||
m_Backend.SetLength(buffer.Length);
|
||||
m_Backend.Write(buffer, 0, buffer.Length);
|
||||
m_Backend.Flush();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
Flush(false);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
m_Backend.Close();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
|
||||
private Stream m_Backend;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user