/* CLASS: VIRTDASM
   This Virtual Dasm Object is a  VirtualDataObject which specializes
   in storing memory disassembly data.
   It implements the VirtDataObject protocol for memory disassembly using
   fixed-sized chunks of memory addresses range (i.e. startAddr - endAddr).

   REQUIRE: VIRTDATA.CLS, CHUNKMAN.CLS
*/
!!

inherit(VirtDataObject, #VirtDasmObject, #(
dasmSession /* Server session ID */
aMemoryRange /* Addr Range Descriptor */
), 2, nil)!!

now(class(VirtDasmObject))!!

/* 5/29/1992 10:32 - PUBLIC 
  Open a new Virtual Dasm Object representing the specified memory range.
  NOTES: Do not destroy anAddrRangeDesc after calling open(). It will will
    destroy by the object when it's done.
  Ex: 
    open(VirtDasmObject, anAddrRangeDesc). 
 */
Def open(self, anAddrRange | aVirtualDasmObject)
{ 
  aVirtualDasmObject := new(self:ancestor);
  ^init(aVirtualDasmObject, anAddrRange);
}!!

now(VirtDasmObject)!!

/* 6/26/1992 8:55 - PUBLIC 
   Calculate number of lines in chunk.
*/
Def getChunkSize(self, chunkNumber)
{ 
  /* rememberedLocations = #(startOffset, numLinesInChunk, startLineInChunk) */
  ^at(rememberedLocations,chunkNumber)[1];
}

!!

/* 6/26/1992 8:55 - PUBLIC 
   Calculate starting line number of chunk.
   The 1st chunks's line numbers range from 1 to linesPerChunk, and so on...
*/
Def getChunkStartLine(self, chunkNumber)
{ 
  /* rememberedLocations = #(startOffset, numLinesInChunk, startLineInChunk) */
  ^at(rememberedLocations,chunkNumber)[2];
}

!!

/* 1/8/1993 11:00 - PRIVATE 
  At end of virtual data object, set its end offset to the actual 
  end offset.
*/
Def resetEndOffsetRange(self, newEndOffset | startOffset temp)
{ 
  /* Check startOffset > newEndOffset (upper bits are ON) then reset startOffset */
  startOffset := getOffset(AddressLibClass$Inst, aMemoryRange);
  temp := compareOffsets(AddressLibClass$Inst, startOffset, newEndOffset);
  
  if (temp = ADRLIB_ADDR_GREATER_THAN) then /* startOffset > newEndOffset */
     setOffset(AddressLibClass$Inst, aMemoryRange, 
     (startOffset bitAnd maxOutputOffset(AddressLibClass$Inst,aMemoryRange)));
  endif;   
  /* Set the endOffset */
  setEndAddrOffset(AddressLibClass$Inst, aMemoryRange, newEndOffset);     
  /* DEBUG
  printLine("New address Range: "+asHex(getOffset(AddressLibClass$Inst, aMemoryRange))+" - "+
    asHex(getEndOffset(AddressLibClass$Inst, aMemoryRange)));
  */  
}
!!

/* 6/25/1992 13:22 - PRIVATE
  Check to see if an address offset is at the end od range.
  NOTES: Simulating the atEnd() function for the virtDasmObject.
*/
Def atEnd(self, addrOffset | endOffset temp)
{ 
  if not(endOffset := getEndOffset(AddressLibClass$Inst, aMemoryRange)) then
    ^nil;
  endif;
  /* Compare if addrOffset < endOffset */
  temp := compareOffsets(AddressLibClass$Inst, addrOffset, endOffset);
  if ((temp = ADRLIB_ADDR_GREATER_THAN) cor 
      (temp = ADRLIB_ADDR_EQUAL)) /* addrOffset >= endOffset */ 
    ^0;
  else
    ^nil;
  endif;
}
!!

/* 7/2/1992 14:03 - PRIVATE 
  Build a dasm chunk from a know dasm position #(dasmStartOffset, numLinesInChunk, startLineOfChunk).
  NOTES: dasmPos = #(dasmStartOffset, numLinesInChunk, startLineInChunk)
  Return tuple: #(StartLineNumber, TextCollection). 
*/
Def buildKnownChunk(self, dasmPos, chunkNum | dasmCol)
{   
  /* NOTES: dasmPos = #(startOffset, numLinesInChunk, startLineOfChunk) */   
  /* Move to the desired offset position */
  if not(simpleSetOffset(DisAsmLibClass$Inst, dasmSession, dasmPos[0])) then
    ^nil;
  endif;
  /* Get the numLinesInChunk @ startOffset */  
  if (dasmCol := getTextOfChunk(self, dasmPos[1], dasmPos[0])) then
    ^tuple(firstLineOfChunk(self, chunkNum), dasmCol);
  endif;
  ^nil; /* Something wrong here */
}
!!

