--------------------------------------------------------------------------------
-- ZCPU utility & support code
--------------------------------------------------------------------------------
local INVALID_BREAKPOINT_IP = 2e7

CPULib = CPULib or {}
if CLIENT or TESTING then
  -- Sourcecode available as compiled binary
  CPULib.Source = ""
  -- Compiled binary
  CPULib.Buffer = {}

  -- Sourcecode currently being compiled
  CPULib.CurrentSource = ""
  -- Buffer currently being written
  CPULib.CurrentBuffer = {}

  -- State variables
  CPULib.Compiling = false
  CPULib.Uploading = false
  CPULib.ServerUploading = false

  -- Debugger
  CPULib.DebuggerAttached = false
  CPULib.Debugger = {}
  CPULib.Debugger.Variables = {}
  CPULib.Debugger.SourceTab = nil

  -- Reset on recompile
  CPULib.Debugger.MemoryVariableByIndex = {}
  CPULib.Debugger.MemoryVariableByName = {}
  CPULib.Debugger.Labels = {}
  CPULib.Debugger.PositionByPointer = {}
  CPULib.Debugger.PointersByLine = {}
  CPULib.Debugger.PreferredTracker = "IP"
  CPULib.Debugger.PreferredTrackerFriendlyName = "IP"
  CPULib.Debugger.FollowTracker = true

  CPULib.Debugger.Breakpoint = {}

  -- Convars to control CPULib
  local wire_cpu_upload_speed = CreateClientConVar("wire_cpu_upload_speed",1000,false,false)
  local wire_cpu_compile_speed = CreateClientConVar("wire_cpu_compile_speed",256,false,false)
  local wire_cpu_show_all_registers = CreateClientConVar("wire_cpu_show_all_registers",0,false,false)

  ------------------------------------------------------------------------------
  -- Request compiling specific sourcecode
  function CPULib.Compile(source,fileName,successCallback,errorCallback,targetPlatform,sourceDirectory)
    -- Stop any compile/upload process that is running right now
    timer.Remove("cpulib_compile")
    timer.Remove("cpulib_upload")
    CPULib.Uploading = false

    -- See if compiled source is available
    --if CPULib.Source == source then
    --  successCallback()
    --  return
    --end

    -- Remember the sourcecode being compiled
    CPULib.CurrentSource = source
    CPULib.CurrentBuffer = {}

    -- Clear debugging info
    CPULib.Debugger.MemoryVariableByIndex = {}
    CPULib.Debugger.MemoryVariableByName = {}
    CPULib.Debugger.Labels = {}
    CPULib.Debugger.PositionByPointer = {}
    CPULib.Debugger.PointersByLine = {}
    CPULib.CPUName = nil
    CPULib.Warnings = HCOMP:StartCompile(source,fileName or "source",CPULib.OnWriteByte,nil)

    -- Start compiling the sourcecode
    HCOMP.Settings.CurrentPlatform = targetPlatform or "CPU"
    HCOMP.Location = sourceDirectory or HCOMP.Settings.CurrentPlatform .. "chip"
    print("=== HL-ZASM High Level Assembly Compiler Output ==")

    -- Initialize callbacks
    CPULib.SuccessCallback = successCallback
    CPULib.ErrorCallback = errorCallback

    -- Run the timer
    timer.Create("cpulib_compile",1/60,0,CPULib.OnCompileTimer)
    CPULib.Compiling = true
  end

  ------------------------------------------------------------------------------
  -- Make sure the file is opened in the tab
  function CPULib.SelectTab(editor,fileName,dontForceChange)
    if not editor then return end
    local editorType = string.lower(editor.EditorType)
    local fullFileName
    if string.match(fileName,"^"..editor.Location) then
      fullFileName = fileName
    else
      fullFileName = editor.Location.."/"..fileName
    end

    local currentTab = editor:GetActiveTabIndex()
    local sourceTab
    for tab=1,editor:GetNumTabs() do
      if editor:GetEditor(tab).chosenfile == fullFileName then
        sourceTab = tab
      end
    end

    if not sourceTab then
      editor:LoadFile(fullFileName,true)
      sourceTab = editor:GetActiveTabIndex()
      if dontForceChange then
        editor:SetActiveTab(currentTab)
      end
    else
      if not dontForceChange then
        editor:SetActiveTab(sourceTab)
      end
    end

    return editor:GetEditor(sourceTab),sourceTab
  end

  ------------------------------------------------------------------------------
  -- Request validating the code
  function CPULib.Validate(editor,source,fileName)
    CPULib.Compile(source,fileName,
      function(warnings)
        if #warnings > 0 then
        editor.C.Val:Update(nil,warnings,"   "..#warnings.." Warnings, with "..(HCOMP.WritePointer or "?").." bytes compiled.", Color(163, 130, 64, 255))
        else
        editor.C.Val:Update(nil, nil, "   Success, "..(HCOMP.WritePointer or "?").." bytes compiled.", Color(50, 128, 20))
        end
      end,
      function(error,errorPos)
        local issue = (error or "unknown error")

        local line, char = 0, 0

        if errorPos then 
          line = errorPos.Line
          char = errorPos.Col

          local textEditor = CPULib.SelectTab(editor,errorPos.File)
          if not textEditor then return end
          textEditor:SetCaret({errorPos.Line,errorPos.Col})
        end

        editor.C.Val:Update({{message = issue, line = line, char = char}}, nil, issue, Color(128, 20, 50))
      end,editor.EditorType,editor.Location)
  end

  ------------------------------------------------------------------------------
  -- Compiler callback
  function CPULib.OnWriteByte(caller,address,byte)
    CPULib.CurrentBuffer[address] = byte
  end

  ------------------------------------------------------------------------------
  -- Compiler timer
  function CPULib.OnCompileTimer()
    local compile_speed = wire_cpu_compile_speed:GetFloat()

    for _ = 1, compile_speed do
      local status,result = pcall(HCOMP.Compile,HCOMP)
      if not status then
        print("==================================================")
        if CPULib.ErrorCallback then CPULib.ErrorCallback(HCOMP.ErrorMessage or ("Internal error: "..result),HCOMP.ErrorPosition) end
        timer.Remove("cpulib_compile")
        CPULib.Compiling = false

        return
      elseif not result then
        print("==================================================")
        CPULib.Source = CPULib.CurrentSource
        CPULib.Buffer = CPULib.CurrentBuffer

        if CPULib.SuccessCallback then
          CPULib.Debugger.Labels = HCOMP.DebugInfo.Labels
          CPULib.Debugger.PositionByPointer = HCOMP.DebugInfo.PositionByPointer
          CPULib.Debugger.PointersByLine = HCOMP.DebugInfo.PointersByLine

          CPULib.SuccessCallback(CPULib.Warnings)
        end
        timer.Remove("cpulib_compile")
        CPULib.Compiling = false

        return
      end
    end
  end


  ------------------------------------------------------------------------------
  -- Uploader timer
  function CPULib.OnUploadTimer()
    if not CPULib.RemainingData then return end

    local upload_speed = wire_cpu_upload_speed:GetFloat() -- Number of index/value pairs to send (11 bytes each)
    if game.SinglePlayer() then upload_speed = 5000 end

    local iters = math.min(upload_speed, CPULib.RemainingUploadData)
    net.Start("wire_cpulib_buffer")
    net.WriteUInt(iters, 16)
    for _ = 1, iters do
      local index,value = next(CPULib.RemainingData)
      CPULib.RemainingUploadData = CPULib.RemainingUploadData - 1
      net.WriteUInt(index, 24)
      net.WriteDouble(value or 0) -- 64bits, in case theres any float literals. Int21 is sufficient for all function calls/memory addresses
      CPULib.RemainingData[index] = nil
    end
    if CPULib.RemainingUploadData <= 0 then
      timer.Remove("cpulib_upload")
      net.WriteBit(true) -- End
      CPULib.Uploading = false
    else
      net.WriteBit(false) -- Keep going
    end
    net.SendToServer()
  end

  ------------------------------------------------------------------------------
  -- Start upload
  function CPULib.Upload(customBuffer)
    -- Stop any upload in the progress
    timer.Remove("cpulib_upload")

    -- Send the buffer over to server
    net.Start("wire_cpulib_bufferstart") net.WriteString(CPULib.CPUName or "") net.SendToServer()

    CPULib.TotalUploadData = 0
    CPULib.RemainingData = {}
    if customBuffer then
      for k,v in pairs(customBuffer) do
        CPULib.RemainingData[k] = v
        CPULib.TotalUploadData = CPULib.TotalUploadData + 1
      end
    else
      for k,v in pairs(CPULib.Buffer) do
        CPULib.RemainingData[k] = v
        CPULib.TotalUploadData = CPULib.TotalUploadData + 1
      end
    end

    CPULib.RemainingUploadData = CPULib.TotalUploadData
    timer.Create("cpulib_upload",0.5,0,CPULib.OnUploadTimer)
    CPULib.Uploading = true
  end

  ------------------------------------------------------------------------------
  -- Get debug text for specific variable/function name
  function CPULib.GetDebugPopupText(var)
    if not var then return "" end
    local csvar = string.upper(var)

    if CPULib.Debugger.Variables[csvar] then
      return var.." = "..CPULib.Debugger.Variables[csvar]
    else
      if CPULib.Debugger.Labels[csvar] then
        if CPULib.Debugger.Labels[csvar].Offset then
          if not CPULib.Debugger.MemoryVariableByName[csvar] then
            CPULib.Debugger.MemoryVariableByName[csvar] = CPULib.Debugger.Labels[csvar].Offset
            table.insert(CPULib.Debugger.MemoryVariableByIndex,csvar)
            RunConsoleCommand("wire_cpulib_debugvar",#CPULib.Debugger.MemoryVariableByIndex,CPULib.Debugger.Labels[csvar].Offset)
          end
          return var.." = ..."
        elseif CPULib.Debugger.Labels[csvar].Pointer then
          return var.." = ptr "..CPULib.Debugger.Labels[csvar].Pointer
        else
          return var.." = cannot resolve"
        end
      end
    end
  end

  ------------------------------------------------------------------------------
  -- Get debug text for specific variable/function name
  CPULib.InterruptText = nil
  function CPULib.GetDebugWindowText()
    local result = {
      "EAX = "..(CPULib.Debugger.Variables.EAX or "#####"),
      "EBX = "..(CPULib.Debugger.Variables.EBX or "#####"),
      "ECX = "..(CPULib.Debugger.Variables.ECX or "#####"),
      "EDX = "..(CPULib.Debugger.Variables.EDX or "#####"),
      "ESI = "..(CPULib.Debugger.Variables.ESI or "#####"),
      "EDI = "..(CPULib.Debugger.Variables.EDI or "#####"),
      "EBP = "..(CPULib.Debugger.Variables.EBP or "#####"),
      "ESP = "..(CPULib.Debugger.Variables.ESP or "#####"),
    }

    table.insert(result,"")
    local maxReg = 7
    if wire_cpu_show_all_registers:GetFloat() == 1 then maxReg = 31 end

    for reg=0,maxReg do
      table.insert(result,"R"..reg.." = "..(CPULib.Debugger.Variables["R"..reg] or "#####"))
    end


    table.insert(result,"")
    if CPULib.Debugger.Variables.IP == INVALID_BREAKPOINT_IP then
      table.insert(result,"IP = #####")
    else
      table.insert(result,"IP = "..(CPULib.Debugger.Variables.IP or "#####"))
    end
    if CPULib.Debugger.Variables.IP == INVALID_BREAKPOINT_IP then
      table.insert(result,"CS:IP = #####")
    else
      table.insert(result,"CS:IP = "..((CPULib.Debugger.Variables.CSIP) or "#####"))
    end
    if CPULib.Debugger.Variables.IP == INVALID_BREAKPOINT_IP then
      table.insert(result,"PHYS IP = #####")
    else
      table.insert(result,"PHYS IP = "..(CPULib.Debugger.Variables.PHYSIP or "#####"))
    end
    table.insert(result,"Following "..CPULib.Debugger.PreferredTrackerFriendlyName)
    if CPULib.InterruptText then
      table.insert(result,"")
      table.insert(result,CPULib.InterruptText)
    end

    return result
  end

  ------------------------------------------------------------------------------
  -- Invalidate debugger data
  function CPULib.InvalidateDebugger()
    CPULib.InterruptText = nil
    CPULib.Debugger.MemoryVariableByIndex = {}
    CPULib.Debugger.MemoryVariableByName = {}
    CPULib.Debugger.Breakpoint = {}
    CPULib.Debugger.Variables = {}
    CPULib.Debugger.FirstFile = nil
    CPULib.DebugUpdateHighlights(not CPULib.Debugger.FollowTracker)
  end

  net.Receive("CPULib.InvalidateDebugger", function(netlen)
    local state = net.ReadUInt(2) -- 0: No change just invalidate, 1: detach, 2: attach
    if state == 1 then
      CPULib.DebuggerAttached = false
      GAMEMODE:AddNotify("CPU debugger detached!",NOTIFY_GENERIC,7)
    elseif state == 2 then
      CPULib.DebuggerAttached = true
      GAMEMODE:AddNotify("CPU debugger has been attached!",NOTIFY_GENERIC,7)
    end
    CPULib.InvalidateDebugger()
  end)

  -- Get breakpoint at line
  function CPULib.GetDebugBreakpoint(fileName,caretPos)
    if not fileName or not caretPos then return nil end
    return CPULib.Debugger.Breakpoint[caretPos[1]..":"..fileName]
  end

  -- Set breakpoint at line
  -- FIXME: bug: can only set breakpoints in one file
  function CPULib.SetDebugBreakpoint(fileName,caretPos,condition)
    if not fileName or not caretPos then return nil end
    if not condition then
      CPULib.Debugger.Breakpoint[caretPos[1]..":"..fileName] = nil
      if CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName] then
        RunConsoleCommand("wire_cpulib_debugbreakpoint",CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName][1],0)
      end
    else
      if CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName] then
        CPULib.Debugger.Breakpoint[caretPos[1]..":"..fileName] = condition
        RunConsoleCommand("wire_cpulib_debugbreakpoint",CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName][1],condition)
      end
    end

    CPULib.DebugUpdateHighlights(true)
  end

  -- Update highlighted lines
  function CPULib.DebugUpdateHighlights(dontForcePosition)
    if ZCPU_Editor then
      -- Clear all highlighted lines
      for tab=1,ZCPU_Editor:GetNumTabs() do
        ZCPU_Editor:GetEditor(tab):ClearHighlightedLines()
      end

      -- Highlight current position
      local currentPosition = CPULib.Debugger.PositionByPointer[CPULib.Debugger.Variables[CPULib.Debugger.PreferredTracker or "IP"]]
      if currentPosition then
        -- Check if currentposition is even in the correct editor first, when switching editors CPULib's debugging info switches
        -- to the file in the new editor.
        if not currentPosition.File:sub(1,9):match("cpuchip/") then
          return
        end
        
        local textEditor = CPULib.SelectTab(ZCPU_Editor,currentPosition.File,not CPULib.Debugger.FollowTracker)
        if textEditor then
          textEditor:HighlightLine(currentPosition.Line,130,0,0,255)
          if not dontForcePosition then
            textEditor:SetCaret({currentPosition.Line,1}) --currentPosition.Col
          end
        end
      end

      -- Highlight breakpoints
      for key,breakpoint in pairs(CPULib.Debugger.Breakpoint) do
        local line = tonumber(string.sub(key,1,(string.find(key,":") or 0) - 1)) or 0
        local file = string.sub(key,  (string.find(key,":") or 0) + 1)

        local textEditor = CPULib.SelectTab(ZCPU_Editor,file)
        if textEditor then
          if currentPosition and (currentPosition.Line == line) then
            if breakpoint == true then
              textEditor:HighlightLine(line,130,70,20,255)
            else
              textEditor:HighlightLine(line,130,20,70,255)
            end
          else
            if breakpoint == true then
              textEditor:HighlightLine(line,0,70,20,255)
            else
              textEditor:HighlightLine(line,0,20,70,255)
            end
          end
        end
      end
    end
  end

  ------------------------------------------------------------------------------
  -- Debug data arrived from server
  function CPULib.OnDebugData_Registers(um)
    CPULib.Debugger.Variables.CS       = um:ReadFloat()
    CPULib.Debugger.Variables.IP       = um:ReadFloat()
    CPULib.Debugger.Variables.PHYSPAGE = um:ReadFloat()
    CPULib.Debugger.Variables.EAX      = um:ReadFloat()
    CPULib.Debugger.Variables.EBX      = um:ReadFloat()
    CPULib.Debugger.Variables.ECX      = um:ReadFloat()
    CPULib.Debugger.Variables.EDX      = um:ReadFloat()
    CPULib.Debugger.Variables.ESI      = um:ReadFloat()
    CPULib.Debugger.Variables.EDI      = um:ReadFloat()
    CPULib.Debugger.Variables.EBP      = um:ReadFloat()
    CPULib.Debugger.Variables.ESP      = um:ReadFloat()
    CPULib.Debugger.Variables.CSIP     = CPULib.Debugger.Variables.CS + CPULib.Debugger.Variables.IP
    CPULib.Debugger.Variables.PHYSIP   = CPULib.Debugger.Variables.PHYSPAGE*128 + CPULib.Debugger.Variables.CSIP%128
    for reg=0,31 do
      CPULib.Debugger.Variables["R"..reg]  = um:ReadFloat()
    end

    CPULib.DebugUpdateHighlights(not CPULib.Debugger.FollowTracker)
  end
  usermessage.Hook("cpulib_debugdata_registers", CPULib.OnDebugData_Registers)

  function CPULib.OnDebugData_Variables(um)
    local startIndex = um:ReadShort()
    for varIdx = startIndex,startIndex+59 do
      if CPULib.Debugger.MemoryVariableByIndex[varIdx] then
        CPULib.Debugger.Variables[CPULib.Debugger.MemoryVariableByIndex[varIdx]] = um:ReadFloat()
      end
    end
  end
  usermessage.Hook("cpulib_debugdata_variables", CPULib.OnDebugData_Variables)

  function CPULib.OnDebugData_Interrupt(um)
    local interruptNo,interruptParameter = um:ReadFloat(),um:ReadFloat()
    CPULib.InterruptText = "Error #"..interruptNo.. " ["..interruptParameter.."]"
  end
  usermessage.Hook("cpulib_debugdata_interrupt", CPULib.OnDebugData_Interrupt)

  ------------------------------------------------------------------------------
  -- Show ZCPU/ZGPU documentation
  CPULib.HandbookWindow = nil
  -- Platform bookmarks, it'll automatically jump to this section when opening.
  local bookmarks = {
    ZCPU="",
    ZGPU="#zgpu",
    ZSPU="#zspu"
  }
  function CPULib.ShowDocumentation(bookmark)
    local w = ScrW() * 2/3
    local h = ScrH() * 2/3
    local browserWindow = vgui.Create("DFrame")
    browserWindow:SetTitle("Documentation")
    browserWindow:SetPos((ScrW() - w)/2, (ScrH() - h)/2)
    browserWindow:SetSize(w,h)
    browserWindow:MakePopup()

    local browser = vgui.Create("DHTML",browserWindow)
    browser:SetPos(10, 25)
    browser:SetSize(w - 20, h - 35)

    browser:OpenURL("https://wiremod.github.io/Miscellaneous/zcpudoc.html"..(bookmarks[bookmark] or ""))
  end
