#!/usr/bin/env lua
--[[-------------------------------------------------------------------

  ChunkSpy.lua
  A Lua 5.3 binary chunk disassembler
  ChunkSpy was inspired by Nick Trout's vmmerge5.lua

  Copyright (c) 2004-2006 Kein-Hong Man <khman@users.sf.net>
  The COPYRIGHT file describes the conditions under which this
  software may be distributed (basically a Lua 5-style license.)

  https://github.com/viruscamp/luadec/tree/master/ChunkSpy
  http://luaforge.net/projects/chunkspy/
  http://www.geocities.com/keinhong/chunkspy.html
  See the ChangeLog for more information.

--]]-------------------------------------------------------------------

--[[-------------------------------------------------------------------
-- Notes:
-- * See 5.0.2/ChunkSpy.lua for script notes and outline
--]]-------------------------------------------------------------------

--[[-------------------------------------------------------------------
-- description and help texts
--]]-------------------------------------------------------------------

title = [[
ChunkSpy: A Lua 5.3 binary chunk disassembler
Version 0.9.9 (20150329)
Copyright (c) 2004-2006 Kein-Hong Man , 2014-2015 VirusCamp
The COPYRIGHT file describes the conditions under which this
software may be distributed (basically a Lua 5-style license.)
]]
-----------------------------------------------------------------------
USAGE = [[
usage: %s [options] [filenames]

options:
  -h, --help        prints usage information
  --stats           prints some statistical information
  --brief           generate assembly-style brief listing
  --auto            auto detects binary chunk profile
  -o <file>         specify file name to write output listing
  --source <file>   generate listing from a Lua source file
  --rewrite "plat"  generate binary chunk using given profile;
                    use "local" for local binary chunk format
  --run             convert to local format, load and execute
  --test            perform internal tests only
  --sample          generate sample listing only
  --interact        *interactive mode*, output brief listings
  --                stop handling arguments

example:
  >luac myscript.lua
  >%s luac.out myscript.lua -o myscript.lst

* Other configuration settings can be customized in the script
* If source filename is specified, the source listing is merged in
]]
-----------------------------------------------------------------------
interactive_help = [[
Type 'exit' or 'quit' to end the interactive session. 'help' displays
this message. ChunkSpy will attempt to turn anything else into a
binary chunk and process it into an assembly-style listing.
A '\' can be used as a line continuation symbol; this allows multiple
lines to be strung together.
]]

--[[-------------------------------------------------------------------
-- Configuration table
-- * Contains fixed constants, display constants and options, and
--   platform-dependent configuration constants
-----------------------------------------------------------------------
-- Configuration settings of binary chunks to be processed. Such tables
-- may be used to sort of "auto-detect" binary chunks from different
-- platforms. More or less equivalent to the global header.
-- * There is currently only one supported host, "x86 standard"
-- * MAX_STACK is no longer required for decoding RK register indices,
--   instead, the MSB bit in the field is used as a flag (Lua 5.1)
-- * number_type is also used to lookup conversion function, etc.
--]]-------------------------------------------------------------------

CONFIGURATION = {
  ["x86 standard"] = {
    description = "x86 standard (32-bit, little endian, long long, double)",
    endianness = 1,             -- 1 = little endian
    size_int = 4,               -- (data type sizes in bytes)
    size_size_t = 4,
    size_Instruction = 4,
    size_lua_Integer = 8,
    integer_type = "long long",
    size_lua_Number = 8,        -- this & integral identifies the
    integral = 0,               -- type of lua_Number
    number_type = "double",     -- used for lookups
  },
  ["x64 standard"] = {
    description = "x64 standard (64-bit, little endian, long long, double)",
    endianness = 1,             -- 1 = little endian
    size_int = 4,               -- (data type sizes in bytes)
    size_size_t = 8,
    size_Instruction = 4,
    size_lua_Integer = 8,
    integer_type = "long long",
    size_lua_Number = 8,        -- this & integral identifies the
    integral = 0,               -- type of lua_Number
    number_type = "double",     -- used for lookups
  },
  ["big endian int"] = {
    description = "(32-bit, big endian, int, int)",
    endianness = 0,
    size_int = 4,
    size_size_t = 4,
    size_Instruction = 4,
    size_lua_Integer = 4,
    integer_type = "int",
    size_lua_Number = 4,
    integral = 1,
    number_type = "int",
  },
  -- you can add more platforms here
  ["x86 single"] = {
    description = "x86 single (32-bit, little endian, int, single)",
    endianness = 1,
    size_int = 4,
    size_size_t = 4,
    size_Instruction = 4,
    size_lua_Integer = 4,
    integer_type = "int",
    size_lua_Number = 4,
    integral = 0,
    number_type = "single",
  },
}

-----------------------------------------------------------------------
-- set the default platform (can override with --auto auto-detection)
-- * both in & out paths use config.* parms, a bit clunky for now
-----------------------------------------------------------------------
config = {}

function CheckLuaVersion(fmt)
  if _VERSION ~= "Lua 5.3" then
    if type(fmt) == "string" then
      msg = string.format(fmt, "Lua 5.3")
    else
      msg = "needs Lua 5.3"
    end
    error(msg)
  end
end

function SetProfile(profile)
  if profile == "local" then
    -- arrives here only for --rewrite and --run option
    local flag1, flag2 = config.DISPLAY_FLAG, config.AUTO_DETECT
    config.DISPLAY_FLAG, config.AUTO_DETECT = false, true
    local LUA_SAMPLE = string.dump(function() end)
    -- config.* profile parms set in ChunkSpy() call...
    config.SIGNATURE = "\27Lua"
    config.LUAC_DATA = "\25\147\r\n\26\n"
    local ok, _ = pcall(ChunkSpy, "", LUA_SAMPLE)
    if not ok then error("error compiling sample to test local profile") end
    config.DISPLAY_FLAG, config.AUTO_DETECT = flag1, flag2
    -- resume normal operation
  else
    local c = CONFIGURATION[profile]
    if not c then return false end
    if not c.SIGNATURE then c.SIGNATURE = "\27Lua" end
    if not c.LUAC_DATA then c.LUAC_DATA = "\25\147\r\n\26\n" end
    for i, v in pairs(c) do config[i] = v end
  end
  return true
end
SetProfile("x64 standard") -- default profile

-----------------------------------------------------------------------
-- chunk constants
-- * changed in 5.1: VERSION, FPF, SIZE_* are now fixed; LUA_TBOOLEAN
--   added for constant table; TEST_NUMBER removed; FORMAT added
-----------------------------------------------------------------------
config.SIGNATURE    = "\27Lua"
config.LUAC_DATA    = "\25\147\r\n\26\n" -- new in 5.2 tail of header, LUAC_DATA="\x19\x93\r\n\x1a\n"
-- TEST_NUMBER no longer needed, using size_lua_Number + integral
config.LUA_TNIL     = 0
config.LUA_TBOOLEAN = 1
config.LUA_TNUMBER  = 3
config.LUA_TNUMFLT  = config.LUA_TNUMBER | (0 << 4)
config.LUA_TNUMINT  = config.LUA_TNUMBER | (1 << 4)
config.LUA_TSTRING  = 4
config.LUA_TSHRSTR  = config.LUA_TSTRING | (0 << 4)
config.LUA_TLNGSTR  = config.LUA_TSTRING | (1 << 4)
config.VERSION      = 83 -- 0x53
config.FORMAT       = 0  -- LUAC_FORMAT (new in 5.1)
config.FPF          = 50 -- LFIELDS_PER_FLUSH
config.SIZE_OP      = 6  -- instruction field bits
config.SIZE_A       = 8
config.SIZE_B       = 9
config.SIZE_C       = 9
-- MAX_STACK no longer needed for instruction decoding, removed
-- LUA_FIRSTINDEX currently not supported; used in SETLIST
config.LUA_FIRSTINDEX = 1

config.typestr = {
[config.LUA_TNIL] = "LUA_TNIL",
[config.LUA_TBOOLEAN] = "LUA_TBOOLEAN",
[config.LUA_TNUMFLT] = "LUA_TNUMFLT",
[config.LUA_TNUMINT] = "LUA_TNUMINT",
[config.LUA_TSHRSTR] = "LUA_TSHRSTR",
[config.LUA_TLNGSTR] = "LUA_TLNGSTR",
}

-----------------------------------------------------------------------
-- display options: you can set your defaults here
-----------------------------------------------------------------------
config.DISPLAY_FLAG = true              -- global listing output on/off
config.DISPLAY_BRIEF = nil              -- brief listing style
config.DISPLAY_INDENT = nil             -- indent flag for brief style
config.STATS = nil                      -- set if always display stats
config.DISPLAY_OFFSET_HEX = true        -- use hexadecimal for position
config.DISPLAY_SEP = "  "               -- column separator
config.DISPLAY_COMMENT = "; "           -- comment sign
config.DISPLAY_HEX_DATA = true          -- show hex data column
config.WIDTH_HEX = 8                    -- width of hex data column
config.WIDTH_OFFSET = nil               -- width of position column
config.DISPLAY_LOWERCASE = true         -- lower-case operands
config.WIDTH_OPCODE = nil               -- width of opcode field
config.VERBOSE_TEST = false             -- more verbosity for --test

--[[-------------------------------------------------------------------
-- Other globals
--]]-------------------------------------------------------------------

other_files = {}        -- non-chunks (may be source listings)
arg_other = {}          -- other arguments (for --run option)

--[[-------------------------------------------------------------------
-- Number handling functions
-- * converts a string of bytes to and from a proper number
-- * WARNING single() and double() can only do normal floats
--   and zeros. Denormals, infinities and NaNs are not recognized.
-- * See 5.0.2/ChunkSpy.lua for IEEE floating-point notes
--]]-------------------------------------------------------------------
convert_from = {}       -- tables for number conversion function lookup
convert_to = {}

-----------------------------------------------------------------------
-- support function for convert_to functions
-----------------------------------------------------------------------
function grab_byte(v)
  return math.floor(v / 256), string.char(math.floor(v) % 256)
end

-----------------------------------------------------------------------
-- No more TEST_NUMBER in Lua 5.1, uses size_lua_Number + integral
-----------------------------------------------------------------------
LUANUMBER_ID = {
  ["80"] = "double",         -- IEEE754 double
  ["40"] = "single",         -- IEEE754 single
  ["41"] = "int",            -- int
  ["81"] = "long long",      -- long long
}

-----------------------------------------------------------------------
-- Converts an 8-byte little-endian string to a IEEE754 double number
-- * NOTE: see warning about accuracy in the header comments!
-----------------------------------------------------------------------
local function convert_from_double(x)
  local sign = 1
  local mantissa = string.byte(x, 7) % 16
  for i = 6, 1, -1 do mantissa = mantissa * 256 + string.byte(x, i) end
  if string.byte(x, 8) > 127 then sign = -1 end
  local exponent = (string.byte(x, 8) % 128) * 16 +
                   math.floor(string.byte(x, 7) / 16)
  if exponent == 0 then return 0.0 end
  mantissa = (math.ldexp(mantissa, -52) + 1.0) * sign
  return math.ldexp(mantissa, exponent - 1023)
end
convert_from["double"] = convert_from_double

-----------------------------------------------------------------------
-- Converts a 4-byte little-endian string to a IEEE754 single number
-- * TODO UNTESTED!!! *
-----------------------------------------------------------------------
local function convert_from_single(x)
  local sign = 1
  local mantissa = string.byte(x, 3) % 128
  for i = 2, 1, -1 do mantissa = mantissa * 256 + string.byte(x, i) end
  if string.byte(x, 4) > 127 then sign = -1 end
  local exponent = (string.byte(x, 4) % 128) * 2 +
                   math.floor(string.byte(x, 3) / 128)
  if exponent == 0 then return 0.0 end
  mantissa = (math.ldexp(mantissa, -23) + 1.0) * sign
  return math.ldexp(mantissa, exponent - 127)
end
convert_from["single"] = convert_from_single