/* 5/29/1992 13:04 - PRIVATE 
   Get a randomized dasm chunk. Memorize chunks for later accessing (in rememberedLocations).
   If find AtEnd, set totalLinesExact. If beyond AtEnd, return nil.
*/
Def buildRandomChunk(self, chunkWanted | chunkNum, oldLoc, dasmCol,
  line, dasmLine, newAddr, linesInChunk)
{ 
  /* Move as far as we know how */
  if chunkNum := highestChunkSeen then
    /* NOTES: oldLoc = #(startOffset, NumLinesInChunk, startLineOfChunk) */
    if not(oldLoc := at(rememberedLocations, chunkNum)) then
      ^nil;
    endif;
    newAddr := oldLoc[0];
    dasmLine := oldLoc[2];  /* StartLine of chunk */
    linesInChunk := oldLoc[1];
  else
    /* Begin with chunk #1 and the start offset of address range - Move to location */
    chunkNum := 1; dasmLine := 1; linesInChunk := linesPerChunk;
    newAddr := getOffset(AddressLibClass$Inst, aMemoryRange);
    /* Remember the 1st one*/
    add(rememberedLocations, chunkNum, tuple(newAddr, linesPerChunk, dasmLine));
    highestChunkSeen := chunkNum;
  endif;

  /* Locate the chunk position, get the dasm text of chunk */
  if (newAddr := locateRandomChunk(self, chunkWanted, chunkNum,
      dasmLine, newAddr, linesInChunk)) cand
    (dasmCol := getTextOfChunk(self, linesInChunk, newAddr)) then
    /* At end of data source, set the totallinesExact */
    if atEnd(self, getDasmOffset(self)) and not(totalLinesExact) then  
      setTotalLinesExact(self, (size(dasmCol) - 1 + firstLineOfChunk(self, chunkWanted)));
      /* Reset the endAddress range to the new boundary */
      resetEndOffsetRange(self, getDasmOffset(self));
    endif;

    /* Save the total Lines of chunk and return the chunk */
    (rememberedLocations[chunkWanted])[1] := size(dasmCol);
    ^tuple(firstLineOfChunk(self, chunkWanted), dasmCol);
  endif;
  ^nil;
}
!!

/* 5/29/1992 13:04 - PRIVATE 
   Scan a randomized dasm chunk. Memorize chunks for later accessing
   (in rememberedLocations).
   Return the Address offset of the wanted chunk.
*/
Def locateRandomChunk(self, chunkWanted, chunkNum, dasmLine, newAddr, linesInChunk 
| dasmCol, line )
{ 
  /* Set the start offset for the DASM Server to start disassemble */
  if not(simpleSetOffset(DisAsmLibClass$Inst, dasmSession, newAddr)) then
    ^nil;
  endif;

  /* Locate the chunkWanted from the highestChunkSeen, find starting line, 
   * remembering along the way. Not for the 1st chunk. 
   */
  loop
  while (chunkNum < chunkWanted) cand not(atEnd(self, newAddr))
  begin
    line := 0 ;
    if checkAbort(self) then ^nil; endif;
    loop  /* Find out the next 20 lines offset from the current offset */
    while (line < linesInChunk) cand not(atEnd(self, newAddr))
    begin 
      if not(dasmCol := getTextOfChunk(self, linesInChunk, newAddr)) then
        ^nil; /* Failed to disassemble memory */  
      endif;   
      line := line + size(dasmCol);
      newAddr := getDasmOffset(self);
      dasmCol := nil;
    endLoop;
    /* Save the total Lines of remembered chunk */
    if line cand line > 0 then
      (rememberedLocations[chunkNum])[1] := line;
    else
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_DASM_CANT, FORCE_POPUP, nil, nil, nil);
      ^nil;
    endif;    
    
    /* Check termination conditions */
    if atEnd(self, newAddr) then
      if not(totalLinesExact) then
        /* SetTotalLineExact by callBack to browser to set numLines */
        setTotalLinesExact(self, (line-1 + firstLineOfChunk(self, chunkNum)) );
        /* Reset the endAddress range to the new boundary */
        resetEndOffsetRange(self, getDasmOffset(self));
      endif;  
      ^nil ; /* ran out of memory range */
    else
      /* Advance to next chunk */
      chunkNum := chunkNum + 1 ;   /* must read through next unwanted chunk */
      dasmLine := dasmLine + line; /* Keep track of dasm lines */
      /* Save offset to rememberedLocations[] */
      add(rememberedLocations, chunkNum, tuple(newAddr, linesPerChunk, dasmLine));
      highestChunkSeen := chunkNum;
    endif ;
  endLoop;
  ^newAddr;
}
!!