end







if SERVER then
  util.AddNetworkString("CPULib.ServerUploading")
  ------------------------------------------------------------------------------
  -- Data received from server
  CPULib.DataBuffer = {}

  ------------------------------------------------------------------------------
  -- Set this entity as a receiver for networked upload
  function CPULib.SetUploadTarget(entity,player)
    CPULib.DataBuffer[player:UserID()] = {
      Entity = entity,
      Player = player,
      Data = {},
    }
  end

  util.AddNetworkString("wire_cpulib_bufferstart")
  net.Receive("wire_cpulib_bufferstart", function(netlen, player)
    local Buffer = CPULib.DataBuffer[player:UserID()]
    if (not Buffer) or (Buffer.Player ~= player) then return end
    if not IsValid(Buffer.Entity) then return end

    net.Start("CPULib.ServerUploading") net.WriteBit(true) net.Send(player)
    if Buffer.Entity:GetClass() == "gmod_wire_cpu" then
      Buffer.Entity:SetCPUName(net.ReadString())
    end
  end)

  -- Concommand to send a single stream of bytes
  util.AddNetworkString("wire_cpulib_buffer")
  net.Receive("wire_cpulib_buffer", function(netlen, player)
    local Buffer = CPULib.DataBuffer[player:UserID()]
    if (not Buffer) or (Buffer.Player ~= player) then return end
    if not IsValid(Buffer.Entity) then return end

    for _ = 1, net.ReadUInt(16) do
      Buffer.Data[net.ReadUInt(24)] = net.ReadDouble()
    end

    if net.ReadBit() ~= 0 then -- We're done!
      CPULib.DataBuffer[player:UserID()] = nil
      net.Start("CPULib.ServerUploading") net.WriteBit(false) net.Send(player)

      if Buffer.Entity:GetClass() == "gmod_wire_cpu" then
        Buffer.Entity:FlashData(Buffer.Data)
      elseif Buffer.Entity:GetClass() == "gmod_wire_dhdd" then
        for k,v in pairs(Buffer.Data) do
          Buffer.Entity.Memory[k] = v
        end
        Buffer.Entity:ShowOutputs()
      elseif (Buffer.Entity:GetClass() == "gmod_wire_gpu") or (Buffer.Entity:GetClass() == "gmod_wire_spu") then
        Buffer.Entity:WriteCell(65535,0)
        if Buffer.Entity.WriteCell then
          for k,v in pairs(Buffer.Data) do
            Buffer.Entity:WriteCell(k,v)
          end
        end
        Buffer.Entity:WriteCell(65535,Buffer.Entity.Clk)
        Buffer.Entity:WriteCell(65534,1)
      else
        if Buffer.Entity.WriteCell then
          for k,v in pairs(Buffer.Data) do
            Buffer.Entity:WriteCell(k,v)
          end
        end
      end
    end
  end)

  ------------------------------------------------------------------------------
  -- Players and corresponding entities (for the debugger)
  CPULib.DebuggerData = {}


  ----------------------------------------------------------------------
  -- Detach debugger
  function CPULib.DetachDebugger(player)
    if not IsValid(player) then return end

    local data = CPULib.DebuggerData[player:UserID()]
    if not data then return end

    local ent = data.Entity
    if IsValid(ent) and ent.VM then
      ent.BreakpointInstructions = nil
      ent.OnBreakpointInstruction = nil
      ent.OnVMStep = nil


      if ent.VM.BaseJump then
        ent.VM.Jump = ent.VM.BaseJump
        ent.VM.BaseJump = nil
      end

      if ent.VM.BaseInterrupt then
        ent.VM.Interrupt = ent.VM.BaseInterrupt
        ent.VM.BaseInterrupt = nil
      end
      
      if ent._CPULibDebuggerHooked then
        ent._CPULibDebuggerHooked = nil
        
        if ent._CPULibOriginalOnRemove then
          ent.OnRemove = ent._CPULibOriginalOnRemove
          ent._CPULibOriginalOnRemove = nil
        else
          ent.OnRemove = nil
        end
      end
    end

    CPULib.DebuggerData[player:UserID()] = nil
  end


  ----------------------------------------------------------------------
  -- Attach a debugger
  function CPULib.AttachDebugger(entity, player)
    if not IsValid(player) then return end

    -- DETACH PATH
    if not entity then
      CPULib.DetachDebugger(player)
      return
    end

    if not IsValid(entity) or not entity.VM then return end

    
    --// detach any existing debugger first
    CPULib.DetachDebugger(player)

  
    if not entity._CPULibDebuggerHooked then
      entity._CPULibDebuggerHooked = true

      entity._CPULibOriginalOnRemove = entity.OnRemove
      
      entity.OnRemove = function(ent)
        if entity._CPULibOriginalOnRemove then
          entity._CPULibOriginalOnRemove(ent)
        end

        if IsValid(player) then
          if not CPULib.DebuggerData[player:UserID()] then return end 
          
          CPULib.DetachDebugger(player)

          net.Start("CPULib.InvalidateDebugger")
            net.WriteUInt(1, 2) --// 1 = detach
          net.Send(player)
        end
      end
    end

    entity.BreakpointInstructions = {}

    entity.OnBreakpointInstruction = function(IP)
      local data = CPULib.DebuggerData[player:UserID()]
      if not data then return end

      CPULib.SendDebugData(entity.VM, data.MemPointers, player)
    end

    entity.OnVMStep = function()
      local data = CPULib.DebuggerData[player:UserID()]
      if not data then return end

      if CurTime() - data.PreviousUpdateTime > 0.2 then
        data.PreviousUpdateTime = CurTime()

        -- Send a fake update that messes up line pointer, updates registers
        local tempIP = entity.VM.IP
        entity.VM.IP = INVALID_BREAKPOINT_IP
        CPULib.SendDebugData(entity.VM, nil, player)
        entity.VM.IP = tempIP
      end
    end

    if not entity.VM.BaseJump then
      entity.VM.BaseJump = entity.VM.Jump
      entity.VM.Jump = function(VM, IP, CS)
        VM:BaseJump(IP, CS)
        entity.ForceLastInstruction = true
      end

      entity.VM.BaseInterrupt = entity.VM.Interrupt
      entity.VM.Interrupt = function(VM, interruptNo, interruptParameter, isExternal, cascadeInterrupt)
        VM:BaseInterrupt(interruptNo, interruptParameter, isExternal, cascadeInterrupt)

        if interruptNo < 27 then
          CPULib.DebugLogInterrupt(player, interruptNo, interruptParameter, isExternal, cascadeInterrupt)

          local data = CPULib.DebuggerData[player:UserID()]
          if data then
            CPULib.SendDebugData(entity.VM, data.MemPointers, player)
          end
        end
      end
    end

    CPULib.DebuggerData[player:UserID()] = {
      Entity = entity,
      Player = player,
      MemPointers = {},
      PreviousUpdateTime = CurTime(),
    }
  end


  -- Log debug interrupt
  function CPULib.DebugLogInterrupt(player,interruptNo,interruptParameter,isExternal,cascadeInterrupt)
    local umsgrp = RecipientFilter()
    umsgrp:AddPlayer(player)

    umsg.Start("cpulib_debugdata_interrupt", umsgrp)
      umsg.Float(interruptNo)
      umsg.Float(interruptParameter)
    umsg.End()
  end

  -- Send debug log entry to client
  function CPULib.SendDebugLogEntry(player,text)