-----------------------------------------------------------------------
-- Converts a little-endian integer string to a number
-- * TODO UNTESTED!!! *
-----------------------------------------------------------------------
local function convert_from_int(x, size_int)
  size_int = size_int or 8
  local sum = 0
  local highestbyte = string.byte(x, size_int)
  -- test for negative number
  if highestbyte <= 127 then
    sum = highestbyte
  else
    sum = highestbyte - 256
  end
  for i = size_int-1, 1, -1 do
    sum = sum * 256 + string.byte(x, i)
  end
  return sum
end
convert_from["int"] = function(x) return convert_from_int(x, 4) end

-----------------------------------------------------------------------
-- * WARNING this will fail for large long longs (64-bit numbers)
--   because long longs exceeds the precision of doubles.
-----------------------------------------------------------------------
convert_from["long long"] = convert_from_int

-----------------------------------------------------------------------
-- Converts a IEEE754 double number to an 8-byte little-endian string
-- * NOTE: see warning about accuracy in the header comments!
-----------------------------------------------------------------------
convert_to["double"] = function(x)
  local sign = 0
  if x < 0 then sign = 1; x = -x end
  local mantissa, exponent = math.frexp(x)
  if x == 0 then -- zero
    mantissa, exponent = 0, 0
  else
    mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53)
    exponent = exponent + 1022
  end
  local v, byte = "" -- convert to bytes
  x = mantissa
  for i = 1,6 do
    x, byte = grab_byte(x); v = v..byte -- 47:0
  end
  x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48
  x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56
  return v
end

-----------------------------------------------------------------------
-- Converts a IEEE754 single number to a 4-byte little-endian string
-- * TODO UNTESTED!!! *
-----------------------------------------------------------------------
convert_to["single"] = function(x)
  local sign = 0
  if x < 0 then sign = 1; x = -x end
  local mantissa, exponent = math.frexp(x)
  if x == 0 then -- zero
    mantissa = 0; exponent = 0
  else
    mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 24)
    exponent = exponent + 126
  end
  local v, byte = "" -- convert to bytes
  x, byte = grab_byte(mantissa); v = v..byte -- 7:0
  x, byte = grab_byte(x); v = v..byte -- 15:8
  x, byte = grab_byte(exponent * 128 + x); v = v..byte -- 23:16
  x, byte = grab_byte(sign * 128 + x); v = v..byte -- 31:24
  return v
end

-----------------------------------------------------------------------
-- Converts a number to a little-endian integer string
-- * TODO UNTESTED!!! *
-----------------------------------------------------------------------

convert_to["int"] = function(x, size_int)
  size_int = size_int or config.size_lua_Integer or 4
  local v = ""
  x = math.floor(x)
  if x >= 0 then
    for i = 1, size_int do
      v = v..string.char(x % 256); x = math.floor(x / 256)
    end
  else-- x < 0
    x = -x
    local carry = 1
    for i = 1, size_int do
      local c = 255 - (x % 256) + carry
      if c == 256 then c = 0; carry = 1 else carry = 0 end
      v = v..string.char(c); x = math.floor(x / 256)
    end
  end
  -- optional overflow test; not enabled at the moment
  -- if x > 0 then error("number conversion overflow") end
  return v
end

-----------------------------------------------------------------------
-- * WARNING this will fail for large long longs (64-bit numbers)
--   because long longs exceeds the precision of doubles.
-----------------------------------------------------------------------
convert_to["long long"] = convert_to["int"]

--[[-------------------------------------------------------------------
-- Display support functions
-- * considerable work is done to maintain nice alignments
-- * some widths are initialized at chunk start
-- * this is meant to make output customization easy
--]]-------------------------------------------------------------------

-----------------------------------------------------------------------
-- width of number, left justify, zero padding
-----------------------------------------------------------------------
function WidthOf(n) return string.len(tostring(n)) end
function LeftJustify(s, width) return s..string.rep(" ", width - string.len(s)) end
function ZeroPad(s, width) return string.rep("0", width - string.len(s))..s end

-----------------------------------------------------------------------
-- initialize display formatting settings
-- * chunk_size parameter used to set width of position column
-----------------------------------------------------------------------
function DisplayInit(chunk_size)
  ---------------------------------------------------------------
  -- set up printing widths
  ---------------------------------------------------------------
  if not config.WIDTH_OFFSET then config.WIDTH_OFFSET = 0 end
  if config.DISPLAY_OFFSET_HEX then
    local w = string.len(string.format("%X", chunk_size))
    if w > config.WIDTH_OFFSET then config.WIDTH_OFFSET = w end
    if (config.WIDTH_OFFSET % 2) == 1 then
      config.WIDTH_OFFSET = config.WIDTH_OFFSET + 1
    end
  else
    config.WIDTH_OFFSET = string.len(tonumber(chunk_size))
  end
  ---------------------------------------------------------------
  -- sane defaults
  ---------------------------------------------------------------
  if config.WIDTH_OFFSET < 4 then config.WIDTH_OFFSET = 4 end
  if not config.DISPLAY_SEP then config.DISPLAY_SEP = "  " end
  if config.DISPLAY_HEX_DATA == nil then config.DISPLAY_HEX_DATA = true end
  if not config.WIDTH_HEX then config.WIDTH_HEX = 8 end
  config.BLANKS_HEX_DATA = string.rep(" ", config.WIDTH_HEX * 2 + 1)

  -- default output path
  if not WriteLine then WriteLine = print end
end

-----------------------------------------------------------------------
-- initialize listing output path (an optional redirect)
-- * this is done before calling ChunkSpy() to redirect output
-----------------------------------------------------------------------
function OutputInit()
  if config.OUTPUT_FILE then
    if type(config.OUTPUT_FILE) == "string" then
      local INF = io.open(config.OUTPUT_FILE, "wb")
      if not INF then
        error("cannot open \""..config.OUTPUT_FILE.."\" for writing")
      end
      config.OUTPUT_FILE = INF
      WriteLine = function(s) config.OUTPUT_FILE:write(s, "\n") end
    end
  end
end

-----------------------------------------------------------------------
-- cleanup listing output path
-----------------------------------------------------------------------
function OutputExit()
  if WriteLine and WriteLine ~= print then io.close(config.OUTPUT_FILE) end
end

-----------------------------------------------------------------------
-- escape control bytes in strings
-----------------------------------------------------------------------
function EscapeString(s, quoted)
  local v = ""
  for i = 1, string.len(s) do
    local c = string.byte(s, i)
    -- other escapees with values > 31 are "(34), \(92)
    if c < 32 or c == 34 or c == 92 or c > 126 then
      if c >= 7 and c <= 13 then
        c = string.sub("abtnvfr", c - 6, c - 6)
      elseif c == 34 or c == 92 then
        c = string.char(c)
      end
      v = v.."\\"..c
    else -- 32 <= v <= 126
      v = v..string.char(c)
    end
  end
  if quoted then return string.format("\"%s\"", v) end
  return v
end

-----------------------------------------------------------------------
-- listing legend/header
-----------------------------------------------------------------------
function HeaderLine()
  if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end
  WriteLine(LeftJustify("Pos", config.WIDTH_OFFSET)..config.DISPLAY_SEP
            ..LeftJustify("Hex Data", config.WIDTH_HEX * 2 + 1)..config.DISPLAY_SEP
            .."Description or Code\n"
            ..string.rep("-", 72))
end

-----------------------------------------------------------------------
-- description-only line, no position or hex data
-----------------------------------------------------------------------
function DescLine(desc)
  if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end
  WriteLine(string.rep(" ", config.WIDTH_OFFSET)..config.DISPLAY_SEP
            ..config.BLANKS_HEX_DATA..config.DISPLAY_SEP
            ..desc)
end

-----------------------------------------------------------------------
-- optionally display a pre-formatted statistic
-----------------------------------------------------------------------
function DisplayStat(stat)
  if config.STATS and not config.DISPLAY_BRIEF then DescLine(stat) end
end

-----------------------------------------------------------------------
-- returns position, i uses string index (starts from 1)
-----------------------------------------------------------------------
function FormatPos(i)
  local pos
  if config.DISPLAY_OFFSET_HEX then
    pos = string.format("%X", i - 1)
  else
    pos = tonumber(i - 1)
  end
  return ZeroPad(pos, config.WIDTH_OFFSET)
end

--[[-------------------------------------------------------------------
-- Instruction decoder functions (changed in Lua 5.1)
-- * some fixed decode data is placed in the config table
-- * these function are quite flexible, they can accept non-standard
--   instruction field sizes as long as the arrangement is the same.
-----------------------------------------------------------------------
  Visually, an instruction can be represented as one of:

   31      |     |     |         0  bit position
    +-----+-----+-----+----------+
    |  B  |  C  |  A  |  Opcode  |  iABC format
    +-----+-----+-----+----------+
    -  9  -  9  -  8  -    6     -  field sizes (standard Lua)
    +-----+-----+-----+----------+
    |   [s]Bx   |  A  |  Opcode  |  iABx | iAsBx format
    +-----+-----+-----+----------+
    |        Ax       |  Opcode  |  iAx format new in 5.2
    +-----+-----+-----+----------+

  The signed argument sBx is represented in excess K, with the range
  of -max to +max represented by 0 to 2*max.

  For RK(x) constants, MSB is set and constant number is in the rest
  of the bits.

--]]-------------------------------------------------------------------

