mirror of
https://github.com/kwsch/pkNX.git
synced 2026-03-23 02:34:22 -05:00
File scoped namespaces for all lib projects netstandard2.0 => net6; now uniform. bye netframework!
389 lines
13 KiB
C#
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)
|
|
}
|
|
}
|