--
  end

  -- Send debugging data to client
  function CPULib.SendDebugData(VM,MemPointers,Player,onlyMemPointers)
    local umsgrp = RecipientFilter()
    umsgrp:AddPlayer(Player)

    if not onlyMemPointers then
      umsg.Start("cpulib_debugdata_registers", umsgrp)
        umsg.Float(VM.CS)
        umsg.Float(VM.IP)
        if(VM.CurrentPage.Remapped == 1) then
          umsg.Float(VM.CurrentPage.MappedIndex)
        else
          umsg.Float(math.floor((VM.CS+VM.IP)/128))
        end
        umsg.Float(VM.EAX)
        umsg.Float(VM.EBX)
        umsg.Float(VM.ECX)
        umsg.Float(VM.EDX)
        umsg.Float(VM.ESI)
        umsg.Float(VM.EDI)
        umsg.Float(VM.EBP)
        umsg.Float(VM.ESP)

        for reg = 0,31 do
          umsg.Float(VM["R"..reg])
        end
      umsg.End()
    end

    if MemPointers then
      for msgIdx=0,math.floor(#MemPointers/60) do
        umsg.Start("cpulib_debugdata_variables", umsgrp)
          umsg.Short(msgIdx*60)
          for varIdx=msgIdx*60,msgIdx*60+59 do
            if MemPointers[varIdx] then
              umsg.Float(VM:ReadCell(MemPointers[varIdx]))
            end
          end
        umsg.End()
      end
    end
  end

  -- Concommand to step forward
  concommand.Add("wire_cpulib_debugstep", function(player, command, args)
    local Data = CPULib.DebuggerData[player:UserID()]
    if (not Data) or (Data.Player ~= player) then return end
    if not IsValid(Data.Entity) then return end

    if not args[1] then -- Step forward
      Data.Entity.VM:Step(1)
      Data.Entity.VMStopped = true
    else -- Run until instruction
      Data.Entity.VMStopped = false
      Data.Entity:NextThink(CurTime())

      Data.Entity.LastInstruction = tonumber(args[1]) or 0
      Data.Entity.OnLastInstruction = function()
        Data.Entity.LastInstruction = nil
        Data.Entity.OnLastInstruction = nil
        CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player)
      end
    end
    CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player)
  end)

  -- Concommand to run till breakpoint
  concommand.Add("wire_cpulib_debugrun", function(player, command, args)
    local Data = CPULib.DebuggerData[player:UserID()]
    if (not Data) or (Data.Player ~= player) then return end
    if not IsValid(Data.Entity) then return end

    -- Send a fake update that messes up line pointer
    local tempIP = Data.Entity.VM.IP
    Data.Entity.VM.IP = INVALID_BREAKPOINT_IP
    CPULib.SendDebugData(Data.Entity.VM,nil,Data.Player)
    Data.Entity.VM.IP = tempIP

    Data.Entity.Clk = true
    Data.Entity:NextThink(CurTime())
  end)

  -- Concommand to reset
  concommand.Add("wire_cpulib_debugreset", function(player, command, args)
    local Data = CPULib.DebuggerData[player:UserID()]
    if (not Data) or (Data.Player ~= player) then return end
    if not IsValid(Data.Entity) then return end

    Data.Entity.VM:Reset()
    CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player)
  end)

  -- Concommand to add a variable
  concommand.Add("wire_cpulib_debugvar", function(player, command, args)
    local Data = CPULib.DebuggerData[player:UserID()]
    if (not Data) or (Data.Player ~= player) then return end
    if not IsValid(Data.Entity) then return end

    Data.MemPointers[tonumber(args[1]) or 0] = tonumber(args[2])
    CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player,true)
  end)

  -- Concommand to set a debug breakpoint
  concommand.Add("wire_cpulib_debugbreakpoint", function(player, command, args)
    local Data = CPULib.DebuggerData[player:UserID()]
    if (not Data) or (Data.Player ~= player) then return end
    if not IsValid(Data.Entity) then return end

    if tonumber(args[2]) == 0 then
      Data.Entity.BreakpointInstructions[tonumber(args[1]) or 0] = nil
    else
      Data.Entity.BreakpointInstructions[tonumber(args[1]) or 0] = true
    end
  end)
end


--------------------------------------------------------------------------------
-- Create a new virtual machine
--------------------------------------------------------------------------------
function CPULib.VirtualMachine()
  -- Create new instance of the VM
  include("wire/zvm/zvm_core.lua")

  -- Remove from global scope
  local newVM = ZVM
  ZVM = nil
  return newVM
end


--------------------------------------------------------------------------------
-- Generate a serial number
--------------------------------------------------------------------------------
local sessionBase, sessionDate
function CPULib.GenerateSN(entityType)
  local currentDate = os.date("*t")

  local SNDate = (currentDate.year-2007)*500+(currentDate.yday)
  if (not sessionBase) or (SNDate ~= sessionDate) then
    sessionBase = math.floor(math.random()*99999)
    sessionDate = SNDate
  else
    sessionBase = sessionBase + 1
  end

  if entityType == "CPU" then
    return sessionBase + 100000 + SNDate*1000000
  elseif entityType == "SPU" then
    return sessionBase + 200000 + SNDate*1000000
  elseif entityType == "GPU" then
    return sessionBase + 300000 + SNDate*1000000
  elseif entityType == "UNK" then
    return sessionBase + 700000 + SNDate*1000000
  end