local iABC, iABx, iAsBx, iAx = 0, 1, 2, 3
-----------------------------------------------------------------------
-- instruction decoder initialization
-----------------------------------------------------------------------
function DecodeInit()
  ---------------------------------------------------------------
  -- calculate masks
  ---------------------------------------------------------------
  config.SIZE_Bx = config.SIZE_B + config.SIZE_C
  config.SIZE_Ax = config.SIZE_A + config.SIZE_B + config.SIZE_C
  local MASK_OP = 1 << config.SIZE_OP
  local MASK_A  = 1 << config.SIZE_A
  local MASK_B  = 1 << config.SIZE_B
  local MASK_C  = 1 << config.SIZE_C
  local MASK_Bx = 1 << config.SIZE_Bx
  local MASK_Ax = 1 << config.SIZE_Ax
  config.MAXARG_sBx = (MASK_Bx - 1) >> 1
  config.BITRK = 1 << (config.SIZE_B - 1)

  ---------------------------------------------------------------
  -- iABC instruction segment tables
  ---------------------------------------------------------------
  config.iABC = {       -- tables allows field sequence to be extracted
    config.SIZE_OP,     -- using a loop; least significant field first
    config.SIZE_A,      -- additional lookups below, kludgy
    config.SIZE_C,
    config.SIZE_B,
  }
  config.mABC = { MASK_OP, MASK_A, MASK_C, MASK_B, }
  config.nABC = { "OP", "A", "C", "B", }

  ---------------------------------------------------------------
  -- opcode name table
  ---------------------------------------------------------------
  local op = [[
    MOVE LOADK LOADKX LOADBOOL LOADNIL 
    GETUPVAL GETTABUP GETTABLE SETTABUP SETUPVAL 
    SETTABLE NEWTABLE SELF ADD SUB 
    MUL MOD POW DIV IDIV 
    BAND BOR BXOR SHL SHR 
    UNM BNOT NOT LEN CONCAT 
    JMP EQ LT LE TEST 
    TESTSET CALL TAILCALL RETURN FORLOOP 
    FORPREP TFORCALL TFORLOOP SETLIST CLOSURE 
    VARARG EXTRAARG 
  ]]

  iABC=0; iABx=1; iAsBx=2; iAx=3
  config.opmode = {
    [0]=iABC,iABx,iABx,iABC,iABC,
    iABC,iABC,iABC,iABC,iABC,
    iABC,iABC,iABC,iABC,iABC,
    iABC,iABC,iABC,iABC,iABC,
    iABC,iABC,iABC,iABC,iABC,
    iABC,iABC,iABC,iABC,iABC,
    iAsBx,iABC,iABC,iABC,iABC,
    iABC,iABC,iABC,iABC,iAsBx,
    iAsBx,iABC,iAsBx,iABC,iABx,
    iABC,iAx
  }

  ---------------------------------------------------------------
  -- build opcode name table
  ---------------------------------------------------------------
  config.opnames = {}
  config.opcodes = {}
  config.NUM_OPCODES = 0
  if not config.WIDTH_OPCODE then config.WIDTH_OPCODE = 0 end
  for v in string.gmatch(op, "[^%s]+") do
    if config.DISPLAY_LOWERCASE then v = string.lower(v) end
    config.opnames[config.NUM_OPCODES] = v
    config.opcodes[v] = config.NUM_OPCODES
    local vlen = string.len(v)
    -- find maximum opcode length
    if vlen > config.WIDTH_OPCODE then
      config.WIDTH_OPCODE = vlen
    end
    config.NUM_OPCODES = config.NUM_OPCODES + 1
  end

  config.operators={
    [config.opcodes["add"]]="+",
    [config.opcodes["sub"]]="-",
    [config.opcodes["mul"]]="*",
    [config.opcodes["div"]]="/",
    [config.opcodes["mod"]]="%",
    [config.opcodes["pow"]]="^",
    [config.opcodes["unm"]]="-",
    [config.opcodes["not"]]="not ",
    [config.opcodes["len"]]="#",
    [config.opcodes["eq"]]="==",
    [config.opcodes["lt"]]="<",
    [config.opcodes["le"]]="<=",
    [config.opcodes["idiv"]]="//",
    [config.opcodes["band"]]="&",
    [config.opcodes["bor"]]="|",
    [config.opcodes["bxor"]]="~",
    [config.opcodes["shl"]]="<<",
    [config.opcodes["shr"]]=">>",
    [config.opcodes["bnot"]]="~",
  }
  ---------------------------------------------------------------
  -- initialize text widths and formats for display
  ---------------------------------------------------------------
  config.WIDTH_A = WidthOf(MASK_A)
  config.WIDTH_B = WidthOf(MASK_B)
  config.WIDTH_C = WidthOf(MASK_C)
  config.WIDTH_Bx = WidthOf(MASK_Bx) + 1 -- with minus sign
  config.WIDTH_Ax = WidthOf(MASK_Ax)
  config.FORMAT_A = string.format("%%-%dd", config.WIDTH_A)
  config.FORMAT_B = string.format("%%-%dd", config.WIDTH_B)
  config.FORMAT_C = string.format("%%-%dd", config.WIDTH_C)
  config.PAD_Bx = config.WIDTH_A + config.WIDTH_B + config.WIDTH_C + 2
                  - config.WIDTH_Bx
  if config.PAD_Bx > 0 then
    config.PAD_Bx = string.rep(" ", config.PAD_Bx)
  else
    config.PAD_Bx = ""
  end
  config.PAD_Ax = config.WIDTH_A + config.WIDTH_B + config.WIDTH_C + 2
                  - config.WIDTH_Ax
  if config.PAD_Ax > 0 then
    config.PAD_Ax = string.rep(" ", config.PAD_Ax)
  else
    config.PAD_Ax = ""
  end
  config.FORMAT_Bx  = string.format("%%-%dd", config.WIDTH_Bx)
  config.FORMAT_AB  = string.format("%s %s %s", config.FORMAT_A, config.FORMAT_B, string.rep(" ", config.WIDTH_C))
  config.FORMAT_ABC = string.format("%s %s %s", config.FORMAT_A, config.FORMAT_B, config.FORMAT_C)
  config.FORMAT_AC  = string.format("%s %s %s", config.FORMAT_A, string.rep(" ", config.WIDTH_B), config.FORMAT_C)
  config.FORMAT_ABx = string.format("%s %s", config.FORMAT_A, config.FORMAT_Bx)
  config.FORMAT_A1  = string.format("%s %s %s", config.FORMAT_A, string.rep(" ", config.WIDTH_B), string.rep(" ", config.WIDTH_C))
  config.FORMAT_Ax = string.format("%%-%dd", config.WIDTH_Ax)
end

-----------------------------------------------------------------------
-- instruction decoder
-- * decoder loops starting from the least-significant byte, this allow
--   a field to be extracted using % operations
-- * returns a table populated with the appropriate fields
-- * WARNING B,C arrangement is hard-coded here for calculating [s]Bx
-----------------------------------------------------------------------
function DecodeInst(code, iValues)
  local iSeq, iMask = config.iABC, config.mABC
  local cValue, cBits, cPos = 0, 0, 1
  -- decode an instruction
  for i = 1, #iSeq do
    -- if need more bits, suck in a byte at a time
    while cBits < iSeq[i] do
      cValue = string.byte(code, cPos) * (1 << cBits) + cValue
      cPos = cPos + 1; cBits = cBits + 8
    end
    -- extract and set an instruction field
    iValues[config.nABC[i]] = cValue % iMask[i]
    cValue = cValue // iMask[i]
    cBits = cBits - iSeq[i]
  end
  iValues.opname = config.opnames[iValues.OP]   -- get mnemonic
  iValues.opmode = config.opmode[iValues.OP]
  if iValues.opmode == iABx then                 -- set Bx or sBx
    iValues.Bx = iValues.B * iMask[3] + iValues.C
  elseif iValues.opmode == iAsBx then
    iValues.sBx = iValues.B * iMask[3] + iValues.C - config.MAXARG_sBx
  elseif iValues.opmode == iAx then
    iValues.Ax = iValues.B * iMask[3] * iMask[2] + iValues.C * iMask[2] + iValues.A
  end
  return iValues
end

-----------------------------------------------------------------------
-- encodes an instruction into a little-endian byte string
-- * encodes from OP/A/B/C fields, to enable bit field size changes
-----------------------------------------------------------------------
function EncodeInst(inst)
  local v, i = "", 0
  local cValue, cBits, cPos = 0, 0, 1
  -- encode an instruction
  while i < config.size_Instruction do
    -- if need more bits, suck in a field at a time
    while cBits < 8 do
      cValue = inst[config.nABC[cPos]] << cBits + cValue
      cBits = cBits + config.iABC[cPos]; cPos = cPos + 1
    end
    -- extract bytes to instruction string
    while cBits >= 8 do
      v = v..string.char(cValue % 256)
      cValue = math.floor(cValue / 256)
      cBits = cBits - 8; i = i + 1
    end
  end
  return v
end