/* 6/25/1992 11:35 - PUBLIC
   Given a (virtual) line number, return the chunk number which contains it.
   NOTES: rememberedLocations = #(startOffset, numLines, startLines)
*/
Def chunkNumberFromLineNumber(self, lineNumber | chunkNum)
{ 
  /* 
  ** Chunks have the different number of lines, so we have to search for location.
  ** Note that the 1st chunk number is 1. 
  */
  if size(rememberedLocations) = 0 then
    ^1; /* Start with chunk# 1 */
  endif;
  /* rememberLocation = chunk#->#(startOffset, NumLinesInChunk, startLineOfChunk) */
  assocsDo (rememberedLocations,     
    {using(chunk)
      /* If lineNumber is between startLine and endLine of Chunk then return chunk# */
      if (lineNumber >= value(chunk)[2]) cand (lineNumber <= (value(chunk)[1]+value(chunk)[2])) then
        ^key(chunk);
      endif;        
    });
  /* Not found in rememberedLocations */    
  ^1 + size(rememberedLocations);  
}!!

/* 6/25/1992 12:53 - PUBLIC 
  Close Dasm server session.
*/
Def close(self)
{ 
  closed? := #true;
  if dasmSession then
    closeDasm(DisAsmLibClass$Inst, dasmSession);
  endif;  
  dasmSession := nil;
}
!!

/* 6/26/1992 8:55 - PRIVATE 
   Calculate starting line number of chunk.
   The 1st chunks's line numbers range from 1 to linesPerChunk, and so on...
*/
Def firstLineOfChunk(self, chunkNumber)
{ 
  /* rememberedLocations = #(startOffset, numLinesInChunk, startLineInChunk) */
  ^at(rememberedLocations,chunkNumber)[2];
}

!!

/* 6/25/1992 13:39 - PUBLIC (to it descendant)
   If a DataObject has been reOpen()'ed, cached info may be staled.  The user
   can call this function to flush cached info, or not call it to keep
   cached info.
*/
Def flushCachedMemory(self)
{
  highestChunkSeen := totalLinesExact  := nil;
  /* chunk#->(addrOffset, numLines, startLine) */
  if rememberedLocations then
    clear(rememberedLocations);
  else  
    rememberedLocations := new(Dictionary, 2); 
  endif; 
}

!!

/* 6/25/1992 11:39 - PUBLIC
  Given a chunkNumber >= 1, return the tuple: #(startingLineNum, TextCollection).
  If chunk number is beyond the actual address range, then return nil.
*/
Def getChunk(self, chunkNumber | dasmPos, chunk)
{ 
  /* NOTES: dasmPos = #(startOffset, numLineInChunk, startLineOfChunk) */
  if checkAbort(self) then
    ^nil;
  endif;
  if dasmPos := at(rememberedLocations, chunkNumber) then
    chunk := buildKnownChunk(self, dasmPos, chunkNumber);
  else
    chunk := buildRandomChunk(self, chunkNumber);
  endif;   
  ^chunk;
}
!!

/* 7/2/1992 14:52 - PRIVATE
  Return an offset of the current dasm session address position.
*/
Def getDasmOffset(self | addrDesc, addrOffset)
{       
  if not(addrDesc := getAddress(DisAsmLibClass$Inst, dasmSession)) then
     ^nil; /* failed to get current dasm address, bail out */
  endif;
  if not(addrOffset := getOffset(AddressLibClass$Inst, addrDesc)) then /* NONPORTABLE */
    destroyAddress(AddressLibClass$Inst, addrDesc);
    ^nil;
  endif;
  destroyAddress(AddressLibClass$Inst, addrDesc);
  ^addrOffset;
}
!!

