pkNX/pkNX.Structures/Scripts/Amx.cs
Kurt 2578ba073c Refactoring
File scoped namespaces for all lib projects
netstandard2.0 => net6; now uniform. bye netframework!
2022-10-01 17:08:17 -07:00

389 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace pkNX.Structures;
/// <summary>
/// Pawn Script (.amx) Script File
/// </summary>
/// <remarks>https://github.com/compuphase/pawn</remarks>
public class Amx
{
private const int MAX_NAME_LENGTH = 31;
public readonly byte[] Data;
public readonly AmxHeader Header;
public readonly int CellSize;
public Amx(byte[] data)
{
Data = data;
Header = data.ToClass<AmxHeader>();
CellSize = Header.CellSize;
if (Header.Flags.HasFlagFast(AmxFlags.DEBUG))
return;
if (Header.Flags.HasFlagFast(AmxFlags.OVERLAY))
throw new ArgumentException("Multi-environment script!?");
Unpack();
Trace.Assert(Header != null);
Trace.Assert(Header.Magic != 0);
Trace.Assert(Header.Natives <= Header.Libraries);
}
public byte[] Write() => Data;
public bool IsDebug => Header.Flags.HasFlagFast(AmxFlags.DEBUG);
// Generated Attributes
public int CodeLength => Header.Data - Header.COD;
public int CompressedLength => Header.Size - Header.COD;
public byte[] CompressedBytes => Data.Skip(Header.COD).ToArray();
public int DecompressedLength => Header.Heap - Header.COD;
public uint[] DecompressedInstructions => PawnUtil.QuickDecompress(CompressedBytes, DecompressedLength / sizeof(uint));
public uint[] ScriptCommands => DecompressedInstructions.Take(CodeLength / sizeof(uint)).ToArray(); // Code
public uint[] DataPayload => DecompressedInstructions.Skip(CodeLength / sizeof(uint)).ToArray(); // Data
public string[] ParseScript => PawnUtil.ParseScript(ScriptCommands);
public string[] DataChunk => PawnUtil.ParseMovement(DataPayload);
public string Info => string.Join(Environment.NewLine, SummaryLines);
public IEnumerable<string> SummaryLines
{
get
{
yield return $"Code Start: 0x{Header.COD:X4}";
yield return $"Data Start: 0x{Header.Data:X4}";
yield return $"Total Used Size: 0x{Header.Heap:X4}";
yield return $"Reserved Size: 0x{Header.StackTop:X4}";
yield return $"Compressed Len: 0x{CompressedLength:X4}";
yield return $"Decompressed Len: 0x{DecompressedLength:X4}";
yield return $"Entry Point: 0x{Header.CurrentInstructionPointer:X4}";
yield return $"Compression Ratio: {(DecompressedLength - CompressedLength) / (decimal)DecompressedLength:p1}";
}
}
public Function LookupFunction(uint pc) => Array.Find(Functions, f => f.Within(pc));
public TableRecord LookupPublic(string name) => Array.Find(Publics, t => t.Name == name);
public TableRecord LookupPublic(uint addr) => Array.Find(Publics, t => t.Address == addr);
public Function[] Functions { get; protected set; }
public TableRecord[] Publics { get; protected set; }
public TableRecord[] Natives { get; protected set; }
public TableRecord[] Libraries { get; protected set; }
public TableRecord[] PublicVars { get; protected set; }
public Variable[] Globals { get; protected set; }
public static string ReadName(byte[] data, int offset)
{
var end = Array.FindIndex(data, offset, z => z == 0);
if (end < 0)
end = offset + MAX_NAME_LENGTH;
if (end >= data.Length)
return null;
return System.Text.Encoding.UTF8.GetString(data, offset, end - offset);
}
public void Unpack()
{
if (Header.Publics > 0)
ReadPublics();
if (Header.Natives > 0)
ReadNatives();
if (Header.Libraries > 0)
ReadLibraries();
if (Header.PublicVars > 0)
ReadPublicVars();
if (IsDebug)
{
// todo
}
}
public class Cell
{
}
protected void ReadPublics()
{
var count = (Header.Natives - Header.Publics) / Header.DefinitionSize;
Publics = ReadTable(Header.Publics, count);
}
protected void ReadNatives()
{
var count = (Header.Libraries - Header.Natives) / Header.DefinitionSize;
Natives = ReadTable(Header.Natives, count);
}
protected void ReadLibraries()
{
var count = (Header.PublicVars - Header.Libraries) / Header.DefinitionSize;
Libraries = ReadTable(Header.Libraries, count);
}
protected void ReadPublicVars()
{
var count = (Header.Tags - Header.PublicVars) / Header.DefinitionSize;
PublicVars = ReadTable(Header.PublicVars, count);
}
protected TableRecord[] ReadTable(int offset, int count)
{
using var stream = new MemoryStream(Data, offset, count * Header.DefinitionSize);
using var reader = new BinaryReader(stream);
var dest = new TableRecord[count];
for (int i = 0; i < dest.Length; i++)
{
var address = reader.ReadUInt32();
var nameoffset = reader.ReadUInt32();
var name = default(string);
if (nameoffset < Data.Length)
name = ReadName(Data, (int)nameoffset);
name ??= "Unknown";
dest[i] = new TableRecord(name, address);
}
return dest;
}
private int sysreq_flg;
public void ParseOp(AmxOpCode op, ref int cip, ref Cell tgt)
{
static void GETPARAM_P(Cell v, AmxOpCode o) { } // (v = ((Cell) (o) >> (int) (CellSize * 4)));}
switch (op)
{
case AmxOpCode.CONST:
case AmxOpCode.CONST_S:
cip += CellSize * 2;
break;
/* Packed Instructions */
case AmxOpCode.CONST_P_PRI:
case AmxOpCode.CONST_P_ALT:
case AmxOpCode.ADDR_P_PRI:
case AmxOpCode.ADDR_P_ALT:
case AmxOpCode.STRB_P_I:
case AmxOpCode.LIDX_P_B:
case AmxOpCode.IDXADDR_P_B:
case AmxOpCode.ALIGN_P_PRI:
case AmxOpCode.PUSH_P_C:
case AmxOpCode.PUSH_P:
case AmxOpCode.PUSH_P_S:
case AmxOpCode.STACK_P:
case AmxOpCode.HEAP_P:
case AmxOpCode.SHL_P_C_PRI:
case AmxOpCode.SHL_P_C_ALT:
case AmxOpCode.ADD_P_C:
case AmxOpCode.SMUL_P_C:
case AmxOpCode.ZERO_P:
case AmxOpCode.ZERO_P_S:
case AmxOpCode.EQ_P_C_PRI:
case AmxOpCode.EQ_P_C_ALT:
case AmxOpCode.MOVS_P:
case AmxOpCode.CMPS_P:
case AmxOpCode.FILL_P:
case AmxOpCode.HALT_P:
case AmxOpCode.BOUNDS_P:
case AmxOpCode.PUSH_P_ADR:
break;
/* Packed Instructions referencing pointers */
case AmxOpCode.LOAD_P_PRI:
case AmxOpCode.LOAD_P_ALT:
case AmxOpCode.INC_P:
case AmxOpCode.DEC_P:
GETPARAM_P(tgt, op);
break;
/* Packed Instructions referencing stack */
case AmxOpCode.LOAD_P_S_PRI:
case AmxOpCode.LOAD_P_S_ALT:
case AmxOpCode.LREF_P_S_PRI:
case AmxOpCode.LREF_P_S_ALT:
case AmxOpCode.INC_P_S:
case AmxOpCode.DEC_P_S:
GETPARAM_P(tgt, op); /* verify address */
break;
/* Single-Value Instructions */
case AmxOpCode.LODB_I:
case AmxOpCode.CONST_PRI:
case AmxOpCode.CONST_ALT:
case AmxOpCode.ADDR_PRI:
case AmxOpCode.ADDR_ALT:
case AmxOpCode.STRB_I:
case AmxOpCode.LIDX_B:
case AmxOpCode.IDXADDR_B:
case AmxOpCode.ALIGN_PRI:
case AmxOpCode.LCTRL:
case AmxOpCode.SCTRL:
case AmxOpCode.PICK:
case AmxOpCode.PUSH_C:
case AmxOpCode.PUSH:
case AmxOpCode.PUSH_S:
case AmxOpCode.STACK:
case AmxOpCode.HEAP:
case AmxOpCode.SHL_C_PRI:
case AmxOpCode.SHL_C_ALT:
case AmxOpCode.ADD_C:
case AmxOpCode.SMUL_C:
case AmxOpCode.ZERO:
case AmxOpCode.ZERO_S:
case AmxOpCode.EQ_C_PRI:
case AmxOpCode.EQ_C_ALT:
case AmxOpCode.MOVS:
case AmxOpCode.CMPS:
case AmxOpCode.FILL:
case AmxOpCode.HALT:
case AmxOpCode.BOUNDS:
case AmxOpCode.PUSH_ADR:
cip += CellSize;
break;
case AmxOpCode.LOAD_PRI:
case AmxOpCode.LOAD_ALT:
case AmxOpCode.INC:
case AmxOpCode.DEC:
//VerifyAddress(0, );
cip += CellSize;
break;
case AmxOpCode.LOAD_S_PRI:
case AmxOpCode.LOAD_S_ALT:
case AmxOpCode.LREF_S_PRI:
case AmxOpCode.LREF_S_ALT:
case AmxOpCode.INC_S:
case AmxOpCode.DEC_S:
cip += CellSize;
break;
/* Parameterless Instructions */
case AmxOpCode.LOAD_I:
case AmxOpCode.STOR_I:
case AmxOpCode.LIDX:
case AmxOpCode.IDXADDR:
case AmxOpCode.XCHG:
case AmxOpCode.PUSH_PRI:
case AmxOpCode.PUSH_ALT:
case AmxOpCode.PPRI:
case AmxOpCode.PALT:
case AmxOpCode.PROC:
case AmxOpCode.RET:
case AmxOpCode.RETN:
case AmxOpCode.SHL:
case AmxOpCode.SHR:
case AmxOpCode.SSHR:
case AmxOpCode.SMUL:
case AmxOpCode.SDIV:
case AmxOpCode.ADD:
case AmxOpCode.SUB:
case AmxOpCode.AND:
case AmxOpCode.OR:
case AmxOpCode.XOR:
case AmxOpCode.NOT:
case AmxOpCode.NEG:
case AmxOpCode.INVERT:
case AmxOpCode.ZERO_PRI:
case AmxOpCode.ZERO_ALT:
case AmxOpCode.EQ:
case AmxOpCode.NEQ:
case AmxOpCode.SLESS:
case AmxOpCode.SLEQ:
case AmxOpCode.SGRTR:
case AmxOpCode.SGEQ:
case AmxOpCode.INC_PRI:
case AmxOpCode.INC_ALT:
case AmxOpCode.INC_I:
case AmxOpCode.DEC_PRI:
case AmxOpCode.DEC_ALT:
case AmxOpCode.DEC_I:
case AmxOpCode.SWAP_PRI:
case AmxOpCode.SWAP_ALT:
case AmxOpCode.NOP:
case AmxOpCode.BREAK:
break;
/* Jump w/ Relocation */
case AmxOpCode.CALL:
case AmxOpCode.JUMP:
case AmxOpCode.JZER:
case AmxOpCode.JNZ:
case AmxOpCode.JEQ:
case AmxOpCode.JNEQ:
case AmxOpCode.JSLESS:
case AmxOpCode.JSLEQ:
case AmxOpCode.JSGRTR:
case AmxOpCode.JSGEQ:
case AmxOpCode.SWITCH:
/* if this file is an older version (absolute references instead of the
* current use of position-independent code), convert the parameter
* to position-independent code first
*/
cip += CellSize;
break;
/* overlay opcodes (overlays must be enabled) */
case AmxOpCode.ISWITCH:
Debug.Assert(Header.FileVersion >= 10);
/* drop through */
goto case AmxOpCode.ICALL;
case AmxOpCode.ICALL:
cip += CellSize;
/* drop through */
goto case AmxOpCode.IRETN;
case AmxOpCode.IRETN:
Debug.Assert(Header.Overlays != 0 && Header.Overlays != Header.NameTable);
//return AmxError.OVERLAY; /* no overlay callback */
break;
case AmxOpCode.ICASETBL:
{
// Cell num;
//DBGPARAM(num); /* number of records follows the opcode */
//cip += (2 * num + 1) * CellSize;
//if (Header.Overlays == 0)
// return AmxError.OVERLAY; /* no overlay callback */
break;
} /* case */
case AmxOpCode.SYSREQ_C:
cip += CellSize;
sysreq_flg |= 0x01; /* mark SYSREQ found */
break;
case AmxOpCode.SYSREQ_N:
cip += CellSize * 2;
sysreq_flg |= 0x02; /* mark SYSREQ.N found */
break;
case AmxOpCode.CASETBL:
{
DBGPARAM(out _);
//cip += (2 * num + 1) * CellSize;
break;
}
default:
Header.Flags &= ~AmxFlags.VERIFY;
//return AmxError.INVINSTR;
break;
}
static void DBGPARAM(out Cell v) => v = null; // v = (Cell)(amx->code + (int)cip), cip += CellSize)
}
}