-----------------------------------------------------------------------
-- describe an instruction
-- * make instructions descriptions more verbose and readable
-----------------------------------------------------------------------
function DescribeInst(inst, pos, func)
  local Operand
  local Comment = ""
  local CommentArg = ""
  local CommentRtn = ""

  ---------------------------------------------------------------
  -- operand formatting helpers
  ---------------------------------------------------------------
  local function OperandAB(i)   return string.format(config.FORMAT_AB, i.A, i.B) end
  local function OperandABC(i)  return string.format(config.FORMAT_ABC, i.A, i.B, i.C) end
  local function OperandAC(i)   return string.format(config.FORMAT_AC, i.A, i.C) end
  local function OperandABx(i)  return string.format(config.FORMAT_ABx, i.A, i.Bx) end
  local function OperandAsBx(i) return string.format(config.FORMAT_ABx, i.A, i.sBx) end
  local function OperandA1(i)   return string.format(config.FORMAT_A1, i.A) end
  local function OperandAx(i)   return string.format(config.FORMAT_Ax, i.Ax) end

  ---------------------------------------------------------------
  -- comment formatting helpers
  -- calculate jump location, conditional flag
  ---------------------------------------------------------------
  local function CommentLoc(sbx, cond)
    local loc = string.format("pc+=%d (goto [%d])", sbx, pos + 1 + sbx)
    if cond then loc = loc..cond end
    return loc
  end

  ---------------------------------------------------------------
  -- Kst(x) - constant (in constant table)
  ---------------------------------------------------------------
  local function IS_CONSTANT(r)
    return (r >= config.BITRK)
  end
  local function Kst(index, quoted)
    local typec = func.typek[index + 1]
    local c = func.k[index + 1]
    if typec == config.LUA_TSHRSTR or typec == config.LUA_TLNGSTR then
      return EscapeString(c.val, quoted)
    elseif type(c) == "number" or type(c) == "boolean" then
      return tostring(c)
    else
      return "nil"
    end
  end
  local function K(index)
    return "K"..tostring(index).."(="..Kst(index, true)..")"
  end

  ---------------------------------------------------------------
  -- R(x)
  ---------------------------------------------------------------
  local function RName(index)
    -- can we get local vaname using index and pos ?
    -- func.locals[?].varname .startpc .endpc
    return nil
  end
  local function R(index)
    local name = RName(index)
    if name and name ~= "" then
      return "R"..tostring(index).."(="..name..")"
    else
      return "R"..tostring(index)
    end
  end

  ---------------------------------------------------------------
  -- RK(x) == if BITRK then Kst(x&~BITRK) else R(x)
  ---------------------------------------------------------------
  local function RK(index)
    if IS_CONSTANT(index) then
      return K(index - config.BITRK)
    else
      return R(index)
    end
  end

  ---------------------------------------------------------------
  -- comments for Upvalue
  ---------------------------------------------------------------
  local function UName(x)
    local upvalue = func.upvalues[x + 1]
    if upvalue and upvalue.name then
      return EscapeString(upvalue.name)
    else
      return nil
    end
  end
  local function U(x)
    local name = UName(x)
    if name and name ~= "" then
      return 'U'..tostring(x).."(="..name..")"
    else
      return 'U'..tostring(x)
    end
  end

  ---------------------------------------------------------------
  -- comments for Reg list
  ---------------------------------------------------------------
  local function RList(start,num)
    if (num>2) then
      return "R"..start.." to R"..(start+num-1)
    elseif (num==2) then
      return "R"..start..", R"..(start+1)
    elseif (num==1) then
      return "R"..start
    elseif (num==0) then
      return ""
    else
      return "R"..start.." to top"
    end
  end

  ---------------------------------------------------------------
  -- floating point byte conversion
  -- bit positions: mmmmmxxx, actual: (1xxx) * 2^(m-1)
  ---------------------------------------------------------------
  local function fb2int(x)
    local e = math.floor(x / 8) % 32
    if e == 0 then return x end
    return math.ldexp((x % 8) + 8, e - 1)
  end

  local a=inst.A
  local b=inst.B
  local c=inst.C
  local bx=inst.Bx
  local sbx=inst.sBx
  local ax=inst.Ax
  local o=inst.OP
  local pc=pos
  
  local isop_opc = config.opcodes
  local isop_lower = string.lower
  local function isop(opname)
    return o == isop_opc[isop_lower(opname)]
  end
  ---------------------------------------------------------------
  -- yeah, I know this is monstrous...
  -- * see the descriptions in lopcodes.h for more information
  ---------------------------------------------------------------
  if inst.prev then -- continuation of SETLIST
    Operand = string.format(config.FORMAT_Ax, inst.Ax)..config.PAD_Ax
  ---------------------------------------------------------------
  elseif isop("MOVE") then -- MOVE A B
    Operand = OperandAB(inst)
    Comment = string.format("%s := %s",R(a),R(b))
  ---------------------------------------------------------------
  elseif isop("LOADK") then -- LOADK A Bx
    Operand = OperandABx(inst)
    Comment = string.format("%s := %s",R(a),K(bx))
  ---------------------------------------------------------------
  elseif isop("LOADKX") then -- LOADKX A
    Operand = OperandA1(inst)
    Comment = string.format("%s :=",R(a))
  ---------------------------------------------------------------
  elseif isop("EXTRAARG") then -- EXTRAARG Ax
    Operand = OperandAx(inst)
    Comment = K(ax)
  ---------------------------------------------------------------
  elseif isop("LOADBOOL") then -- LOADBOOL A B C
    Operand = OperandABC(inst)
    local v
    if b == 0 then v = "false" else v = "true" end
    if c > 0 then
      Comment = string.format("%s := %s; %s",R(a),v,CommentLoc(1));
    else
      Comment = string.format("%s := %s",R(a),v)
    end
    v=nil
  ---------------------------------------------------------------
  elseif isop("LOADNIL") then -- LOADNIL A B
    Operand = OperandAB(inst)
    Comment = RList(a,b+1).." := nil"
  ---------------------------------------------------------------
  elseif isop("GETUPVAL") then -- GETUPVAL A B
    Operand = OperandAB(inst)
    Comment = string.format("%s := %s", R(a), U(b));
  ---------------------------------------------------------------
  elseif isop("SETUPVAL") then -- SETUPVAL A B
    Operand = OperandAB(inst)
    Comment = string.format("%s := %s", U(b), R(a))
  ---------------------------------------------------------------
  elseif isop("GETTABUP") then -- GETTABUP A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s := %s[%s]", R(a), U(b), RK(c))
  ---------------------------------------------------------------  
  elseif isop("SETTABUP") then -- SETTABUP A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s[%s] := %s", U(a), RK(b), RK(c))
  ---------------------------------------------------------------
  elseif isop("GETTABLE") then -- GETTABLE A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s := %s[%s]",R(a),R(b),RK(c))
  ---------------------------------------------------------------
  elseif isop("SETTABLE") then -- SETTABLE A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s[%s] := %s",R(a),RK(b),RK(c))
  ---------------------------------------------------------------
  elseif isop("NEWTABLE") then -- NEWTABLE A B C
    Operand = OperandABC(inst)
    local ar = fb2int(b)  -- array size
    local hs = fb2int(c)  -- hash size
    Comment = string.format("%s := {} , array_size=%d, hash_size=%d",R(a),ar,hs)
  ---------------------------------------------------------------
  elseif isop("SELF") then -- SELF A B C
    Operand = OperandABC(inst)
    Comment = string.format("R%d := %s; %s := %s[%s]",a+1,R(b),R(a),R(b),RK(c))
  ---------------------------------------------------------------
  elseif isop("ADD") or   -- ADD A B C
         isop("SUB") or   -- SUB A B C
         isop("MUL") or   -- MUL A B C
         isop("DIV") or   -- DIV A B C
         isop("MOD") or   -- MOD A B C
         isop("POW") or   -- POW A B C
         isop("IDIV") or  -- IDIV A B C
         isop("BAND") or  -- BAND A B C
         isop("BOR") or   -- BOR A B C
         isop("BXOR") or  -- BXOR A B C
         isop("SHL") or   -- SHL A B C
         isop("SHR") then -- SHR A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s := %s %s %s",R(a),RK(b),config.operators[inst.OP],RK(c))
  ---------------------------------------------------------------
  elseif isop("UNM") or    -- UNM A B
         isop("NOT") or    -- NOT A B
         isop("LEN") or    -- LEN A B
         isop("BNOT") then -- BNOT A B
    Operand = OperandAB(inst)
    Comment = string.format("%s := %s %s",R(a),config.operators[inst.OP],RK(b))
  ---------------------------------------------------------------
  elseif isop("CONCAT") then -- CONCAT A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s := %s",R(a),RList(b,c-b+1))
  ---------------------------------------------------------------
  elseif isop("JMP") then -- JMP A sBx
    Operand = OperandAsBx(inst)
    if a>0 then
      Comment="close all upvalues >= R"..(a-1).."; "
    else
      Comment=""
    end
    Comment = Comment..CommentLoc(sbx)
  ---------------------------------------------------------------
  elseif isop("EQ") or   -- EQ A B C
         isop("LT") or   -- LT A B C
         isop("LE") then -- LE A B C
    Operand = OperandABC(inst)
    Comment = string.format("%s %s %s, ",RK(b),config.operators[inst.OP],RK(c))
    local sense = " if false"
    if inst.A == 0 then sense = " if true" end
    Comment = Comment..CommentLoc(1, sense)
  elseif isop("TESTSET") then -- TESTSET A B C
    Operand = OperandABC(inst)
    local sense = " "
    if c == 0 then sense = " not " end
    Comment = string.format("if%s%s then %s = %s else ",sense,R(b),R(a),R(b))
    Comment = Comment..CommentLoc(1)
  elseif isop("TEST") then -- TEST A C
    Operand = OperandAC(inst)
    local sense = " not "
    if c == 0 then sense = " " end
    Comment = string.format("if%s%s then ",sense,R(a))
    Comment = Comment..CommentLoc(1)
  ---------------------------------------------------------------
  elseif isop("CALL") or   -- CALL A B C
         isop("TAILCALL") then -- TAILCALL A B C
    Operand = OperandABC(inst)
    CommentArg = RList(a+1,b-1)
    CommentRtn = RList(a,c-1)
    Comment = string.format("%s := %s(%s)",CommentRtn,R(a),CommentArg);
  ---------------------------------------------------------------
  elseif isop("RETURN") then -- RETURN A B
    Operand = OperandAB(inst)
    CommentRtn = RList(a,b-1)
    Comment = "return "..CommentRtn
  ---------------------------------------------------------------
  elseif isop("FORLOOP") then -- FORLOOP A sBx
    Operand = OperandAsBx(inst)
    Comment = string.format("R%d += R%d; if R%d <= R%d then { R%d := R%d; %s }",a,a+2,a,a+1,a+3,a,CommentLoc(sbx));
  ---------------------------------------------------------------
  elseif isop("FORPREP") then -- FORPREP A sBx
    Operand = OperandAsBx(inst)
    Comment = string.format("R%d -= R%d; %s",a,a+2,CommentLoc(sbx));
  ---------------------------------------------------------------
  elseif isop("TFORCALL") then -- TFORCALL A C
    Operand = OperandAC(inst)
    if (c>0) then
      CommentRtn = RList(a+3,c)
    else
      CommentRtn = "Error Regs"
    end
    Comment = string.format("%s := R%d(R%d,R%d)", CommentRtn, a, a+1,a+2);
  ---------------------------------------------------------------
  elseif isop("TFORLOOP") then -- TFORLOOP A sBx
    Operand = OperandAsBx(inst)
    Comment = string.format("if R%d ~= nil then { R%d := R%d; %s", a+1,a, a+1, CommentLoc(sbx));
  ---------------------------------------------------------------
  elseif isop("SETLIST") then -- SETLIST A B C
    Operand = OperandABC(inst)
    -- R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B
    if c == 0 then
      -- grab next inst when index position is large
      local ninst = {}
      DecodeInst(func.code[pos + 1], ninst)
      c = ninst.Ax
      func.inst[pos + 1].prev = true
    end
    local start = (c - 1) * config.FPF + 1
    local last = start + b - 1
    CommentArg = start.." to "
    local EndReg
    if b ~= 0 then
      CommentArg = CommentArg..last
      EndReg = "R"..(a+last)
    else
      CommentArg = CommentArg.."top"
      EndReg = "top"
    end
    Comment = string.format("%s[%s] := R%d to %s",R(a),CommentArg,a+1,EndReg)
  ---------------------------------------------------------------
  elseif isop("CLOSURE") then -- CLOSURE A Bx
    Operand = OperandABx(inst)
    -- lets user know how many following instructions are significant
    Comment = func.p[bx + 1].sizeupvalues.." upvalues"
    Comment = string.format("%s := closure(function[%d]) %s",R(a),bx,Comment)
  ---------------------------------------------------------------
  elseif isop("VARARG") then -- VARARG A B
    Operand = OperandAB(inst)
    CommentRtn = RList(a,b-1)
    if ( b==1 or b<0 ) then
      CommentRtn = "Error Regs"
    end
    Comment = CommentRtn.." := ..."
  ---------------------------------------------------------------
  else
    -- add your VM extensions here
    Operand = string.format("OP %d %s", inst.OP, config.opnames[inst.OP])
  end

  ---------------------------------------------------------------
  -- compose operands and comments
  ---------------------------------------------------------------
  if Comment and Comment ~= "" then
    Operand = Operand..config.DISPLAY_SEP
              ..config.DISPLAY_COMMENT..Comment
  end
  return LeftJustify(inst.opname, config.WIDTH_OPCODE)
         ..config.DISPLAY_SEP..Operand
end

--[[-------------------------------------------------------------------
-- Source listing merging
-- * for convenience, file name matching is first via case-sensitive
--   comparison, then case-insensitive comparison, and the first
--   match found using either method is the one that is used
--]]-------------------------------------------------------------------

-----------------------------------------------------------------------
-- initialize source list for merging
-- * this will normally be called by the main chunk function
-- * the source listing is read only once, upon initialization
-----------------------------------------------------------------------
function SourceInit(source)
  if config.source then config.srcprev = 0; return end
  if not source or source == "" or
     string.sub(source, 1, 1) ~= "@" then
    return
  end
  source = string.sub(source, 2)                -- chomp leading @
  for _, fname in ipairs(other_files) do        -- find a match
    if not config.source then
      if fname == source or
         string.lower(fname) == string.lower(source) then
        config.source = fname
      end
    end
  end
  if not config.source then return end          -- no source file
  local INF = io.open(config.source, "rb")      -- read in source file
  if not INF then
    error("cannot read file \""..filename.."\"")
  end
  config.srcline = {}; config.srcmark = {}
  local n, line = 1
  repeat
    line = INF:read("*l")
    if line then
      config.srcline[n], config.srcmark[n] = line, false
      n = n + 1
    end
  until not line
  io.close(INF)
  config.srcsize = n - 1
  config.DISPLAY_SRC_WIDTH = WidthOf(config.srcsize)
  config.srcprev = 0
end

-----------------------------------------------------------------------
-- mark source lines
-- * marks source lines as a function is read to delineate stuff
-----------------------------------------------------------------------
function SourceMark(func)
  if not config.source then return end
  if func.sizelineinfo == 0 then return end
  for i = 1, func.sizelineinfo do
    if i <= config.srcsize then
      config.srcmark[func.lineinfo[i]] = true
    end
  end
end

-----------------------------------------------------------------------
-- generate source lines
-- * peek at lines above and print them if they have not been printed
-- * mark all printed lines so all non-code lines are printed once only
-----------------------------------------------------------------------
function SourceMerge(func, pc)
  if not config.source or not config.DISPLAY_FLAG then return end
  local lnum = func.lineinfo[pc]
  -- don't print anything new if instruction is on the same line
  if config.srcprev == lnum then return end
  config.srcprev = lnum
  if config.srcsize < lnum then return end      -- something fishy
  local lfrom = lnum
  config.srcmark[lnum] = true
  while lfrom > 1 and config.srcmark[lfrom - 1] == false do
    lfrom = lfrom - 1
    config.srcmark[lfrom] = true
  end
  for i = lfrom, lnum do
    WriteLine(config.DISPLAY_COMMENT
              .."("..ZeroPad(i, config.DISPLAY_SRC_WIDTH)..")"
              ..config.DISPLAY_SEP..config.srcline[i])
  end
end

--[[-------------------------------------------------------------------
-- ChunkSpy main processing function
-- * in order to maintain correct positional order, the listing will
--   show functions as nested; a level number is kept to help the
--   user trace the extent of functions in the listing
--]]-------------------------------------------------------------------

