Editor/Decompiler/MapDecompiler.cs
namespace BspImport.Decompiler;
using Formats;
using System.Diagnostics;
[MapDecompiler( "Default" )]
public partial class MapDecompiler
{
protected ImportContext Context { get; set; }
public MapDecompiler( ImportContext context )
{
Context = context;
}
public virtual void GetFileInfo()
{
var mapName = Path.GetFileNameWithoutExtension( Context.Name );
Log.Info( $"Decompiling Context '{Context.Name}' (map '{mapName}')." );
var reader = new BinaryReader( new MemoryStream( Context.Data ) );
// read bsp header
var ident = reader.ReadInt32();
var mapversion = reader.ReadInt32();
// detect format based on version, this will be used to determine how to read lumps as we go along
Context.BspVersion = mapversion;
Context.FormatDescriptor = BspFormatRegistry.DetectByVersion( mapversion );
RefineFormatWithMapName( mapversion );
// iterate all 64 possible lump headers
for ( int i = 0; i < 64; i++ )
{
var lumpInfo = ReadLumpInfo( reader, i );
// only attempt to decompile lumps we know about
if ( !Enum.IsDefined( typeof( LumpType ), i ) )
{
continue;
}
if ( lumpInfo.IsEmpty )
{
Log.Info( $"Lump {i} is empty." );
continue;
}
// refine format from entity classnames after lump 0, resolves shared BSP versions
if ( i == 0 )
{
RefineFormatFromEntities( mapversion );
}
var lumpType = (LumpType)i;
Log.Info( $"Got Lump: [{lumpInfo.Index}] {lumpType} size: {lumpInfo.Length}" );
}
}
/// <summary>
/// Begin decompiling the bsp file structure. Reads bsp header and 64 sequential lump headers. Will jump to read section of bsp as lump type along the way if we know about the lump type.
/// </summary>
public virtual void Decompile()
{
var mapName = Path.GetFileNameWithoutExtension( Context.Name );
Log.Info( $"Decompiling Context '{Context.Name}' (map '{mapName}')." );
var reader = new BinaryReader( new MemoryStream( Context.Data ) );
// read bsp header
var ident = reader.ReadInt32();
var mapversion = reader.ReadInt32();
// detect format based on version, this will be used to determine how to read lumps as we go along
Context.BspVersion = mapversion;
Context.FormatDescriptor = BspFormatRegistry.DetectByVersion( mapversion );
RefineFormatWithMapName( mapversion );
// iterate all 64 possible lump headers
for ( int i = 0; i < 64; i++ )
{
var lumpInfo = ReadLumpInfo( reader, i );
// only attempt to decompile lumps we know about
if ( !Enum.IsDefined( typeof( LumpType ), i ) )
{
continue;
}
// prepare lump data section
byte[] lumpData = new byte[lumpInfo.Length];
Array.Copy( Context.Data, lumpInfo.Offset, lumpData, 0, lumpInfo.Length );
if ( !Enum.IsDefined( typeof( LumpType ), i ) )
continue;
var lumpType = (LumpType)i;
BaseLump? lump = null;
try
{
lump = ParseLump( lumpType, lumpData, lumpInfo.Version );
}
catch ( Exception ex )
{
Log.Error( $"Failed decompiling lump: {(LumpType)i} {ex}" );
continue;
}
if ( lump is null )
{
Log.Info( $"Skipping unsupported lump {lumpType}." );
continue;
}
// refine format from entity classnames after lump 0, resolves shared BSP versions
if ( i == 0 )
{
RefineFormatFromEntities( mapversion );
}
// store in context after we're done with all lumps
Context.Lumps[i] = lump;
}
var revision = reader.ReadInt32();
Log.Info( $"Finished Decompiling: [ident: {ident} version: {mapversion} revision: {revision}]" );
}
/// <summary>
/// Read lump header to get info about lump in BSP
/// </summary>
/// <param name="reader"></param>
/// <param name="index"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private LumpInfo ReadLumpInfo( BinaryReader reader, int index )
{
int offset; // offset into bsp
int length; // length in bytes
int version; // lump versions can affect data structure
switch ( Context.FormatDescriptor.LumpHeaderLayout )
{
case LumpHeaderLayout.Standard:
offset = reader.ReadInt32();
length = reader.ReadInt32();
version = reader.ReadInt32();
break;
case LumpHeaderLayout.VersionFirst:
version = reader.ReadInt32();
offset = reader.ReadInt32();
length = reader.ReadInt32();
break;
default:
throw new InvalidOperationException( $"Unsupported lump header layout: " + $"{Context.FormatDescriptor.LumpHeaderLayout}" );
}
reader.Skip( 4 ); // fourCC - unused
return new LumpInfo( index, offset, length, version );
}
}
public readonly record struct LumpInfo( int Index, int Offset, int Length, int Version )
{
public bool IsEmpty => Offset == 0 || Length == 0;
}
public enum LumpHeaderLayout
{
Standard, // fileofs, filelen, version, fourCC
VersionFirst // version, fileofs, filelen, fourCC
}
public enum BrushSideLayout
{
Standard,
ThinFlag
}
public enum StaticPropLayout
{
V4,
V5,
V6,
V7,
V7Xbox,
V8,
V9,
V10,
V11
}