using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace pkNX.Structures; /// /// Pawn Script (.amx) Script File /// /// https://github.com/compuphase/pawn 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(); 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 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) } }