function ChunkSpy(chunk_name, chunk)
  ---------------------------------------------------------------
  -- variables
  ---------------------------------------------------------------
  local idx = 1
  local previdx, len
  local result = {}     -- table with all parsed data
  local stat = {}
  result.chunk_name = chunk_name or ""
  result.chunk_size = string.len(chunk)

  ---------------------------------------------------------------
  -- tests if a given number of bytes is available
  ---------------------------------------------------------------
  local function TestChunk(size, idx, errmsg)
    if idx + size - 1 > result.chunk_size then
      error(string.format("chunk too small for %s at offset %d", errmsg, idx - 1))
    end
  end

  ---------------------------------------------------------------
  -- loads a single byte and returns it as a number
  ---------------------------------------------------------------
  local function LoadByte()
    previdx = idx
    idx = idx + 1
    return string.byte(chunk, previdx)
  end

  ---------------------------------------------------------------
  -- loads a block of endian-sensitive bytes
  -- * rest of code assumes little-endian by default
  ---------------------------------------------------------------
  local function LoadBlock(size, notreverse)
    if not pcall(TestChunk, size, idx, "LoadBlock") then return end
    previdx = idx
    idx = idx + size
    local b = string.sub(chunk, idx - size, idx - 1)
    if config.endianness == 1 or notreverse then
      return b
    else-- reverse bytes if big endian
      return string.reverse(b)
    end
  end

  ---------------------------------------------------------------
  -- display a position, hex data, description line
  ---------------------------------------------------------------
  function FormatLine(size, desc, index, segment)
    if not config.DISPLAY_FLAG or config.DISPLAY_BRIEF then return end
    if config.DISPLAY_HEX_DATA then
      -- nicely formats binary chunk data in multiline hexadecimal
      if size == 0 then
        WriteLine(FormatPos(index)..config.DISPLAY_SEP
                  ..config.BLANKS_HEX_DATA..config.DISPLAY_SEP
                  ..desc)
      else
        -- split hex data into config.WIDTH_HEX byte strings
        while size > 0 do
          local d, dlen = "", size
          if size > config.WIDTH_HEX then dlen = config.WIDTH_HEX end
          -- build hex data digits
          for i = 0, dlen - 1 do
            d = d..string.format("%02X", string.byte(chunk, index + i))
          end
          -- add padding or continuation indicator
          d = d..string.rep("  ", config.WIDTH_HEX - dlen)
          if segment or size > config.WIDTH_HEX then
            d = d.."+"; size = size - config.WIDTH_HEX
          else
            d = d.." "; size = 0
          end
          -- description only on first line of a multiline
          if desc then
            WriteLine(FormatPos(index)..config.DISPLAY_SEP
                      ..d..config.DISPLAY_SEP
                      ..desc)
            desc = nil
          else
            WriteLine(FormatPos(index)..config.DISPLAY_SEP..d)
          end
          index = index + dlen
        end--while
      end--if size
    else--no hex data mode
      WriteLine(FormatPos(index)..config.DISPLAY_SEP..desc)
    end
    -- end of FormatLine
  end

  ---------------------------------------------------------------
  -- initialize listing display
  ---------------------------------------------------------------
  DisplayInit(result.chunk_size)
  HeaderLine()                  -- listing display starts here
  if result.chunk_name then
    FormatLine(0, "** source chunk: "..result.chunk_name, idx)
    if config.DISPLAY_BRIEF then WriteLine(config.DISPLAY_COMMENT.."source chunk: "..result.chunk_name) end
  end
  DescLine("** global header start **")

  ---------------------------------------------------------------
  -- test signature
  ---------------------------------------------------------------
  len = string.len(config.SIGNATURE)
  TestChunk(len, idx, "header signature")
  if string.sub(chunk, 1, len) ~= config.SIGNATURE then
    error("header signature not found, this is not a Lua chunk")
  end
  FormatLine(len, "header signature: "..EscapeString(config.SIGNATURE, 1), idx)
  idx = idx + len

  ---------------------------------------------------------------
  -- test version
  ---------------------------------------------------------------
  TestChunk(1, idx, "version byte")
  result.version = LoadByte()
  if not config.IgnoreVersion and result.version ~= config.VERSION then
    error(string.format("ChunkSpy cannot read version %02X chunks", result.version))
  end
  FormatLine(1, "version (major:minor hex digits)", previdx)

  ---------------------------------------------------------------
  -- test format (from 5.1)
  -- * ChunkSpy does not accept anything other than 0. For custom
  -- * binary chunks, modify ChunkSpy to read it properly.
  ---------------------------------------------------------------
  TestChunk(1, idx, "format byte")
  result.format = LoadByte()
  if not config.IgnoreFormat and result.format ~= config.FORMAT then
    error(string.format("ChunkSpy cannot read format %02X chunks", result.format))
  end
  FormatLine(1, "format (0=official)", previdx)

  -- new in 5.2 called LUAC_TAIL, in 5.3 move here and rename to LUAC_DATA
  len = string.len(config.LUAC_DATA)
  TestChunk(len, idx, "LUAC_DATA")
  local LUAC_DATA = LoadBlock(len, true)
  if LUAC_DATA ~= config.LUAC_DATA then
    error("header LUAC_DATA not found, this is not a Lua chunk")
  end
  FormatLine(len, "LUAC_DATA: "..EscapeString(LUAC_DATA, 1), previdx)

  ---------------------------------------------------------------
  -- test sizes
  ---------------------------------------------------------------
  TestChunk(4, idx, "size bytes")
  local function TestSize(mysize, sizename, typename)
    local byte = LoadByte()
    if not config.AUTO_DETECT then
      if byte ~= config[mysize] then
        error(string.format("mismatch in %s size (needs %d but read %d)",
          sizename, config[mysize], byte))
      end
    else
      config[mysize] = byte
    end
    FormatLine(1, string.format("size of %s (%s)", sizename, typename), previdx)
  end
  -- byte sizes
  TestSize("size_int", "int", "bytes")
  TestSize("size_size_t", "size_t", "bytes")
  TestSize("size_Instruction", "Instruction", "bytes")
  TestSize("size_lua_Integer", "Integer", "bytes")
  TestSize("size_lua_Number", "Number", "bytes")

  -- initialize decoder (see the 5.0.2 script if you want to customize
  -- bit field sizes; Lua 5.1 has fixed instruction bit field sizes)
  DecodeInit()

  --[[
  ---------------------------------------------------------------
  -- test integral flag (from 5.1)
  ---------------------------------------------------------------
  TestChunk(1, idx, "integral byte")
  config.integral = LoadByte()
  FormatLine(1, "integral (1=integral)", previdx)

  ---------------------------------------------------------------
  -- verify or determine lua_Number type
  ---------------------------------------------------------------
  local num_id = config.size_lua_Number .. config.integral
  if not config.AUTO_DETECT then
    if config.number_type ~= LUANUMBER_ID[num_id] then
      error("incorrect lua_Number format or bad test number")
    end
  else
    -- look for a number type match in our table
    config.number_type = nil
    for i, v in pairs(LUANUMBER_ID) do
      if num_id == i then config.number_type = v end
    end
    if not config.number_type then
      error("unrecognized lua_Number type")
    end
  end
  DescLine("* number type: "..config.number_type)
  --]]

  --[[
  ---------------------------------------------------------------
  -- primitive platform auto-detection
  ---------------------------------------------------------------
  if config.AUTO_DETECT then
    config.description = nil
    for _, cfg in pairs(CONFIGURATION) do
      if cfg.endianness == config.endianness and
         cfg.size_int == config.size_int and
         cfg.size_size_t == config.size_size_t and
         cfg.size_Instruction == config.size_Instruction and
         cfg.size_lua_Number == config.size_lua_Number and
         cfg.integral == config.integral and
         cfg.number_type == config.number_type then
        config.description = cfg.description
      end
    end
    if not config.description then
      config.description = "chunk platform unrecognized"
    end
    -- some parameters are not in the global header, e.g. FPF
    -- see the config table for more on these constants
  end
  DescLine("* "..config.description)
  if config.DISPLAY_BRIEF then WriteLine(config.DISPLAY_COMMENT..config.description) end
  --]]
  
  ---------------------------------------------------------------
  -- test endianness
  -- LUAC_INT = 0x5678 in lua 5.3
  ---------------------------------------------------------------
  TestChunk(8, idx, "endianness bytes")
  local endianness_bytes = LoadBlock(8)
  local endianness_value = convert_from_int(endianness_bytes, 8)
  --[[
  if not config.AUTO_DETECT then
    if endianness ~= config.endianness then
      error("unsupported endianness")
    end
  else
    config.endianness = endianness
  end
  --]]
  FormatLine(8, "endianness bytes "..string.format("0x%x", endianness_value), previdx)

  ---------------------------------------------------------------
  -- test endianness
  -- LUAC_NUM = cast_num(370.5) in lua 5.3
  ---------------------------------------------------------------
  TestChunk(8, idx, "float format bytes")
  local float_format_bytes = LoadBlock(8)
  local float_format_value = convert_from_double(float_format_bytes)
  FormatLine(8, "float format "..float_format_value, previdx)

  TestChunk(1, idx, "global closure nupvalues")
  local global_closure_nupvalues = LoadByte()
  FormatLine(1, "global closure nupvalues "..global_closure_nupvalues, previdx)

  -- end of global header
  stat.header = idx - 1
  DisplayStat("* global header = "..stat.header.." bytes")
  DescLine("** global header end **")

  ---------------------------------------------------------------
  -- this is recursively called to load the chunk or function body
  ---------------------------------------------------------------
  local function LoadFunction(funcname, num, level)
    local func = {}

    -------------------------------------------------------------
    -- loads an integer (signed)
    -------------------------------------------------------------
    local function LoadInt()
      local x = LoadBlock(config.size_int)
      if not x then
        error("could not load integer")
      else
        local sum = 0
        for i = config.size_int, 1, -1 do
          sum = sum * 256 + string.byte(x, i)
        end
        -- test for negative number
        if string.byte(x, config.size_int) > 127 then
          sum = sum - math.ldexp(1, 8 * config.size_int)
        end
        -- from the looks of it, integers needed are positive
        if sum < 0 then error("bad integer") end
        return sum
      end
    end

    -------------------------------------------------------------
    -- loads a size_t (assume unsigned)
    -------------------------------------------------------------
    local function LoadSize()
      local x = LoadBlock(config.size_size_t)
      if not x then
        --error("could not load size_t") handled in LoadString()
        return
      else
        local sum = 0
        for i = config.size_size_t, 1, -1 do
          sum = sum * 256 + string.byte(x, i)
        end
        return sum
      end
    end

    -------------------------------------------------------------
    -- loads a integer (lua_Integer type)
    -------------------------------------------------------------
    local function LoadInteger()
      local x = LoadBlock(config.size_lua_Integer)
      if not x then
        error("could not load lua_Integer")
      else
        local convert_func = convert_from[config.integer_type]
        if not convert_func then
          error("could not find conversion function for lua_Integer")
        end
        return convert_func(x)
      end
    end

    -------------------------------------------------------------
    -- loads a number (lua_Number type)
    -------------------------------------------------------------
    local function LoadNumber()
      local x = LoadBlock(config.size_lua_Number)
      if not x then
        error("could not load lua_Number")
      else
        local convert_func = convert_from[config.number_type]
        if not convert_func then
          error("could not find conversion function for lua_Number")
        end
        return convert_func(x)
      end
    end

    -------------------------------------------------------------
    -- load a string (size, data pairs)
    -------------------------------------------------------------
    local function LoadString()
      local len = LoadByte()
      local islngstr = nil
      if not len then
        error("could not load String")
        return
      end
      if len == 255 then
        len = LoadSize()
        islngstr = true
      end
      if len == 0 then        -- there is no error, return a nil
        return nil, len, islngstr
      end
      if len == 1 then
        return "", len, islngstr
      end
      TestChunk(len - 1, idx, "LoadString")
      local s = string.sub(chunk, idx, idx + len - 2)
      idx = idx + len - 1
      return s, len, islngstr
    end

    local function LoadString53()
      local str = {}
      str.val, str.len, str.islngstr = LoadString()
      return str
    end

    -------------------------------------------------------------
    -- load line information
    -------------------------------------------------------------
    local function LoadLines()
      local size = LoadInt()
      func.pos_lineinfo = previdx
      func.lineinfo = {}
      func.sizelineinfo = size
      for i = 1, size do
        func.lineinfo[i] = LoadInt()
      end
    end

    -------------------------------------------------------------
    -- load locals information
    -------------------------------------------------------------
    local function LoadLocals()
      local n = LoadInt()
      func.pos_locvars = previdx
      func.locvars = {}
      func.sizelocvars = n
      for i = 1, n do
        local locvar = {}
        locvar.varname53 = LoadString53()
        locvar.varname = locvar.varname53.val
        locvar.pos_varname = previdx
        locvar.startpc = LoadInt()
        locvar.pos_startpc = previdx
        locvar.endpc = LoadInt()
        locvar.pos_endpc = previdx
        func.locvars[i] = locvar
      end
    end

    -------------------------------------------------------------
    -- load upvalues information
    -------------------------------------------------------------
    local function LoadUpvalues()
      local n = LoadInt()
      func.pos_upvalues = previdx
      func.upvalues = {}
      func.sizeupvalues = n
      for i = 1, n do
        local upvalue = {}
        upvalue.instack = LoadByte()
        upvalue.pos_instack = previdx
        upvalue.idx = LoadByte()
        upvalue.pos_idx = previdx
        func.upvalues[i] = upvalue
      end
    end

    -------------------------------------------------------------
    -- load constants information (data)
    -------------------------------------------------------------
    local function LoadConstants()
      local n = LoadInt()
      func.pos_ks = previdx
      func.k = {}
      func.typek = {}
      func.sizek = n
      func.posk = {}
      for i = 1, n do
        local t = LoadByte()
        func.typek[i] = t
        func.posk[i] = previdx
        if t == config.LUA_TNIL then
          func.k[i] = nil
        elseif t == config.LUA_TBOOLEAN then
          local b = LoadByte()
          if b == 0 then b = false else b = true end
          func.k[i] = b
        elseif t == config.LUA_TNUMFLT then
          func.k[i] = LoadNumber()
        elseif t == config.LUA_TNUMINT then
          func.k[i] = LoadInteger()
        elseif t == config.LUA_TSHRSTR or t == config.LUA_TLNGSTR then
          func.k[i] = LoadString53()
        else
          error("bad constant type "..t.." at "..previdx)
        end
      end--for
    end

    -------------------------------------------------------------
    -- load constants information (local functions)
    -------------------------------------------------------------
    local function LoadProtos()
      local n = LoadInt()
      func.pos_ps = previdx
      func.p = {}
      func.sizep = n
      for i = 1, n do
        -- recursive call back on itself, next level
        func.p[i] = LoadFunction(func.source, i - 1, level + 1)
      end
    end

    -------------------------------------------------------------
    -- load function code
    -------------------------------------------------------------
    local function LoadCode()
      local size = LoadInt()
      func.pos_code = previdx
      func.code = {}
      func.sizecode = size
      for i = 1, size do
        func.code[i] = LoadBlock(config.size_Instruction)
      end
    end

    -------------------------------------------------------------
    -- load upvalue names
    -------------------------------------------------------------
    local function LoadUpvalueNames()
      local n = LoadInt()
      if n > func.sizeupvalues then
        error(string.format("bad upvalue_names: read %d, expected %d", n, func.sizeupvalues))
        return
      end
      func.size_upvalue_names = n
      func.pos_upvalue_names = previdx
      for i = 1, n do
        local upvalue = func.upvalues[i]
        upvalue.name53 = LoadString53()
        upvalue.name = upvalue.name53.val
        upvalue.pos_name = previdx
      end
    end

    -------------------------------------------------------------
    -- body of LoadFunction() starts here
    -------------------------------------------------------------
    -- statistics handler
    local start = idx
    func.stat = {}
    local function SetStat(item)
      func.stat[item] = idx - start
      start = idx
    end

    -- source file name, move here in Lua 5.3
    func.source53 = LoadString53()
    func.source = func.source53.val
    func.pos_source = previdx
    if func.source == nil and level == 1 then func.source = funcname end

    -- line where the function was defined
    func.linedefined = LoadInt()
    func.pos_linedefined = previdx
    func.lastlinedefined = LoadInt()
    func.pos_lastlinedefined = previdx

    -------------------------------------------------------------
    -- some byte counts
    -------------------------------------------------------------
    if TestChunk(3, idx, "function header") then return end
    func.numparams = LoadByte()
    func.is_vararg = LoadByte()
    func.maxstacksize = LoadByte()
    SetStat("header")

    -------------------------------------------------------------
    -- these are lists, LoadProtos() may be recursive
    -------------------------------------------------------------
    -- load parts of a chunk
    LoadCode()       SetStat("code")
    LoadConstants()  SetStat("consts")
    LoadUpvalues()   SetStat("upvalues")
    LoadProtos()     SetStat("funcs")

    LoadLines()          SetStat("lines")
    LoadLocals()         SetStat("locals")
    LoadUpvalueNames()   SetStat("upvalue_names")

    return func
    -- end of LoadFunction
  end

  ---------------------------------------------------------------
  -- displays function information
  -- * decoupled from LoadFunction due to 5.2 chunk rearrangement
  ---------------------------------------------------------------
  function DescFunction(func, num, level, funcnumstr)
    -------------------------------------------------------------
    -- brief display mode with indentation style option
    -------------------------------------------------------------
    local function BriefLine(desc)
      if not config.DISPLAY_FLAG or not config.DISPLAY_BRIEF then return end
      if DISPLAY_INDENT then
        WriteLine(string.rep(config.DISPLAY_SEP, level - 1)..desc)
      else
        WriteLine(desc)
      end
    end

    -------------------------------------------------------------
    -- describe a string (size, data pairs)
    -- changed for lua 5.3
    -- s = { val=nil, len=0, islngstr=nil }, { val="", len=1, islngstr=nil }, { val="ab", len=3, islngstr=nil }
    -------------------------------------------------------------
    local function DescString(str, pos)
      local len = str.len
      local s = str.val
      if str.islngstr then
        FormatLine(1 + config.size_size_t, string.format("long string size (%d)", len), pos)
        pos = pos + 1 + config.size_size_t
      else
        FormatLine(1, string.format("string size (%d)", len), pos)
        pos = pos + 1
      end
      if len == 0 then return end
      len = len - 1
      if len <= config.WIDTH_HEX then
        FormatLine(len, EscapeString(s, 1), pos)
      else
        -- split up long strings nicely, easier to view
        while len > 0 do
          local seg_len = config.WIDTH_HEX
          if len < seg_len then seg_len = len end
          local seg = string.sub(s, 1, seg_len)
          s = string.sub(s, seg_len + 1)
          len = len - seg_len
          FormatLine(seg_len, EscapeString(seg, 1), pos, len > 0)
          pos = pos + seg_len
        end
      end
    end

    -------------------------------------------------------------
    -- describe line information
    -------------------------------------------------------------
    local function DescLines()
      local size = func.sizelineinfo
      local pos = func.pos_lineinfo
      DescLine("* lines:")
      FormatLine(config.size_int, "sizelineinfo ("..size..")", pos)
      pos = pos + config.size_int
      local WIDTH = WidthOf(size)
      DescLine("[pc] (line)")
      for i = 1, size do
        local s = string.format("[%s] (%s)", ZeroPad(i, WIDTH), func.lineinfo[i])
        FormatLine(config.size_int, s, pos)
        pos = pos + config.size_int
      end
      -- mark significant lines in source listing
      SourceMark(func)
    end

    -------------------------------------------------------------
    -- describe locals information
    -------------------------------------------------------------
    local function DescLocals()
      local n = func.sizelocvars
      DescLine("* locals:")
      FormatLine(config.size_int, "sizelocvars ("..n..")", func.pos_locvars)
      for i = 1, n do
        local locvar = func.locvars[i]
        DescString(locvar.varname53, locvar.pos_varname)
        DescLine("local ["..(i - 1).."]: "..EscapeString(locvar.varname))
        FormatLine(config.size_int, "  startpc ("..locvar.startpc..")", locvar.pos_startpc)
        FormatLine(config.size_int, "  endpc   ("..locvar.endpc..")",locvar.pos_endpc)
        BriefLine(".local"..config.DISPLAY_SEP..EscapeString(locvar.varname, 1)
                  ..config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1))
      end
    end

    -------------------------------------------------------------
    -- describe upvalues information
    -------------------------------------------------------------
    local function DescUpvaluesAll()
      local n = func.sizeupvalues
      for i = 1, n do
        local upvalue = func.upvalues[i]
        local name = upvalue.name or ''
        BriefLine(".upvalue"..config.DISPLAY_SEP..EscapeString(name, 1)
                  ..config.DISPLAY_SEP..tostring(upvalue.instack)
                  ..config.DISPLAY_SEP..tostring(upvalue.idx)
                  ..config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1)
                  ..config.DISPLAY_SEP.."instack="..tostring(upvalue.instack)
                  ..config.DISPLAY_SEP.."idx="..tostring(upvalue.idx))
      end
    end

    -------------------------------------------------------------
    -- describe upvalues information
    -------------------------------------------------------------
    local function DescUpvalues()
      local n = func.sizeupvalues
      DescLine("* upvalues:")
      FormatLine(config.size_int, "sizeupvalues ("..n..")", func.pos_upvalues)
      for i = 1, n do
        local upvalue = func.upvalues[i]
        local name = upvalue.name or ''
        DescLine("upvalue ["..(i - 1).."]: "..EscapeString(name))
        FormatLine(1, "  instack ("..upvalue.instack..")", upvalue.pos_instack)
        FormatLine(1, "  idx     ("..upvalue.idx..")",upvalue.pos_idx)
      end
    end

    -------------------------------------------------------------
    -- describe upvalues information
    -------------------------------------------------------------
    local function DescUpvalueNames()
      local n = func.size_upvalue_names
      DescLine("* upvalue names:")
      FormatLine(config.size_int, "size_upvalue_names ("..n..")", func.pos_upvalue_names)
      for i = 1, n do
        local upvalue = func.upvalues[i]
        DescLine("upvalue ["..(i - 1).."]: "..EscapeString(upvalue.name))
        DescString(upvalue.name53, upvalue.pos_name)
      end
    end

    -------------------------------------------------------------
    -- describe constants information (data)
    -------------------------------------------------------------
    local function DescConstants()
      local n = func.sizek
      local pos = func.pos_ks
      DescLine("* constants:")
      FormatLine(config.size_int, "sizek ("..n..")", pos)
      for i = 1, n do
        local posk = func.posk[i]
        local CONST = "const ["..(i - 1).."]: "
        local CONSTB = config.DISPLAY_SEP..config.DISPLAY_COMMENT..(i - 1)
        local k = func.k[i]
        local typek = func.typek[i]
        local typestrk = config.typestr[typek]
        FormatLine(1, "const type "..typestrk, posk)
        if typek == config.LUA_TNUMFLT then
          FormatLine(config.size_lua_Number, CONST.."("..k..")", posk + 1)
          BriefLine(".const"..config.DISPLAY_SEP..k..CONSTB)
        elseif typek == config.LUA_TNUMINT then
          FormatLine(config.size_lua_Integer, CONST.."("..k..")", posk + 1)
          BriefLine(".const"..config.DISPLAY_SEP..k..CONSTB)
        elseif typek == config.LUA_TBOOLEAN then
          FormatLine(1, CONST.."("..tostring(k)..")", posk + 1)
          BriefLine(".const"..config.DISPLAY_SEP..tostring(k)..CONSTB)
        elseif typek == config.LUA_TSHRSTR or typek == config.LUA_TLNGSTR then
          DescString(k, posk + 1)
          DescLine(CONST..EscapeString(k.val, 1))
          BriefLine(".const"..config.DISPLAY_SEP..EscapeString(k.val, 1)..CONSTB)
        elseif typek == config.LUA_TNIL then
          DescLine(CONST.."nil")
          BriefLine(".const"..config.DISPLAY_SEP.."nil"..CONSTB)
        end
      end--for
    end

    -------------------------------------------------------------
    -- describe constants information (local functions)
    -------------------------------------------------------------
    local function DescProtos()
      local n = func.sizep
      DescLine("* functions:")
      FormatLine(config.size_int, "sizep ("..n..")", func.pos_ps)
      for i = 1, n do
        -- recursive call back on itself, next level
        local newfuncnumstr = funcnumstr..'_'..(i - 1)
        DescFunction(func.p[i], i - 1, level + 1, newfuncnumstr)
      end
    end

    -------------------------------------------------------------
    -- describe function code
    -- * inst decode subfunctions: DecodeInst() and DescribeInst()
    -------------------------------------------------------------
    local function DescCode()
      local size = func.sizecode
      local pos = func.pos_code
      DescLine("* code:")
      FormatLine(config.size_int, "sizecode ("..size..")", pos)
      pos = pos + config.size_int
      func.inst = {}
      local ISIZE = WidthOf(size)
      for i = 1, size do
        func.inst[i] = {}
      end
      for i = 1, size do
        DecodeInst(func.code[i], func.inst[i])
        local inst = func.inst[i]
        -- compose instruction: opcode operands [; comments]
        local d = DescribeInst(inst, i, func)
        d = string.format("[%s] %s", ZeroPad(i, ISIZE), d)
        -- source code insertion
        SourceMerge(func, i)
        FormatLine(config.size_Instruction, d, pos)
        BriefLine(d)
        pos = pos + config.size_Instruction
      end
    end

    -------------------------------------------------------------
    -- describe function source
    -------------------------------------------------------------
    local function DescSource()
      -- source file name
      DescString(func.source53, func.pos_source)
      if func.source == nil then
        DescLine("source name: (none)")
      else
        DescLine("source name: "..EscapeString(func.source))
      end
    end

    -------------------------------------------------------------
    -- body of DescFunction() starts here
    -------------------------------------------------------------
    DescLine("")
    BriefLine("")
    FormatLine(0, "** function ["..num.."] definition (level "..level..") "..funcnumstr,
               func.pos_source)
    BriefLine("; function ["..num.."] definition (level "..level..") "..funcnumstr)
    DescLine("** start of function "..funcnumstr.." **")

    -- optionally initialize source listing merging
    SourceInit(func.source)

    DescSource()

    -- line where the function was defined
    local pos = func.pos_linedefined
    FormatLine(config.size_int, "line defined ("..func.linedefined..")", pos)
    pos = pos + config.size_int
    FormatLine(config.size_int, "last line defined ("..func.lastlinedefined..")", pos)
    pos = pos + config.size_int

    -- display byte counts
    FormatLine(1, "numparams ("..func.numparams..")", pos)
    FormatLine(1, "is_vararg ("..func.is_vararg..")", pos + 1)
    FormatLine(1, "maxstacksize ("..func.maxstacksize..")", pos + 2)
    BriefLine(string.format("; %d upvalues, %d params, is_vararg = %d, %d stacks",
      func.sizeupvalues, func.numparams, func.is_vararg, func.maxstacksize))
    BriefLine(string.format(".function%s%d %d %d %d", config.DISPLAY_SEP,
      func.sizeupvalues, func.numparams, func.is_vararg, func.maxstacksize))

    -- display parts of a chunk
    if config.DISPLAY_FLAG and config.DISPLAY_BRIEF then
      -- brief displays 'declarations' first
      DescLocals()
      DescUpvaluesAll()
      DescConstants()
      DescCode()
      DescProtos()
    else
      -- normal displays positional order
      DescCode()
      DescConstants()
      DescUpvalues()  
      DescProtos()
      DescLines()
      DescLocals()
      DescUpvalueNames()
    end

    -- show function statistics block
    DisplayStat("* func header        = "..func.stat.header.." bytes")
    DisplayStat("* lines size         = "..func.stat.lines.." bytes")
    DisplayStat("* locals size        = "..func.stat.locals.." bytes")
    DisplayStat("* upvalues size      = "..func.stat.upvalues.." bytes")
    DisplayStat("* upvalue names size = "..func.stat.upvalue_names.." bytes")
    DisplayStat("* consts size        = "..func.stat.consts.." bytes")
    DisplayStat("* funcs size         = "..func.stat.funcs.." bytes")
    DisplayStat("* code size          = "..func.stat.code.." bytes")
    func.stat.total = func.stat.header + func.stat.lines +
                      func.stat.locals + func.stat.upvalues +
                      func.stat.consts + func.stat.funcs +
                      func.stat.code + func.stat.upvalue_names
    DisplayStat("* TOTAL size    = "..func.stat.total.." bytes")
    DescLine("** end of function "..funcnumstr.." **\n")
    BriefLine("; end of function "..funcnumstr.."\n")
  end

  ---------------------------------------------------------------
  -- actual call to start the function loading process
  ---------------------------------------------------------------
  result.func = LoadFunction("(chunk)", 0, 1)
  DescFunction(result.func, 0, 1, "0")
  stat.total = idx - 1
  DisplayStat("* TOTAL size = "..stat.total.." bytes")
  result.stat = stat
  FormatLine(0, "** end of chunk **", idx)
  return result
  -- end of ChunkSpy