end

--------------------------------------------------------------------------------
-- Columns in the instruction set reference table:
--  Opc - Instruction number
--  Mnemonic - Symbolic mnemonic (uppercase). Can be "RESERVED"
--  Ops - Number of operands
--  Version - Minimum CPU version required
--  Flags - Several or none of the following flags:
--    W1: single-operand opcode which writes 1st operand
--    R0: runlevel 0 opcode (privileged opcode)
--    OB: obsolete/should not be used
--    UB: unconditional branching instruction
--    CB: conditional branching instruction
--    TR: trigonometric syntax operand
--    OL: old mnemonic for the instruction
--    BL: instruction supports block prefix
--    PR: privileged opcode but with custom handler(for example, if it needs to get a value to write to an operand)
--  Op1 - operand 1 name
--  Op2 - operand 2 name

-- Possible operand names:
-- X,Y: arbitrary integer or floating-point value
-- PTR: 48-bit pointer into memory
-- CS: 48-bit pointer into memory, new value of CS segment
-- IDX: unsigned integer index into internal processor table
-- PAGE: unsigned integer page number (each page is 128 bytes)
-- PORT: unsigned 47-bit integer port number
-- BIT: integer between 0 and 47
-- INTR: interrupt nubmer (integer between 0 and 255)
-- SIZE: unsigned 47-bit integer memory block size
--
-- COLOR: 4-byte color
-- VEC2F: 2-byte vector
--
-- INT: 48-bit signed integer

if CLIENT then
  timer.Simple(1,function()
    if E2Helper.Modes then
      -- If the version of wiremod is too old for E2Helper modules we should be fine since it's probably
      -- the one with the hardcoded ZCPU support, we just miss out on seeing extension stuff.
      E2Helper.Modes.ZASM = nil -- Erase existing mode
      CPULib.E2HelperMode = E2Helper:RegisterMode("ZASM")
      local helperModule = CPULib.E2HelperMode

      local helperDesc = helperModule.Descriptions
      local helperInst = helperModule.Items
      helperModule.ModeSetup = function(panel)
        panel.FunctionColumn:SetName("Instruction")
        panel.FunctionColumn:SetWidth(126)
        panel.FromColumn:SetName("From") -- This can be the extension name now
        panel.FromColumn:SetWidth(80)
        panel.TakesColumn:SetName("Takes")
        panel.TakesColumn:SetWidth(60)
        panel.ReturnsColumn:SetName("Platform")
        panel.ReturnsColumn:SetWidth(60)
        panel.CostColumn:SetName("Opcode")
        panel.CostColumn:SetWidth(40)
      end
      -- Divran's instruction generator from the original e2helper implementation.
      local function AddCPUDesc(FuncName, Args, Desc, Platform, Type)
        table.insert(helperInst, { [1] = FuncName, [2] = "ZASM", [3] = Args, [4] = Platform, [5] = Type })
        helperDesc[FuncName] = Desc
      end
      
      -- Add help on all opcodes
      for _, instruction in ipairs(CPULib.InstructionTable) do
        if (instruction.Mnemonic ~= "RESERVED") and
          (not instruction.Obsolete) then
          local instructionArgs = instruction.Operand1
          if instruction.Operand2 ~= "" then
            instructionArgs = instructionArgs .. ", " .. instruction.Operand2
          end
    
          AddCPUDesc(instruction.Mnemonic,
            instructionArgs,
            instruction.Reference,
            instruction.Set,
            instruction.Opcode)
        end
      end
    -- Create and destroy hooks for extended instruction support.
    local function helperCreateInstructionHook(indexes)
      for _,k in ipairs(indexes) do
        local instr = CPULib.InstructionTable[k]
        local instructionArgs = instr.Operand1
        if instr.Operand2 ~= "" then
          instructionArgs = instructionArgs .. ", " .. instr.Operand2
        end
        table.insert(helperInst,{
          [1] = instr.Mnemonic,
          [2] = instr.Extension,
          [3] = instructionArgs,
          [4] = instr.Set,
          [5] = instr.Opcode
        })
        helperDesc[instr.Mnemonic] = instr.Reference
      end
    end
    local function helperDestroyInstructionHook(indexes)
      for _,k in ipairs(indexes) do
        helperDesc[helperInst[k][1]] = nil
        helperInst[k] = nil
      end
    end
    table.insert(CPULib.CreateInstructionHooks,helperCreateInstructionHook)
    table.insert(CPULib.RemoveInstructionHooks,helperDestroyInstructionHook)
    end
  end)
end

function CPULib.SetupEditor(editor,title,location,name)
  -- Modes support removes the original handling for the debug buttons, which are added back here.
  -- These were also moved a bit to make them not overlap the sound browser on the spu
    if E2Helper.Modes then
      -- Add "step forward" button
      local DebugForward = editor:addComponent(vgui.Create("Button", editor), -366, 31, -286, 20)
      DebugForward:SetText("Step Forward")
      DebugForward.Font = "E2SmallFont"
      DebugForward.DoClick = function()
        local currentPosition = CPULib.Debugger.PositionByPointer[CPULib.Debugger.Variables.IP]
        if currentPosition then
          local linePointers = CPULib.Debugger.PointersByLine[currentPosition.Line .. ":" .. currentPosition.File]
          if linePointers then -- Run till end of line
            RunConsoleCommand("wire_cpulib_debugstep", linePointers[2])
          else -- Run just once
            RunConsoleCommand("wire_cpulib_debugstep")
          end
        else -- Run just once
          RunConsoleCommand("wire_cpulib_debugstep")
        end
        -- Reset interrupt text
        CPULib.InterruptText = nil
      end
      editor.C.DebugForward = DebugForward

      -- Add "reset" button
      local DebugReset = editor:addComponent(vgui.Create("Button", editor), -406, 31, -366, 20)
      DebugReset:SetText("Reset")
      DebugReset.DoClick = function()
        RunConsoleCommand("wire_cpulib_debugreset")
        -- Reset interrupt text
        CPULib.InterruptText = nil
      end
      editor.C.DebugReset = DebugReset

      -- Add "run" button
      local DebugRun = editor:addComponent(vgui.Create("Button", editor), -441, 31, -406, 20)
      DebugRun:SetText("Run")
      DebugRun.DoClick = function() RunConsoleCommand("wire_cpulib_debugrun") end
      editor.C.DebugRun = DebugRun
      editor:Setup(title,location,"Z"..name)
    else
      editor:Setup(title,location,name)
      -- ZCPU will be fine without the new version, because the old version is hardcoded to support us.
    end
end

CPULib.InstructionTable = {}
local W1,R0,OB,UB,CB,TR,OL,BL,PR = 1,2,4,8,16,32,64,128,256

local function Bit(x,n) return (math.floor(x / n) % 2) == 1 end
local function Entry(Set,Opc,Mnemonic,Ops,Version,Flags,Op1,Op2,Reference)
  table.insert(CPULib.InstructionTable,
    { Set = Set,
      Opcode = Opc,
      Mnemonic = Mnemonic,
      OperandCount = Ops,
      MinimumVersion = Version,
      Operand1 = Op1,
      Operand2 = Op2,
      Reference = Reference,

      WritesFirstOperand = Bit(Flags,W1),
      Privileged = Bit(Flags,R0),
      Obsolete = Bit(Flags,OB),
      UnconditionalBranching = Bit(Flags,UB),
      ConditionalBranching = Bit(Flags,CB),
      Trigonometric = Bit(Flags,TR),
      Old = Bit(Flags,OL),
      BlockPrefix = Bit(Flags,BL),
      PrivilegedRequester = Bit(Flags,PR)
    })

end
local function CPU(...) Entry("CPU",...) end
local function GPU(...) Entry("GPU",...) end
local function VEX(...) Entry("VEX",...) end
local function SPU(...) Entry("SPU",...) end

CPULib.Extensions = CPULib.Extensions or {}

-- These are the indexes that we should remove from the instruction table
-- when recalculating which instructions should be available to the compiler
CPULib.ExtendedInstructions = CPULib.ExtendedInstructions or {}

-- Holds the load order for extensions by platform
CPULib.ExtensionOrder = CPULib.ExtensionOrder or {}

-- When the extension list is regenerated it will run these funcs to tell
-- CPULib instruction dependant sources to alter their lookup tables to reflect
-- the current set of instructions

-- Instruction indexes that should be removed from lookup tables
CPULib.RemoveInstructionHooks = CPULib.RemoveInstructionHooks or {}

-- Instruction indexes that should be added to the lookup tables
CPULib.CreateInstructionHooks = CPULib.CreateInstructionHooks or {}

CPULib.FlagLookup = {
  ["W1"] = W1,
  ["R0"] = R0,
  ["OB"] = OB,
  ["UB"] = UB,
  ["CB"] = CB,
  ["TR"] = TR,
  ["OL"] = OL,
  ["BL"] = BL,
  ["PR"] = PR
}

-- Parses an array of flags into a single number from a lookup table by name
function CPULib:ParseFlagArray(flags)
  if not flags then return 0 end
  local n = 0

  for _,v in ipairs(flags) do
    if self.FlagLookup[v] then
      n = n + self.FlagLookup[v]
    end
  end
  return n
end

function CPULib:CreateExtension(name, platform)
  local extension = {Platform = string.upper(platform)}
  return self:RegisterExtension(name, extension)
end