/* 7/10/1992 15:48 - PRIVATE
  Get the number of dasm lines at the current setting Dasm server offset position.
  return a dasm textCollection, or nil if error.
  NOTES: Caller needs to make sure that Dasm Server being set with the desired location.
*/
Def getTextOfChunk(self, wantedLines, newAddr | line, textCol, dasmCol, err, errStr, tmpDesc)
{  
  /* Allocate buffer to store dasmCollection */
  dasmCol := new(TextCollection, wantedLines);
  line := 0;
  loop
  while (line < wantedLines) cand not(atEnd(self, newAddr))
  begin 
    /* Get dasm text from the current offset setting of the Dasm Server */
    if (not(textCol := getDasmInstByLines(DisAsmLibClass$Inst, dasmSession,
         wantedLines)) cor (size(textCol) = 0)) then
      /* Return the dasm error buffer or nil -  Unable to diassemble memory */
      if not(dasmCol := getDasmErrorBuffer(self, newAddr, wantedLines))
        ^nil;
      endif;       
      /* Set the end offset to the DASM Server to stop disassemble memory */
      if not(tmpDesc := createAndSetAddress(AddressLibClass$Inst, newAddr))
        ^nil;
      endif;
      
      if not(addToAddress(AddressLibClass$Inst, tmpDesc, (2 * wantedLines)))
        destroyAddress(AddressLibClass$Inst, tmpDesc);
        ^nil;
      endif;
      
      simpleSetOffset(DisAsmLibClass$Inst, dasmSession, 
                    getOffset(AddressLibClass$Inst, tmpDesc));
      destroyAddress(AddressLibClass$Inst,tmpDesc);
      ^dasmCol;
    endif;
    insertAll(dasmCol, textCol, size(dasmCol));  
    line := line + size(textCol);
    newAddr := getDasmOffset(self);
    textCol := nil;
  endLoop;
  /* Clear old dasm server error */
  errReported := GOOD;
  ^dasmCol;
}
!!

/* 7/2/1992 17:01 - PRIVATE
  Initialize; openDasm(VirtDasmObject, addrRange) does most of the work.
*/
Def init(self, anAddrRange)
{ 
  init(self:ancestor);
  ^openDasm(self, anAddrRange);
}
!!

/* 5/29/1992 12:23 - PUBLIC
   Return the number if text lines in self.  Guess if don't know.
*/
Def numLinesIn(self)
{
  /* if we know how many lines exist, say so, else let give a guess */
  if totalLinesExact then
    ^totalLinesExact ;
  else
    ^totalLinesGuess ;
  endif ;
}
!!

/* 5/29/1992 12:11 - PUBLIC (to its child object)
  Open a Dasm Server session for a VirtDasmObject (Called by open()).
  Notes: return nil if error open dasm object.  
*/
Def openDasm(self, addrRangeDesc | line, rangeLength)
{ 
  /* Open the DasmSession then init self data */ 
  if not(dasmSession := openDasm(DisAsmLibClass$Inst, addrRangeDesc)) then
    ^nil; 
  endif;
  /* Save the specified memory range - addrDesc */
  aMemoryRange := addrRangeDesc; 
  /* NOTES: chunk#->(addrOffset, numLines, StartLine) */
  rememberedLocations := new(Dictionary, 2); 
  
  /* Give a guessing number of lines in the virtual dasm object. */
  if (rangeLength := getAddrRangeLength(AddressLibClass$Inst, addrRangeDesc)) then
    /* Guess the totalLineGuess = total bytes / 2 (average 6) bytes per instruction */
    totalLinesGuess := asLong(rangeLength / 2);
  else
    /* Can't get the addressRange to guess */
    totalLinesGuess := totalLinesExact := 0;
    ^nil;
  endif; 
}
!!

/* 6/25/1992 13:42 - PUBLIC
  Reopen the previous opend DasmObject.
*/
Def reOpen(self)
{ 
  if closed? then
    if not(aMemoryRange) cor not(dasmSession := openDasm(DisAsmLibClass$Inst, aMemoryRange)) then
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_DASM_REOPEN, FORCE_POPUP, nil, nil, nil);
      ^nil;
    endif; 
    /* Reopen Ok */  
    closed? := nil; 
  endif;  
  ^self;
}
!!

/* 6/25/1992 13:49 - PUBLIC 
  Set resizeCallBack of the dataObject.
*/
Def setResizeCallback(self, aResizeBlock)
{ 
  resizeCallback := aResizeBlock ;
}

!!

/* 6/25/1992 13:54 - PRIVATE 
  Set the totalLineExact to numLines and resize it.
*/
Def setTotalLinesExact(self, numLines)
{ 
  totalLinesExact := numLines ;
  if resizeCallback then
    eval( resizeCallback, numLines );
  endif;
}
 
!!