end

--[[-------------------------------------------------------------------
-- Writes out a binary chunk as a string or returns it
-- * writer generally assumes the set configuration is good
-- * TODO add tests to ensure that binary chunk rules are followed
--]]-------------------------------------------------------------------

function WriteBinaryChunk(parsed, tofile)
  local Buffer = {}

  -- setup for binary file I/O
  if tofile then
    if not config.OUTPUT_FILE then
      error("must specify an output filename for rewrites")
    else -- a kludge
      WriteLine = function(s) config.OUTPUT_FILE:write(s) end
    end
  end

  ---------------------------------------------------------------
  -- output functions
  ---------------------------------------------------------------
  local function Dump(s)
    if tofile then WriteLine(s) else table.insert(Buffer, s) end
  end
  local function DumpByte(b)
    Dump(string.char(b))
  end
  ---------------------------------------------------------------
  -- writes a block of bytes to little/big-endian output
  ---------------------------------------------------------------
  local function WriteBlock(v)
    if config.endianness == 1 then
      Dump(v)
    else-- reverse bytes if big endian
      Dump(string.reverse(v))
    end
  end

  ---------------------------------------------------------------
  -- write header
  ---------------------------------------------------------------
  Dump(config.SIGNATURE)
  DumpByte(config.VERSION)
  DumpByte(config.FORMAT)
  DumpByte(config.endianness)
  DumpByte(config.size_int)     -- byte sizes
  DumpByte(config.size_size_t)
  DumpByte(config.size_Instruction)
  DumpByte(config.size_lua_Number)
  DumpByte(config.integral)
  Dump(config.LUAC_DATA)
  DecodeInit()
  -- no more test number in 5.1

  ---------------------------------------------------------------
  -- recursively called to write function data
  ---------------------------------------------------------------
  local function WriteFunction(func)
    -------------------------------------------------------------
    -- writes an unsigned integer (for integers, size_ts)
    -- * unsigned because the relevant data is always positive
    -------------------------------------------------------------
    local function WriteUnsigned(num, type_size)
      if not type_size then type_size = config.size_int end
      local v = ""
      for i = 1, type_size do
        v = v..string.char(num % 256); num = math.floor(num / 256)
      end
      WriteBlock(v)
    end

    -------------------------------------------------------------
    -- writes a number (lua_Number type)
    -------------------------------------------------------------
    local function WriteNumber(num)
      local convert_func = convert_to[config.number_type]
      if not convert_func then
        error("could not find conversion function for lua_Number")
      end
      WriteBlock(convert_func(num))
    end

    -------------------------------------------------------------
    -- writes a string
    -------------------------------------------------------------
    local function WriteString(str)
      if not str then
        WriteUnsigned(0, config.size_size_t)
        return
      end
      str = str.."\0"   -- mandatory NUL termination
      WriteUnsigned(string.len(str), config.size_size_t)
      Dump(str)
    end

    -------------------------------------------------------------
    -- write line information
    -------------------------------------------------------------
    local function WriteLines()
      WriteUnsigned(func.sizelineinfo)
      for i = 1, func.sizelineinfo do WriteUnsigned(func.lineinfo[i]) end
    end

    -------------------------------------------------------------
    -- write locals information
    -------------------------------------------------------------
    local function WriteLocals()
      WriteUnsigned(func.sizelocvars)
      for i = 1, func.sizelocvars do
        local locvar = func.locvars[i]
        WriteString(locvar.varname)
        WriteUnsigned(locvar.startpc)
        WriteUnsigned(locvar.endpc)
      end
    end

    -------------------------------------------------------------
    -- write upvalues information
    -------------------------------------------------------------
    local function WriteUpvalues()
      WriteUnsigned(func.sizeupvalues)
      for i = 1, func.sizeupvalues do
        local upvalue = func.upvalues[i]
        DumpByte(upvalue.instack)
        DumpByte(upvalue.idx)
      end
    end

    -------------------------------------------------------------
    -- write upvalues information
    -------------------------------------------------------------
    local function WriteUpvalueNames()
      WriteUnsigned(func.size_upvalue_names)
      for i = 1, func.size_upvalue_names do
        local upvalue = func.upvalues[i]
        WriteString(upvalue.name)
      end
    end

    -------------------------------------------------------------
    -- write constants information (data)
    -------------------------------------------------------------
    local function WriteConstantKs()
      WriteUnsigned(func.sizek)
      for i = 1, func.sizek do
        local v = func.k[i]
        if type(v) == "number" then
          DumpByte(config.LUA_TNUMBER); WriteNumber(v)
        elseif type(v) == "boolean" then
          local b = 0; if v then b = 1 end
          DumpByte(config.LUA_TBOOLEAN); DumpByte(b)
        elseif type(v) == "string" then
          DumpByte(config.LUA_TSTRING); WriteString(v)
        elseif type(v) == "nil" then
          DumpByte(config.LUA_TNIL)
        else
          error("bad constant type \""..type(v).."\" at "..i)
        end
      end--for
    end

    -------------------------------------------------------------
    -- write constants information (local functions)
    -------------------------------------------------------------
    local function WriteConstantPs()
      WriteUnsigned(func.sizep)
      for i = 1, func.sizep do WriteFunction(func.p[i]) end
    end

    -------------------------------------------------------------
    -- write function code
    -------------------------------------------------------------
    local function WriteCode()
      WriteUnsigned(func.sizecode)
      for i = 1, func.sizecode do WriteBlock(EncodeInst(func.inst[i])) end
    end

    -------------------------------------------------------------
    -- body of WriteFunction() starts here
    -------------------------------------------------------------
    WriteUnsigned(func.linedefined)
    WriteUnsigned(func.lastlinedefined)
    DumpByte(func.numparams)
    DumpByte(func.is_vararg)
    DumpByte(func.maxstacksize)
    WriteCode()
    WriteConstantKs()
    WriteConstantPs()   -- may be recursive
    WriteUpvalues()
    WriteString(func.source)
    WriteLines()        -- these are lists
    WriteLocals()
    WriteUpvalueNames()
    -- end of WriteFunction
  end

  ---------------------------------------------------------------
  -- actual call to start the function writing process
  ---------------------------------------------------------------
  WriteFunction(parsed.func)
  if not tofile then return table.concat(Buffer) end