function CPULib:RegisterExtension(name, extension)
  if not self.Extensions[extension.Platform] then
    self.Extensions[extension.Platform] = {}
  end
  if self.Extensions[extension.Platform][name] then
    return false
  end
  self.Extensions[extension.Platform][name] = extension

  -- Create an instruction with the raw API using arguments
  function extension:RegisterInstruction(name, opcount, opfunc, flags, docs)
    local instruction = {
      Name = name,
      Operands = opcount,
      OpFunc = opfunc,
      Flags = flags,
      Op1Name = "",
      Op2Name = "",
      Description = docs.Description
    }
    if not docs then
      instruction.Description = "Instruction "..name.." created by extension "..self.Name
      instruction.Version = 1
      if opcount > 0 then
        instruction.Op1Name = "X"
      end
      if opcount > 1 then
        instruction.Op2Name = "Y"
      end
    end
    table.insert(self.Instructions, instruction)
    return instruction
  end

  -- Takes a lua function expecting args (VM, operands) where operands is a number-indexed table containing the values
  -- of the operands used by the function, setting them inside this table will change their values on
  -- completion of the function (when possible).
  --
  -- Return one or two numbers to generate an interrupt.
  function extension:InstructionFromLuaFunc(name, opcount, luafunc, flags, docs)
    -- This will need to be compiled at extension load time with $interOpIndex replaced
    -- by the actual index into the interop function table
    local opfunc =  [[
        local args = { ... }
        local self = args[1]
        self:Dyn_Emit("$L interOp = {$1 or 0, $2 or 0}")
        self:Dyn_Emit("$L interOpRet1, interOpRet2 = VM.InterOpFuncs[$interOpIndex](VM,interOp)")
        self:Dyn_EmitInterruptCheck()
        self:Dyn_Emit("if interOpRet1 then")
          self:Dyn_EmitState()
          self:Emit("VM.IP = %d",(self.PrecompileIP or 0))
          self:Emit("VM.XEIP = %d",(self.PrecompileTrueXEIP or 0))
          self:Dyn_Emit("VM:Interrupt(interOpRet1 or 5, interOpRet2 or 0)")
          self:Dyn_EmitBreak()
        self:Dyn_Emit("end")
        self:Dyn_Emit("if $1 ~= interOp[1] then")
          self:Dyn_EmitOperand(1,"interOp[1]")
        self:Dyn_Emit("end")
        self:Dyn_Emit("if $2 ~= interOp[2] then")
          self:Dyn_EmitOperand(2,"interOp[2]")
        self:Dyn_Emit("end")
      ]]
      local precompileBreakApplied = false
      for _,i in ipairs(flags) do
        -- force a break if this is a branching function
        if (i == "CB" or i == "UB") and not precompileBreakApplied then
          -- Without this, a CB(conditional branch) or UB(unconditional branch) flagged function will not have VM:Jump take effect.
          opfunc = opfunc .. [[
            self:Dyn_EmitBreak()
            self.PrecompileBreak = true
          ]]
          precompileBreakApplied = true
        end
      end
    local instruction = self:RegisterInstruction(name, opcount, opfunc, flags, docs)
    instruction.InterOpFunc = luafunc
  end
  if not extension.Instructions then
    extension.Instructions = {}
  end
  return extension
end

-- Rebuilds the instruction table according to the load order
function CPULib:RebuildExtendedInstructions()
  for _,hook in ipairs(self.RemoveInstructionHooks) do
    hook(self.ExtendedInstructions)
  end
  for _,v in ipairs(self.ExtendedInstructions) do
    self.InstructionTable[v] = nil
  end
  self.ExtendedInstructions = {}

  -- rebuild by extension name
  for platname,platform in pairs(self.ExtensionOrder) do
    local curOpcode = -1
    for _,extension in ipairs(platform) do
      for _,i in ipairs(self.Extensions[platname][extension].Instructions) do
        Entry(platname,curOpcode,i.Name,i.Operands,i.Version,self:ParseFlagArray(i.Flags),i.Op1Name,i.Op2Name,i.Description)
        -- Store extension name on the instruction
        local lastIndex = #CPULib.InstructionTable
        CPULib.InstructionTable[lastIndex].Extension = extension
        table.insert(self.ExtendedInstructions,lastIndex)
        curOpcode = curOpcode - 1
      end
    end
  end
  for _,hook in ipairs(self.CreateInstructionHooks) do
    hook(self.ExtendedInstructions)
  end
end

function CPULib:ToExtensionString(exttable)
  local ext_str = ""
  for _,ext in ipairs(exttable) do
    ext_str = ext_str .. ext .. ';'
  end
  return ext_str
end

function CPULib:FromExtensionString(extstr,platform)
  local extensions = {}
  -- only available extensions are loaded
  if not extstr then return {} end
  for ext in string.gmatch(extstr or "","([^;]*);") do
    if CPULib.Extensions[platform] and CPULib.Extensions[platform][ext] then
      table.insert(extensions,ext)
    end
  end
  return extensions
end

function CPULib:LoadExtensionOrder(extensions, platform)
  self.ExtensionOrder[platform] = extensions
  self:RebuildExtendedInstructions()
end

function CPULib:LoadExtensions(VM, platform)
  -- Uses negative indexes to avoid collision with non-extension instructions
  -- since there's currently a cap on only being able to have 1000(0 to 999) instructions
  -- using negative indices would expand that to allow for 999(-1 to -999) more instructions
  if not VM.Extensions then
    return false
  end
  local curInstruction = -1
  local curInterop = 1
  for _, name in pairs(VM.Extensions) do
    if self.Extensions[platform][name] then
      -- The actual load order is the order that it's in for the VM.
      for _,instr in ipairs(self.Extensions[platform][name].Instructions) do
        VM.ExtOperandCount[curInstruction*-1] = instr.Operands
        if instr.Privileged then
          VM.ExtOperandRunLevel[curInstruction*-1] = 0
        end
        if instr.InterOpFunc then
          if not VM.InterOpFuncs then
            VM.InterOpFuncs = {}
          end
          VM.InterOpFuncs[curInterop] = instr.InterOpFunc
          local finalOpFunc = string.gsub(instr.OpFunc, "$interOpIndex", curInterop)
          VM.OpcodeTable[curInstruction] = CompileString(finalOpFunc)
          curInterop = curInterop + 1
        else
          VM.OpcodeTable[curInstruction] = instr.OpFunc
        end
        curInstruction = curInstruction - 1
      end
    else
      -- return name of missing extension on error
      return name
    end
  end
end