end

--[[-------------------------------------------------------------------
-- ChunkSpy internal tests
-- * not very comprehensive at the moment, more useful during dev...
-- * to make it more chatty, set config.VERBOSE_TEST to true
--]]-------------------------------------------------------------------

function ChunkSpy_Test()
  ---------------------------------------------------------------
  -- In A.D. 2101
  -- War was beginning.
  ---------------------------------------------------------------
  local FAIL, SUCCEED = false, true
  local GotError = false
  -- force to x64_standard because test data follows this
  SetProfile("x64 standard")
  config.DISPLAY_FLAG = false           -- don't show listing
  config.AUTO_DETECT = false
  print(title)

  ---------------------------------------------------------------
  -- Captain: What happen ?
  ---------------------------------------------------------------
  local function expected(sample, outcome, errmatch, message)
    local ok, msg = pcall(ChunkSpy, "test", sample)
    -- deduce outcome
    if outcome == SUCCEED and not ok then
      print("ChunkSpy_Test: failed instead of success!\nTest was for: "..message)
      GotError = true; return
    elseif outcome == FAIL and ok then
      print("ChunkSpy_Test: success instead of failed!\nTest was for: "..message)
      GotError = true; return
    elseif outcome == FAIL and not ok then
      if not string.find(msg, errmatch, 1, 1) then
        print("ChunkSpy_Test: wrong error message returned!\nTest was for: "
              ..message.."\nError returned: "..msg)
        GotError = true; return
      end
    end
    if config.VERBOSE_TEST then
      print("ChunkSpy_Test: successful test!\nTest was for: "..message)
    end
  end

  ---------------------------------------------------------------
  -- Mechanic: Somebody set up us the bomb.
  ---------------------------------------------------------------
  expected("\0\0\0\0", FAIL,
    "header signature not found", "incorrect header signature")

  ---------------------------------------------------------------
  -- Operator: We get signal.
  -- Captain: What !
  ---------------------------------------------------------------
  expected("\27Lua\64", FAIL,
    "cannot read version", "incorrect version byte")
  expected("\27Lua\82\1", FAIL,
    "cannot read format", "incorrect format byte")

  ---------------------------------------------------------------
  -- Operator: Main screen turn on.
  -- Captain: It's you !!
  ---------------------------------------------------------------
  expected("\27Lua\82\0\0", FAIL,
    "unsupported endianness", "incorrect endianness byte")

  ---------------------------------------------------------------
  -- Cats: How are you gentlemen !!
  -- Cats: All your base are belong to us.
  -- Cats: You are on the way to destruction.
  ---------------------------------------------------------------
  expected("\27Lua\82\0\1\0\0\0\0", FAIL,
    "int size", "incorrect int size byte")
  expected("\27Lua\82\0\1\4\0\0\0", FAIL,
    "size_t size", "incorrect size_t size byte")
  expected("\27Lua\82\0\1\4\4\0\0", FAIL,
    "Instruction size", "incorrect Instruction size byte")
  expected("\27Lua\82\0\1\4\4\4\0", FAIL,
    "number size", "incorrect lua_Number size byte")

  ---------------------------------------------------------------
  -- Captain: What you say !!
  -- Cats: You have no chance to survive make your time.
  -- Cats: Ha Ha Ha Ha ....
  ---------------------------------------------------------------
  expected("\27Lua\82\0\1\4\4\4\8\1", FAIL,
    "incorrect lua_Number", "incorrect integral byte")

  ---------------------------------------------------------------
  -- Operator: Captain '!!'*
  -- Captain: Take off every 'Zig'!!
  -- Captain: You know what you doing.
  -- Captain: Move 'Zig'.
  ---------------------------------------------------------------
  local LUA_HEADER = "\27Lua\82\0\1\4\4\4\8\1"
  -- add more tests here

  ---------------------------------------------------------------
  -- Captain: For great justice.
  ---------------------------------------------------------------
  if not GotError then
    print("ChunkSpy_Test: completed simple tests without errors")
  end
end

--[[-------------------------------------------------------------------
-- ChunkSpy sample generator
-- * try to exercise the code, especially instruction decoding
-- * currently, the following opcodes are not generated:
--   SELF, SUB, MUL, DIV, POW, NOT, LT, LE, TEST, TAILCALL, CLOSE
-- * the code is nonsensical, don't try to run it!!!
-- * check correctness by visual inspection...
--]]-------------------------------------------------------------------

function ChunkSpy_Sample()
  local LUA_SAMPLE = string.dump(
    function()
      local a = 1; b = "the quick brown fox\r\n"
      function c() b = a a = b end
      c = nil; c = -a; c = not b
      for i = 1, 10 do a = a + 2 c() end
      a = {}; a[1] = false; b = a[1]
      a = d..c..b; a = b == c; a = {1,2,}
      for i in b() do b = 1 end
      return
    end
  )
  local ok, msg = pcall(ChunkSpy, "test sample", LUA_SAMPLE)
  if not ok then
    print(title)
    print("* Test sample has failed with the following error:")
    print(msg)
  end
end

--[[-------------------------------------------------------------------
-- Top-level file/chunk handling, processes user's binary chunks
--]]-------------------------------------------------------------------

function ChunkSpy_DoFiles(files)
  local binary_chunks = {}
  
  -- Search for signature used with --sign
  local function CheckAndAdd(binchunk, filename)
    if config.SearchSign then
      local i,j = string.find(binchunk, string.gsub(config.SearchSign, "(%W)", "%%%1"))
      if i then
        binary_chunks[filename] = binchunk
        config.SIGNATURE = string.sub(binchunk, 1, j)
        print(string.format("--sign=%q found at %d for %s", config.SearchSign, i-1, filename))
        return true
      end
      print(string.format("--sign=%q not found for %s", config.SearchSign, filename))
    else
      local sign = string.sub(binchunk, 1, string.len(config.SIGNATURE))
      if sign == config.SIGNATURE then
        binary_chunks[filename] = binchunk
        return true
      end
    end
    return false
  end
  ---------------------------------------------------------------
  -- pre-processing of file list
  ---------------------------------------------------------------
  for i, v in pairs(files) do
    local filename, binchunk
    if type(i) == "number" then -- normally specified files
      filename = v
      local INF = io.open(filename, "rb")
      if not INF then
        error("cannot open \""..filename.."\" for reading")
      end
      binchunk = INF:read("*a")
      io.close(INF)
    else -- binary chunk supplied by --source, after compilation
      filename = i
      binchunk = v
    end
    if binchunk then
      if not CheckAndAdd(binchunk, filename) then
        CheckLuaVersion("compiling needs %s")
        --try to compile file
        local func, msg = loadstring(binchunk, "@"..filename)
        if not func then
          print(string.format("failed to compile %s", msg))
        end
        binchunk = func and string.dump(func)
        if binchunk then
          print(string.format("success compiling %s", filename))
          binary_chunks[filename] = binchunk
        else
          -- may be a source code listing
          table.insert(other_files, filename)
        end
      end
    end
  end
  ---------------------------------------------------------------
  -- per-chunk processing
  ---------------------------------------------------------------
  local done
  for i,v in pairs(binary_chunks) do
    if done and (config.REWRITE_FLAG or config.RUN_FLAG) then
      error("can rewrite or run only one file at a time")
    end
    -- returns parsed table, for further optional processing
    local result = ChunkSpy(i, v); done = true
    -------------------------------------------------------------
    -- write out a binary chunk option
    -------------------------------------------------------------
    if config.REWRITE_FLAG then
      if not SetProfile(config.REWRITE_PROFILE) then
        error("could not load profile for writing binary chunk")
      end
      if files[i] then
        -- force --source consistency with luac output
        if string.sub(result.func.source, 1, 1) ~= "@" then
          result.func.source = "@"..result.func.source
        end
      end
      WriteBinaryChunk(result, true)
    -------------------------------------------------------------
    -- run a binary chunk option
    -- * global environment is inherited if read, quite dangerous
    -------------------------------------------------------------
    elseif config.RUN_FLAG then
      if not SetProfile("local") then
        error("could not load profile for writing binary chunk")
      end
      local binchunk = WriteBinaryChunk(result)

      local sandbox = {}
      arg_other[0] = i                      -- propagate rest of args
      _ENV.arg = arg_other
      setmetatable(sandbox, {__index = _ENV}) -- sandbox (see PIL book)

      local func, msg = load(binchunk, i, nil, sandbox) -- load
      if not func then error(msg) end

      func(table.unpack(arg_other))                                -- execute
      return
    end
  end
  if not done then
    print(title) print("ChunkSpy: no binary chunks processed!")
  end
end

--[[-------------------------------------------------------------------
-- ChunkSpy interactive mode; instant feedback!
--]]-------------------------------------------------------------------

function ChunkSpy_Interact()
  config.DISPLAY_BRIEF = true
  config.OUTPUT_FILE = nil
  local prevline, done
  print(title)
  print(interactive_help)
  while not done do
    if prevline then io.stdout:write(">>") else io.stdout:write(">") end
    io.stdout:flush()
    local l = io.stdin:read("*l")
    -------------------------------------------------------------
    if l == nil or (l == "exit" or l == "quit" and not prevline) then
      done = true
    -------------------------------------------------------------
    elseif l == "help" and not prevline then
      io.stdout:write(interactive_help, "\n")
    -------------------------------------------------------------
    -- handle line continuation
    -------------------------------------------------------------
    elseif string.sub(l, -1, -1) == "\\" then
      if not prevline then prevline = "" end
      prevline = prevline..string.sub(l, 1, -2)
    -------------------------------------------------------------
    -- compose source chunk, compile, disassemble
    -------------------------------------------------------------
    else
      if prevline then l = prevline..l; prevline = nil end
      local func, msg = loadstring(l, "(interactive mode)")
      if not func then
        print("ChunkSpy: failed to compile your input")
      else
        binchunk = string.dump(func)
        ChunkSpy("(interactive mode)", binchunk)
      end
    -------------------------------------------------------------
    end--if l
  end--while
end

--[[-------------------------------------------------------------------
-- loads a Lua source file and tries to compile it using loadstring()
-- * integration with ChunkSpy_DoFiles() is a little messy...
--]]-------------------------------------------------------------------

function CompileSourceFile(filename)
  CheckLuaVersion("compiling needs %s")
  local INF = io.open(filename, "rb")
  if not INF then
    error("cannot open \""..filename.."\" for reading")
  end
  local src = INF:read("*a")
  local func, msg = loadstring(src, "@"..filename)
  if not func then
    print(msg)
    error("failed to compile source file \""..filename.."\"")
  end
  io.close(INF)
  return string.dump(func)
end

function unescape(str)
  str = string.gsub(str, [[\(%d%d?%d?)]],
    function (c)
      local n = tonumber (c)
      if (n and 0 <= n and n < 256) then
        return string.char(n)
      else
        return '\\'..c
      end
    end)
  return str
end

--[[-------------------------------------------------------------------
-- Command-line interface
--]]-------------------------------------------------------------------

function main()
  ---------------------------------------------------------------
  -- arg[0] is not set if ChunkSpy.lua is embedded, for ChunkSpy.c
  ---------------------------------------------------------------
  local usage, exec
  if arg[0] then exec = "lua ChunkSpy.lua" else exec = "ChunkSpy" end
  usage = string.format(USAGE, exec, exec)
  ---------------------------------------------------------------
  -- check Lua version
  ---------------------------------------------------------------
  if _VERSION ~= "Lua 5.3" then
    error("this version of ChunkSpy requires Lua 5.3")
  end
  loadstring = load
  if math.ldexp == nil then
    math.ldexp = function(x, exp)
      return x * 2^exp
    end
  end
  ---------------------------------------------------------------
  -- handle arguments
  ---------------------------------------------------------------
  if not arg[1] then
    print(title) print(usage)
  else
    local i, perform, gotfile = 1
    local files = {}
    while arg[i] do
      local a, b = arg[i], arg[i + 1]
      if string.sub(a, 1, 1) == "-" then        -- handle options here
        ---------------------------------------------------------
        if a == "--test" then
          perform = ChunkSpy_Test
        elseif a == "--sample" then
          perform = ChunkSpy_Sample
        elseif a == "-h" or a == "--help" then
          print(title) print(usage) return
        elseif a == "--stats" then
          config.STATS = true
        elseif a == "--auto" then
          config.AUTO_DETECT = true
        elseif a == "--brief" then
          config.DISPLAY_BRIEF = true
        elseif a == "--interact" then
          CheckLuaVersion("--interact needs %s")
          perform = ChunkSpy_Interact
        ---------------------------------------------------------
        elseif a == "-o" or a == "--source" then
          if not b then error("-o option needs a file name") end
          if a == "-o" then
            config.OUTPUT_FILE = b
          else
            local binchunk = CompileSourceFile(b)
            if binchunk then files[b] = binchunk; gotfile = true end
          end
          i = i + 1
        ---------------------------------------------------------
        elseif a == "--sign" then
          config.SearchSign = config.SIGNATURE
          config.IgnoreVersion = true
          config.IgnoreFormat = true
        elseif string.sub(a, 1, string.len("--sign:")) == "--sign:" then
          config.SearchSign = string.sub(a, string.len("--sign:")+1)
          config.IgnoreVersion = true
          config.IgnoreFormat = true
        ---------------------------------------------------------
        elseif a == "--rewrite" then
          if not b then error("--rewrite option needs a profile name") end
          if b == "local" then
            CheckLuaVersion("--rewrite local needs %s")
          end
          config.DISPLAY_FLAG = false
          config.REWRITE_FLAG = true
          if b == "local" or CONFIGURATION[b] then
            config.REWRITE_PROFILE = b
          else
            error("specified profile \""..b.."\"not found")
          end
          i = i + 1
        ---------------------------------------------------------
        elseif a == "--run" then
          CheckLuaVersion("--run needs %s")
          config.DISPLAY_FLAG = false
          config.AUTO_DETECT = true
          config.RUN_FLAG = true
        ---------------------------------------------------------
        elseif a == "--" then
          local j = i + 1
          while arg[j] do
            table.insert(arg_other, arg[j])     -- gather rest of args
            j = j + 1
          end
          break
        ---------------------------------------------------------
        else
          error("unrecognized option "..a)
        end--if a
        ---------------------------------------------------------
      else
        table.insert(files, a); gotfile = true  -- potential filename
      end--if == "-"
      i = i + 1
    end--while
    -------------------------------------------------------------
    -- make calls to perform functions
    -- * OutputInit() and OutputExit() brackets listing output
    -------------------------------------------------------------
    OutputInit()
    if perform then
      perform()
    elseif gotfile then
      ChunkSpy_DoFiles(files)
    else
      print(title)
      print("ChunkSpy: nothing to do!")
    end
    OutputExit()
  end
end

-----------------------------------------------------------------------
-- program entry point
-----------------------------------------------------------------------
ok, msg = pcall(main)           -- call main() for proper traceback
if not ok then                  -- error
  print(title)
  print("* Run with option -h or --help for usage information")
  print(msg)
end

----end-of-script----