-------------------------------------------------------------------------------------------------------------------------------------------------
-- Zyelios CPU/GPU/SPU instruction set reference table
-------------------------------------------------------------------------------------------------------------------------------------------------
--- Opc  Mnemonic ------- Ops  Version  Flags ---- Op1 ---- Op2 ---- Reference ------------------------------------------------------------------
CPU(000, "RESERVED",      0,   0.00,    0,         "",      "",      "Stop processor execution")
CPU(001, "JNE",           1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is not equal")
CPU(001, "JNZ",           1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is not zero")
CPU(002, "JMP",           1,   1.00,    UB,        "PTR",   "",      "Jump to PTR")
CPU(003, "JG",            1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is greater")
CPU(003, "JNLE",          1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is not less or equal")
CPU(004, "JGE",           1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is greater or equal")
CPU(004, "JNL",           1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is not less")
CPU(005, "JL",            1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is less")
CPU(005, "JNGE",          1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is not greater or equal")
CPU(006, "JLE",           1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is less or equal")
CPU(006, "JNG",           1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is not greater")
CPU(007, "JE",            1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is equal")
CPU(007, "JZ",            1,   1.00,    CB,        "PTR",   "",      "Jump to PTR if result is zero")
CPU(008, "CPUID",         1,   1.00,    0,         "IDX",   "",      "Write processor information variable IDX into EAX register")
CPU(009, "PUSH",          1,   1.00,    0,         "X",     "",      "Push X onto processor stack")
---- Dec 1 --------------------------------------------------------------------------------------------------------------------------------------
CPU(010, "ADD",           2,   1.00,    0,         "X",     "Y",     "X = X + Y")
CPU(011, "SUB",           2,   1.00,    0,         "X",     "Y",     "X = X - Y")
CPU(012, "MUL",           2,   1.00,    0,         "X",     "Y",     "X = X * Y")
CPU(013, "DIV",           2,   1.00,    0,         "X",     "Y",     "X = X / Y")
CPU(014, "MOV",           2,   1.00,    0,         "X",     "Y",     "X = Y")
CPU(015, "CMP",           2,   1.00,    0,         "X",     "Y",     "Compare X and Y. Use with conditional branching instructions")
CPU(016, "RD",            2,   1.00,    PR+OB,     "X",     "PTR",   "Read value from memory by pointer PTR")
CPU(017, "WD",            2,   1.00,    R0+OB,     "PTR",   "Y",     "Write value to memory by pointer PTR")
CPU(018, "MIN",           2,   1.00,    0,         "X",     "Y",     "Set X to smaller value out of X and Y")
CPU(019, "MAX",           2,   1.00,    0,         "X",     "Y",     "Set X to bigger value out of X and Y")
---- Dec 2 --------------------------------------------------------------------------------------------------------------------------------------
CPU(020, "INC",           1,   1.00,    W1,        "X",     "",      "Increase X by one")
CPU(021, "DEC",           1,   1.00,    W1,        "X",     "",      "Decrease X by one")
CPU(022, "NEG",           1,   1.00,    W1,        "X",     "",      "Change sign of X")
CPU(023, "RAND",          1,   1.00,    W1,        "X",     "",      "Set X to random value")
CPU(024, "LOOP",          1,   2.00,    CB,        "PTR",   "",      "Decrease ECX, and if ECX is not set to 0, jump to PTR")
CPU(024, "LOOPC",         1,  10.00,    CB,        "PTR",   "",      "Decrease ECX, and if ECX is not set to 0, jump to PTR")
CPU(025, "LOOPA",         1,   2.00,    CB,        "PTR",   "",      "Decrease EAX, and if EAX is not set to 0, jump to PTR")
CPU(026, "LOOPB",         1,   2.00,    CB,        "PTR",   "",      "Decrease EBX, and if EBX is not set to 0, jump to PTR")
CPU(027, "LOOPD",         1,   2.00,    CB,        "PTR",   "",      "Decrease EDX, and if EDX is not set to 0, jump to PTR")
CPU(028, "SPG",           1,   2.00,    R0,        "PAGE",  "",      "Make PAGE readonly")
CPU(029, "CPG",           1,   2.00,    R0,        "PAGE",  "",      "Make PAGE readable and writeable")
---- Dec 3---------------------------------------------------------------------------------------------------------------------------------------
CPU(030, "POP",           1,   1.00,    0,         "X",     "",      "Pop value off stack and write it into X")
CPU(031, "CALL",          1,   1.00,    UB,        "PTR",   "",      "Call subroutine by address PTR")
CPU(032, "BNOT",          1,   1.00,    W1,        "INT",   "",      "Flip all bits in the integer number")
CPU(033, "FINT",          1,   1.00,    W1,        "X",     "",      "Force X to be an integer value")
CPU(034, "FRND",          1,   1.00,    W1,        "X",     "",      "Round X to the nearest integer value")
CPU(034, "RND",           1,   1.00,    W1+OL,     "X",     "",      "FRND")
CPU(035, "FFRAC",         1,   1.00,    W1,        "X",     "",      "Remove integer part of the X, leaving only the fractional part")
CPU(036, "FINV",          1,   1.00,    W1,        "X",     "",      "X = 1 / X")
CPU(037, "HALT",          1,   1.00,    OB,        "PORT",  "",      "Halt processor execution until PORT is written to")
CPU(038, "FSHL",          1,   2.00,    W1,        "X",     "",      "Multiply X by 2 (does not floor)")
CPU(039, "FSHR",          1,   2.00,    W1,        "X",     "",      "Divide X by 2 (does not floor)")
---- Dec 4 --------------------------------------------------------------------------------------------------------------------------------------
CPU(040, "RET",           0,   1.00,    UB,        "",      "",      "Return from a subroutine")
CPU(041, "IRET",          0,   2.00,    UB,        "",      "",      "Return from an interrupt")
CPU(042, "STI",           0,   2.00,    R0,        "",      "",      "Enable interrupt handling")
CPU(043, "CLI",           0,   2.00,    R0,        "",      "",      "Disable interrupt handling")
CPU(044, "STP",           0,   2.00,    R0+OB,     "",      "",      "Enable protected mode")
CPU(045, "CLP",           0,   2.00,    R0+OB,     "",      "",      "Disable protected mode")
CPU(046, "RESERVED",      0,   0.00,    R0,        "",      "",      "")
CPU(047, "RETF",          0,   1.00,    UB,        "",      "",      "Return from a far subroutine call")
CPU(048, "STEF",          0,   4.00,    R0,        "",      "",      "Enable extended mode")
CPU(049, "CLEF",          0,   4.00,    R0,        "",      "",      "Disable extended mode")
---- Dec 5 --------------------------------------------------------------------------------------------------------------------------------------
CPU(050, "AND",           2,   1.00,    0,         "X",     "Y",     "Logical AND between X and Y")
CPU(050, "LAND",          2,   1.00,    0,         "X",     "Y",     "Logical AND between X and Y")
CPU(051, "OR",            2,   1.00,    0,         "X",     "Y",     "Logical OR between X and Y")
CPU(051, "LOR",           2,   1.00,    0,         "X",     "Y",     "Logical OR between X and Y")
CPU(052, "XOR",           2,   1.00,    0,         "X",     "Y",     "Logical XOR between X and Y")
CPU(052, "LXOR",          2,   1.00,    0,         "X",     "Y",     "Logical XOR between X and Y")
CPU(053, "FSIN",          2,   1.00,    TR,        "X",     "Y",     "Write sine of X to Y")
CPU(054, "FCOS",          2,   1.00,    TR,        "X",     "Y",     "Write cosine of X to Y")
CPU(055, "FTAN",          2,   1.00,    TR,        "X",     "Y",     "Write tangent of X to Y")
CPU(056, "FASIN",         2,   1.00,    TR,        "X",     "Y",     "Write arcsine of X to Y")
CPU(057, "FACOS",         2,   1.00,    TR,        "X",     "Y",     "Write arccosine of X to Y")
CPU(058, "FATAN",         2,   1.00,    TR,        "X",     "Y",     "Write arctangent of X to Y")
CPU(059, "MOD",           2,   2.00,    0,         "X",     "Y",     "Write remainder of X/Y to Y")
---- Dec 6 --------------------------------------------------------------------------------------------------------------------------------------
CPU(060, "BIT",           2,   2.00,    0,         "INT",   "BIT",   "Test whether BIT of X is set. Use with conditional branching instructions")
CPU(061, "SBIT",          2,   2.00,    0,         "INT",   "BIT",   "Set BIT of X")
CPU(062, "CBIT",          2,   2.00,    0,         "INT",   "BIT",   "Clear BIT of X")
CPU(063, "TBIT",          2,   2.00,    0,         "INT",   "BIT",   "Toggle BIT of X")
CPU(064, "BAND",          2,   2.00,    0,         "INT",   "INT",   "Write result of binary AND between operands")
CPU(065, "BOR",           2,   2.00,    0,         "INT",   "INT",   "Write result of binary OR between operands")
CPU(066, "BXOR",          2,   2.00,    0,         "INT",   "INT",   "Write result of binary XOR between operands")
CPU(067, "BSHL",          2,   2.00,    0,         "INT",   "X",     "Shift bits of INT left by X")
CPU(068, "BSHR",          2,   2.00,    0,         "INT",   "X",     "Shift bits of INT right by X")
CPU(069, "JMPF",          2,   2.00,    UB,        "PTR",   "CS",    "Jump to PTR in code segment CS")
---- Dec 7 --------------------------------------------------------------------------------------------------------------------------------------
CPU(070, "NMIINT",        1,   4.00,    R0+OL,     "INTR",  "",      "EXTINT")
CPU(070, "EXTINT",        1,  10.00,    R0,        "INTR",  "",      "Call interrupt INTR as an external interrupt")
CPU(071, "CNE",           1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is not equal")
CPU(071, "CNZ",           1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is not zero")
CPU(072, "RESERVED",      1,   0.00,    0,         "",      "",      "")
CPU(073, "CG",            1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is greater")
CPU(073, "CNLE",          1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is not less or equal")
CPU(074, "CGE",           1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is greater or equal")
CPU(074, "CNL",           1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is not less")
CPU(075, "CL",            1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is less")
CPU(075, "CNGE",          1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is not greater or equal")
CPU(076, "CLE",           1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is less or equal")
CPU(076, "CNG",           1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is not greater")
CPU(077, "CE",            1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is equal")
CPU(077, "CZ",            1,   2.00,    CB,        "PTR",   "",      "Call subrotine if result is zero")
CPU(078, "MCOPY",         1,   2.00,    BL,        "INT",   "",      "Copy INT bytes from array pointed by ESI to EDI")
CPU(079, "MXCHG",         1,   2.00,    BL,        "INT",   "",      "Swap INT bytes between two arrays pointed by ESI and EDI")
---- Dec 8 --------------------------------------------------------------------------------------------------------------------------------------
CPU(080, "FPWR",          2,   2.00,    0,         "X",     "Y",     "Raise X to power Y")
CPU(081, "XCHG",          2,   2.00,    0,         "X",     "Y",     "Swap X and Y")
CPU(082, "FLOG",          2,   2.00,    OL,        "X",     "Y",     "FLN")
CPU(082, "FLN",           2,  10.00,    0,         "X",     "Y",     "Write logarithm (base e) of Y to X")
CPU(083, "FLOG10",        2,   2.00,    0,         "X",     "Y",     "Write logarithm (base 10) of Y to X")
CPU(084, "IN",            2,   2.00,    0,         "X",     "PORT",  "Input value from PORT to X")
CPU(085, "OUT",           2,   2.00,    0,         "PORT",  "Y",     "Write X to PORT")
CPU(086, "FABS",          2,   2.00,    TR,        "X",     "Y",     "Write absolute value of Y to X")
CPU(087, "FSGN",          2,   2.00,    TR,        "X",     "Y",     "Write sign of Y to X")
CPU(088, "FEXP",          2,   2.00,    TR,        "X",     "Y",     "Write exponent of Y to X")
CPU(089, "CALLF",         2,   2.00,    UB,        "PTR",   "CS",    "Call subroutine by offset PTR in code segment CS")
---- Dec 9 --------------------------------------------------------------------------------------------------------------------------------------
CPU(090, "FPI",           1,   2.00,    W1,        "X",     "",      "Set X to precise value of PI (3.1415926..)")
CPU(091, "FE",            1,   2.00,    W1,        "X",     "",      "Set X to precise value of E (2.7182818..)")
CPU(092, "INT",           1,   2.00,    0,         "INTR",  "",      "Call interrupt INTR")
CPU(093, "TPG",           1,   2.00,    0,         "PAGE",  "",      "Test PAGE. Use branching instructions to test for zero on failure, non-zero if test passed.")
CPU(094, "FCEIL",         1,   2.00,    W1,        "X",     "",      "Rounds X up to the next integer")
CPU(095, "ERPG",          1,   2.00,    R0,        "PAGE",  "",      "Erase ROM page")
CPU(096, "WRPG",          1,   2.00,    R0,        "PAGE",  "",      "Copy RAM page into ROM page")
CPU(097, "RDPG",          1,   2.00,    R0,        "PAGE",  "",      "Read ROM page into RAM")
CPU(098, "TIMER",         1,   2.00,    W1,        "X",     "",      "Set X to value of the internal processor timer")
CPU(099, "LIDTR",         1,   2.00,    R0,        "PTR",   "",      "Set interrupt table pointer to PTR")
---- Dec 10 -------------------------------------------------------------------------------------------------------------------------------------
CPU(100, "RESERVED",      1,   0.00,    R0,        "",      "",      "")
CPU(101, "JNER",          1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is not equal")
CPU(101, "JNZR",          1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is not zero")
CPU(102, "JMPR",          1,   3.00,    UB,        "INT",   "",      "Relative jump INT bytes forward")
CPU(103, "JGR",           1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is greater")
CPU(103, "JNLER",         1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is not less or equal")
CPU(104, "JGER",          1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is greater or equal")
CPU(104, "JNLR",          1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is not less")
CPU(105, "JLR",           1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is less")
CPU(105, "JNGER",         1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is not greater or equal")
CPU(106, "JLER",          1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is less or equal")
CPU(106, "JNGR",          1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is not greater")
CPU(107, "JER",           1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is equal")
CPU(107, "JZR",           1,   3.00,    CB,        "INT",   "",      "Relative jump INT bytes forward if result is zero")
CPU(108, "LNEG",          1,   3.00,    W1,        "X",     "",      "Logically negate X")
CPU(108, "LNOT",          1,   3.00,    W1,        "X",     "",      "Logically negate X")
CPU(109, "RESERVED",      1,   0.00,    R0,        "",      "",      "")
---- Dec 11 -------------------------------------------------------------------------------------------------------------------------------------
CPU(110, "NMIRET",        0,   2.00,    R0+OL,     "",      "",      "EXTRET")
CPU(110, "EXTRET",        0,  10.00,    R0,        "",      "",      "Return from an external interrupt")
CPU(111, "IDLE",          0,   4.00,    R0,        "",      "",      "Skip several processor cycles")
CPU(112, "NOP",           0,   5.00,    0,         "",      "",      "Do nothing")
CPU(113, "RESERVED",      0,   0.00,    0,         "",      "",      "")
CPU(114, "PUSHA",         0,   8.00,    0,         "",      "",      "Push all general purpose registers to stack")
CPU(115, "POPA",          0,   8.00,    0,         "",      "",      "Pop all general purpose registers off stack")
CPU(116, "STD2",          0,  10.00,    R0,        "",      "",      "Enable hardware debug mode")
CPU(117, "LEAVE",         0,  10.00,    0,         "",      "",      "Leave subroutine stack frame")
CPU(118, "STM",           0,  10.00,    R0,        "",      "",      "Enable extended memory mode")
CPU(119, "CLM",           0,  10.00,    R0,        "",      "",      "Disable extended memory mode")
---- Dec 12 -------------------------------------------------------------------------------------------------------------------------------------
CPU(120, "CPUGET",        2,   5.00,    PR,         "X",     "IDX",   "Read internal processor register IDX")
CPU(121, "CPUSET",        2,   5.00,    R0,        "IDX",   "Y",     "Write internal processor register IDX")
CPU(122, "SPP",           2,   5.00,    R0+BL,     "PAGE",  "IDX",   "Set page flag IDX")
CPU(123, "CPP",           2,   5.00,    R0+BL,     "PAGE",  "IDX",   "Clear page flag IDX")
CPU(124, "SRL",           2,   5.00,    R0+BL,     "PAGE",  "INT",   "Set page runlevel to INT")
CPU(125, "CRL",           2,   5.00,    R0,        "X",     "PAGE",  "Write page runlevel to INT")
CPU(126, "LEA",           2,   5.00,    0,         "X",     "Y",     "Load absolute address fetched by operand Y into X")
CPU(127, "BLOCK",         2,   6.00,    0,         "PTR",   "SIZE",  "Make next instruction run on this block")
CPU(128, "CMPAND",        2,   6.00,    0,         "X",     "Y",     "Compare X and Y, and logically combine with result of previous comparsion using AND")
CPU(129, "CMPOR",         2,   6.00,    0,         "X",     "Y",     "Compare X and Y, and logically combine with result of previous comparsion using OR")
---- Dec 13 -------------------------------------------------------------------------------------------------------------------------------------
CPU(130, "MSHIFT",        2,   7.00,    0,         "COUNT", "OFFSET","Shift (and rotate) data pointed by ESI by OFFSET bytes")
CPU(131, "SMAP",          2,   8.00,    R0+BL,     "PAGE1", "PAGE2", "Remap PAGE1 to physical page PAGE2")
CPU(132, "GMAP",          2,   8.00,    PR,        "X",     "PAGE",  "Read what physical page PAGE is mapped to")
CPU(133, "RSTACK",        2,   9.00,    0,         "X",     "IDX",   "Read value from stack at offset IDX (from address SS+IDX)")
CPU(134, "SSTACK",        2,   9.00,    0,         "IDX",   "Y",     "Write value to stack at offset IDX (to address SS+IDX)")
CPU(135, "ENTER",         1,  10.00,    0,         "SIZE",  "",      "Enter stack frame and allocate SIZE bytes on stack for local variables")
CPU(136, "IRETP",         1,   2.00,    R0,        "PTBL",  "",      "Set PTBL, then return from an interrupt")
CPU(137, "EXTRETP",       1,  10.00,    R0,        "PTBL",  "",      "Set PTBL, then return from an external interrupt")
---- Dec 14 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
CPU(140, "EXTRETA",       0,  11.00,    R0,        "",      "",      "Return from an external interrupt and restore R0-R31 registers")
CPU(141, "EXTRETPA",      1,  11.00,    R0,        "PTBL",  "",      "Set PTBL, then return from an external interrupt with restoring R0-R31 registers")
---- Dec 15 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
CPU(150, "STERR",         2,   3.00,    R0,        "X",  "Y",        "Output an external error where X is error number and Y is error parameter")
CPU(151, "CLERR",         0,   1.00,    R0,        "",    "",        "Clear the external error output")
---- Dec 16 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
CPU(152, "QUOCMP",        0,   1.00,    0,         "",    "",        "Will set CMPR to 1 if Quota has occurred, or 0 if it hasn't, and reset quota reached flag.")
CPU(153, "QUOTIMER",      1,   1.00,    W1,        "X",    "",       "Get the time since a quota-aware instruction was interrupted.")
---- Dec 17 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
---- Dec 18 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
---- Dec 19 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
---- Dec 20 -- Output buffer control ------------------------------------------------------------------------------------------------------------
GPU(200, "DTEST",         0,    1.0,    0,         "",      "",      "Output a test pattern to screen")
GPU(200, "DRECT_TEST",    0,    0.5,    OL,        "",      "",      "DTEST")
GPU(201, "DEXIT",         0,    0.5,    UB,        "",      "",      "End execution of the current frame")
GPU(201, "DVSYNC",        0,    1.0,    0,         "",      "",      "Wait until next frame (only in asynchronous thread)")
GPU(202, "DCLR",          0,    0.5,    0,         "",      "",      "Clear screen color to black")
GPU(203, "DCLRTEX",       0,    0.5,    0,         "",      "",      "Clear background with texture")
GPU(204, "DVXFLUSH",      0,    0.6,    0,         "",      "",      "Flush current vertex buffer to screen")
GPU(205, "DVXCLEAR",      0,    0.6,    0,         "",      "",      "Clear vertex buffer")
GPU(206, "DSETBUF_VX",    0,    1.0,    0,         "",      "",      "Set frame buffer to vertex output")
GPU(207, "DSETBUF_SPR",   0,    1.0,    0,         "",      "",      "Set frame buffer to sprite buffer")
GPU(207, "DBACKBUF",      0,    1.0,    0,         "",      "",      "Set frame buffer to back buffer")
GPU(208, "DSETBUF_FBO",   0,    1.0,    0,         "",      "",      "Set frame buffer to view buffer")
GPU(208, "DFRONTBUF",     0,    1.0,    0,         "",      "",      "Set frame buffer to front buffer")
GPU(209, "DSWAP",         0,    1.0,    0,         "",      "",      "Copy back buffer to front buffer")
---- Dec 21 -- Pipe controls and one-operand opcodes --------------------------------------------------------------------------------------------
GPU(210, "DVXPIPE",       1,    0.5,    0,         "IDX",   "",      "Set vertex pipe")
GPU(211, "DCVXPIPE",      1,    0.5,    OL,        "IDX",   "",      "DCPIPE")
GPU(211, "DCPIPE",        1,    1.0,    0,         "IDX",   "",      "Set coordinate pipe")
GPU(212, "DENABLE",       1,    0.5,    0,         "IDX",   "",      "Enable parameter")
GPU(213, "DDISABLE",      1,    0.5,    0,         "IDX",   "",      "Disable parameter")
GPU(214, "DCLRSCR",       1,    0.5,    0,         "COLOR", "",      "Clear screen with color")
GPU(215, "DCOLOR",        1,    0.5,    0,         "COLOR", "",      "Set current color")
GPU(216, "DTEXTURE",      1,    1.0,    0,         "IDX",   "",      "Set current texture")
GPU(217, "DSETFONT",      1,    0.5,    0,         "IDX",   "",      "Set current font")
GPU(218, "DSETSIZE",      1,    0.5,    0,         "INT",   "",      "Set font size")
GPU(219, "DMOVE",         1,    0.5,    0,         "VEC2F", "",      "Set drawing position offset")
---- Dec 22 -- Rendering opcodes ----------------------------------------------------------------------------------------------------------------
GPU(220, "DVXDATA_2F",    2,    0.5,    0,         "VEC2F", "IDX",   "Draw a solid 2D polygon (pointer to 2D data, vertex count)")
GPU(220, "DVXPOLY",       2,    0.5,    0,         "VEC2F", "IDX",   "Draw a solid 2D polygon (pointer to 2D data, vertex count)")
GPU(221, "DVXDATA_2F_TEX",2,    0.5,    0,         "VEC2F", "IDX",   "Draw a textured 2D polygon (pointer to 2D data, vertex count)")
GPU(221, "DVXTEXPOLY",    2,    0.5,    0,         "VEC2F", "IDX",   "Draw a textured 2D polygon (pointer to 2D data, vertex count)")
GPU(222, "DVXDATA_3F",    2,    0.5,    0,         "VEC3F", "IDX",   "Draw a solid 3D polygon (pointer to 3D data, vertex count)")
GPU(223, "DVXDATA_3F_TEX",2,    0.5,    0,         "VEC3FT","IDX",   "Draw a textured 3D polygon (pointer to 3D data, vertex count)")
GPU(224, "DVXDATA_3F_WF", 2,    0.5,    0,         "VEC3F", "IDX",   "Draw a wireframe 3D polygon (pointer to 3D data, vertex count)")
GPU(225, "DRECT",         2,    0.5,    0,         "VEC2F", "VEC2F", "Draw a rectangle (by endpoints)")
GPU(226, "DCIRCLE",       2,    0.5,    0,         "VEC2F", "Y",     "Draw a circle with radius Y")
GPU(227, "DLINE",         2,    0.5,    0,         "VEC2F", "VEC2F", "Draw a line")
GPU(228, "DRECTWH",       2,    0.6,    0,         "VEC2F", "VEC2F", "Draw a rectangle (by offset, size)")
GPU(229, "DORECT",        2,    0.5,    0,         "VEC2F", "VEC2F", "Draw an outlined rectangle")
---- Dec 23 -- Additional rendering opcodes -----------------------------------------------------------------------------------------------------
GPU(230, "DTRANSFORM2F",  2,    0.5,    0,         "VEC2F", "VEC2F", "Transform vector and write it to first operand")
GPU(231, "DTRANSFORM3F",  2,    0.5,    0,         "VEC3F", "VEC3F", "Transform vector and write it to first operand")
GPU(232, "DSCRSIZE",      2,    0.5,    0,         "X",     "Y",     "Set screen size")
GPU(233, "DROTATESCALE",  2,    0.5,    0,         "X",     "Y",     "Rotate by X, scale by Y")
GPU(234, "DORECTWH",      2,    0.5,    0,         "VEC2F", "VEC2F", "Draw an outlined rectangle by width/height")
GPU(235, "DCULLMODE",     2,    0.7,    0,         "IDX",   "IDX",   "Set cullmode and lighting mode")
GPU(236, "DARRAY",        2,    1.0,    0,         "VEC2F", "STRUCT","Draw an array of width/height rectangles.\nStruct layout:\nResolution X, Resolution Y, Pixel Width, Pixel Height, Color Mode(like digital screen), Pointer to color buffer")
--GPU(237, "DDTERMINAL",    2,    1.0,    0,         "VEC2F", "STRUCT","Draw a console screen/terminal window")
GPU(238, "DPIXEL",        2,    1.0,    0,         "VEC2F", "COLOR", "Draw a pixel to screen")
GPU(239, "RESERVED",      2,    0.0,    0,         "",      "",      "")
---- Dec 24 -- Text output and lighting ---------------------------------------------------------------------------------------------------------
GPU(240, "DWRITE",        2,    0.5,    0,         "VEC2F", "STRING","Write a string")
GPU(241, "DWRITEI",       2,    0.5,    0,         "VEC2F", "INT",   "Write an integer value")
GPU(242, "DWRITEF",       2,    0.5,    0,         "VEC3F", "Y",     "Write a float value")
GPU(243, "DENTRYPOINT",   2,    0.5,    0,         "IDX",   "PTR",   "Set entry point")
GPU(244, "DSETLIGHT",     2,    0.6,    0,         "IDX",   "STRUCT","Set light")
GPU(245, "DGETLIGHT",     2,    0.6,    0,         "STRUCT","IDX",   "Get light")
GPU(246, "DWRITEFMT",     2,    0.6,    0,         "VEC2F", "STRING","Write a formatted string")
GPU(247, "DWRITEFIX",     2,    0.5,    0,         "VEC2F", "Y",     "Write a fixed value")
GPU(248, "DTEXTWIDTH",    2,    0.8,    0,         "INT",   "STRING","Return text width")
GPU(249, "DTEXTHEIGHT",   2,    0.8,    0,         "INT",   "STRING","Return text height")
---- Dec 25 -- Vector mode extension ------------------------------------------------------------------------------------------------------------
VEX(250, "VADD",          2,   7.00,    0,         "VEC",   "VEC",   "X = X + Y")
VEX(251, "VSUB",          2,   7.00,    0,         "VEC",   "VEC",   "X = X - Y")
VEX(252, "VMUL",          2,   7.00,    0,         "VEC",   "X",     "X = X * SCALAR Y")
VEX(253, "VDOT",          2,   7.00,    0,         "VEC",   "VEC",   "X = X dot Y")
VEX(254, "VCROSS",        2,   7.00,    0,         "VEC",   "VEC",   "X = X cross Y")
VEX(255, "VMOV",          2,   7.00,    0,         "VEC",   "VEC",   "X = Y")
VEX(256, "VNORM",         2,   7.00,    0,         "VEC",   "VEC",   "X = NORMALIZE(Y)")
VEX(257, "VCOLORNORM",    2,   10.0,    0,         "COLOR", "COLOR", "Normalize color (clamp it to RGB range)")
GPU(258, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(259, "DLOOPXY",       2,    0.7,    CB,        "PTR",   "PTR",     "2D loop by ECX/EDX registers, where on EDX = -1 it will decrement ECX and jump to ptr 1, else it will decrement EDX and jump to ptr 2 until ECX = -1")
VEX(259, "LOOPXY",        2,   10.0,    CB,        "PTR",   "PTR",     "2D loop by ECX/EDX registers, where on EDX = -1 it will decrement ECX and jump to ptr 1, else it will decrement EDX and jump to ptr 2 until ECX = -1")
---- Dec 26 -- Matrix math ----------------------------------------------------------------------------------------------------------------------
VEX(260, "MADD",          2,   7.00,    0,         "MATRIX","MATRIX","X = X + Y")
VEX(261, "MSUB",          2,   7.00,    0,         "MATRIX","MATRIX","X = X - Y")
VEX(262, "MMUL",          2,   7.00,    0,         "MATRIX","MATRIX","X = X * Y")
VEX(263, "MROTATE",       2,   7.00,    0,         "MATRIX","VEC4F", "Rotation matrix based on rotation vector")
VEX(264, "MSCALE",        2,   7.00,    0,         "MATRIX","VEC4F", "Scaling matrix based on scaling vector")
VEX(265, "MPERSPECTIVE",  2,   7.00,    0,         "MATRIX","VEC4F", "Perspective matrix based on FOV and near/far planes")
VEX(266, "MTRANSLATE",    2,   7.00,    0,         "MATRIX","VEC4F", "Translation matrix based on translation vector")
VEX(267, "MLOOKAT",       2,   7.00,    0,         "MATRIX","VEC4F", "Lookat matrix based on three vectors")
VEX(268, "MMOV",          2,   7.00,    0,         "MATRIX","MATRIX","X = Y")
VEX(269, "VLEN",          2,   7.00,    0,         "X",     "VEC",   "X = Sqrt(Y dot Y)")
---- Dec 27 -- Matrix math ----------------------------------------------------------------------------------------------------------------------
VEX(270, "MIDENT",        1,   7.00,    0,         "MATRIX","",      "Load identity matrix")
GPU(271, "MLOADPROJ",     1,    0.6,    0,         "MATRIX","",      "Load matrix into view matrix")
GPU(272, "MREAD",         1,    0.6,    0,         "MATRIX","",      "Write view matrix into matrix")
VEX(273, "VMODE",         1,   7.00,    0,         "IDX",   "",      "Set vector math mode")
GPU(274, "DT",            1,    0.6,    W1,        "X",     "",      "Set X to frame length time")
GPU(275, "RESERVED",      1,    0.0,    0,         "",      "",      "")
GPU(276, "DSHADE",        1,    0.5,    0,         "X",     "",      "Shade the current color")
GPU(277, "DSETWIDTH",     1,    0.5,    0,         "X",     "",      "Set line width")
GPU(278, "MLOAD",         1,    0.6,    0,         "MATRIX","",      "Load matrix into model matrix")
GPU(279, "DSHADENORM",    1,    0.6,    0,         "X",     "",      "Shade the current color and normalize it")
GPU(279, "DSHADECOL",     1,    0.6,    OL,        "X",     "",      "DSHADENORM")
---- Dec 28 -- Advanced rendering ---------------------------------------------------------------------------------------------------------------
GPU(280, "DDFRAME",       1,    1.0,    0,         "STRUCT","",      "Draw bordered frame")
GPU(281, "DDBAR",         1,    1.0,    0,         "STRUCT","",      "Draw a progress bar")
GPU(282, "DDGAUGE",       1,    1.0,    0,         "STRUCT","",      "Draw gauge needle")
GPU(283, "DRASTER",       1,    0.6,    0,         "INT",   "",      "Set rasterizer quality level")
GPU(284, "DDTERRAIN",     1,    0.8,    0,         "STRUCT","",      "Draw terrain")
GPU(285, "RESERVED",      1,    0.0,    0,         "",      "",      "")
GPU(286, "RESERVED",      1,    0.0,    0,         "",      "",      "")
GPU(287, "RESERVED",      1,    0.0,    0,         "",      "",      "")
GPU(288, "RESERVED",      1,    0.0,    0,         "",      "",      "")
GPU(289, "RESERVED",      1,    0.0,    0,         "",      "",      "")
---- Dec 29 -- Additional instructions ----------------------------------------------------------------------------------------------------------
GPU(290, "DLOADBYTES",    2,    1.0,    0,         "IDX",   "PTR",   "Load into texture slot by pointer")
GPU(291, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(292, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(293, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(294, "DMULDT",        2,    0.7,    0,         "X",     "Y",     "X = Y * dT")
VEX(295, "VDIV",          2,   7.00,    0,         "VEC",   "Y",     "VEC = VEC / Y")
VEX(296, "VTRANSFORM",    2,   8.00,    0,         "VEC",   "MATRIX","X = X * MATRIX")
GPU(297, "DSMOOTH",       2,    1.0,    0,         "X",     "Y",     "Smooth X with smoothness Y")
GPU(298, "DBEGIN",        0,    1.0,    0,         "",      "",      "Begin rendering (from async thread)")
GPU(299, "DEND",          0,    1.0,    0,         "",      "",      "End rendering (from async thread)")
---- Dec 30 -- 3D rendering ---------------------------------------------------------------------------------------------------------------------
GPU(300, "DROTATE",       1,    1.0,    0,         "VEC4F", "",      "Rotate model by vector")
GPU(301, "DTRANSLATE",    1,    1.0,    0,         "VEC4F", "",      "Translate model by vector")
GPU(302, "DSCALE",        1,    1.0,    0,         "VEC4F", "",      "Scale model by vector")
GPU(303, "DXTEXTURE",     1,    1.0,    0,         "STR",   "",      "Bind a specific external texture")
GPU(304, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(305, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(306, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(307, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(308, "RESERVED",      2,    0.0,    0,         "",      "",      "")
GPU(309, "RESERVED",      2,    0.0,    0,         "",      "",      "")
---- Dec 31 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------
---- Dec 32 -- SPU output control ---------------------------------------------------------------------------------------------------------------
SPU(320, "CHRESET" ,      1,    1.0,    0,         "CHAN",  "",      "Reset channel")
SPU(321, "CHSTART",       1,    1.0,    0,         "CHAN",  "",      "Start sound on channel")
SPU(322, "CHSTOP",        1,    1.0,    0,         "CHAN",  "",      "Stop sound on channel")
SPU(323, "CHTRIGGER",     1,    1.0,    0,         "CHAN",  "",      "Trigger the ADSR envelope on channel")
SPU(324, "CHRELEASE",     1,    1.0,    0,         "CHAN",  "",      "Release the ADSR envelope on channel")
SPU(325, "RESERVED",      1,    0.0,    0,         "",      "",      "")
SPU(326, "RESERVED",      1,    0.0,    0,         "",      "",      "")
SPU(327, "RESERVED",      1,    0.0,    0,         "",      "",      "")
SPU(328, "RESERVED",      1,    0.0,    0,         "",      "",      "")
SPU(329, "RESERVED",      1,    0.0,    0,         "",      "",      "")
---- Dec 33 -- SPU channel control --------------------------------------------------------------------------------------------------------------
SPU(330, "WSET",          2,    1.0,    0,         "WAVE",  "STRING","Set lookup name for specific wave")
SPU(331, "CHWAVE",        2,    1.0,    0,         "CHAN",  "WAVE",  "Set waveform")
SPU(332, "CHLOOP",        2,    1.0,    0,         "CHAN",  "IDX",   "Set looping mode")
SPU(333, "CHVOLUME",      2,    1.0,    0,         "CHAN",  "X",     "Set volume")
SPU(334, "CHPITCH",       2,    1.0,    0,         "CHAN",  "X",     "Set pitch (value interpretation depends on register)")
SPU(335, "CHMODT",        2,    1.0,    0,         "CHAN",  "X",     "Set LFO modulation type")
SPU(336, "CHMODA",        2,    1.0,    0,         "CHAN",  "X",     "Set LFO modulation amplitude")
SPU(337, "CHMODF",        2,    1.0,    0,         "CHAN",  "X",     "Set LFO modulation frequency")
SPU(338, "CHADSR",        2,    1.0,    0,         "CHAN",  "VEC4F", "Set channel ADSR")
SPU(339, "WLEN",          2,    1.0,    0,         "X",     "WAVE",  "Read sound length in seconds")
