PROGRAM SqView;

{ Low Level Squish Message Base Viewer }

USES Dos,
     Crt,
     Ramon;

VAR BasePath : STRING;
    DFile    : FILE;
    IFile    : FILE;

TYPE _SQBASETYPE = RECORD
                         Len        : WORD;                 { LENGTH OF THIS STRUCTURE! }
                         Rsvd1      : WORD;                 { Reserved word }
                         Num_msg,                           { number of msgs }
                         High_msg,                          { highest msg -  always equal to num_msg }
                         Skip_msg   : LONGINT;              { # of msgs to keep in beginning of area }
                         High_water : LONGINT;              { High water marker (umsgid) }
                         Uid        : LONGINT;              { Last usmgid }
                         Base       : ARRAY[1..80] OF CHAR; { Base name for SquishFile }
                         Begin_frame,                       { Offset of first frame in file }
                         Last_frame,                        { Offset to last frame in file }
                         First_free,                        { Offset of first FREE frame in file }
                         Last_free,                         { Ofs of the last free frame }
                         End_frame  : LONGINT;              { Pointer to end of file }
                         Max_msg    : LONGINT;              { Maximum number of messages }
                         Keep_days  : WORD;                 { Max age of messages }
                         sz_sqhdr   : WORD;                 { Size of fram header }
                         Rsvd2      : ARRAY[1..124] OF BYTE;{ Reserved area }
                   END;

TYPE _SQFHDRTYPE = RECORD
                         Id           : LONGINT;     { sqhdr.id must always equal SQHDRID }
                         Next_frame,                 { pointer to next msg in base }
                         Prev_frame   : LONGINT;     { pointer to prior msg in base }
                         Frame_length,               { length of this frame (not counting header) }
                         Msg_length,                 { length of msg in frame. may be less than
                                                       frame_length if this frame has been recycled. }
                         CLen         : LONGINT;     { Length of the control information. }
                         Frame_type   : WORD;        { Either FRAME_MESSAGE or FRAME_FREE. The API
                                                       has been designed to allow things such
                                                       as FRAME_LZSS or FRAME_LZH to be hacked on
                                                       later, without changing the application. }
                         rsvd         : WORD;        { Reserved }
                   END;

TYPE _ADDRESS = RECORD
                      Z,N,F,P : WORD;
                END;

TYPE _SQMHDRTYPE = RECORD
                         Attr         : LONGINT;
                         FromWhom     : ARRAY[1..36] OF CHAR;
                         ToWhom       : ARRAY[1..36] OF CHAR;
                         Subj         : ARRAY[1..72] OF CHAR;
                         Orig,
                         Dest         : _ADDRESS;   { Origination and destination addresses }
                         Date_written,              { When user wrote the msg (UTC) }
                         Date_arrived : LONGINT;    { When msg arrived on-line (UTC) }
                         Utc_ofs      : WORD;       { Minutes offset from UTC of message writer }
                         Replyto      : LONGINT;
                         Replies      : ARRAY[1..10] OF LONGINT;
                         Azdate       : ARRAY[1..20] OF CHAR;  { ASCII date }
                   END;

VAR ErrorOnly : BOOLEAN;
    HaveError : BOOLEAN;

{--------------------------------------------------------------------------}
{ PrintFrameHdr                                                            }
{                                                                          }
PROCEDURE PrintFrameHdr (VAR FrameHdr : _SQFHDRTYPE; Pos,PrevPos : LONGINT);
BEGIN
     Write ('   Id:          $',Long2HexString (FrameHdr.Id));
     IF (FrameHdr.Id = $AFAE4453) THEN
        WriteLn (' (OK)')
     ELSE BEGIN
          TextColor (White);
          WriteLn (' *** ERROR ***'); HaveError:=TRUE;
          TextColor (LightGray);
     END;


     WriteLn ('   NextFrame:   ',FrameHdr.Next_frame);

     Write ('   PrevFrame:   ',FrameHdr.Prev_frame);
     IF (PrevPos <> -1) THEN
     BEGIN
          IF (PrevPos <> FrameHdr.Prev_Frame) THEN
          BEGIN
               TextColor (White);
               WriteLn (' *** ERROR: Actual = ',PrevPos); HaveError:=TRUE;
               TextColor (LightGray);
          END ELSE
              WriteLn (' (OK)');
     END ELSE
         WriteLn;

     Write   ('   FrameLength: ',FrameHdr.Frame_length);
     IF (Pos+FrameHdr.Frame_Length > FileSize (DFile)) THEN
     BEGIN
          TextColor (White);
          WriteLn (' ** ERROR **'); HaveError:=TRUE;
          TextColor (LightGray);
     END ELSE
         WriteLn (' (OK)');

     Write   ('   MsgLength:   ',FrameHdr.Msg_length);
     IF (FrameHdr.Msg_length > FrameHdr.Frame_length) THEN
     BEGIN
          TextColor (White);
          WriteLn (' ** ERROR ** (longer then frame)'); HaveError:=TRUE;
          TextColor (LightGray);
     END ELSE
         WriteLn (' (OK)');

     WriteLn ('   CLen:        ',FrameHdr.CLen);

     Write   ('   FrameType:   ',FrameHdr.Frame_Type,' (');
     CASE FrameHdr.Frame_Type OF
          0 : Write ('message');
          1 : Write ('free');
          ELSE BEGIN
               TextColor (White);
               Write ('** UNKNOWN **'); HaveError:=TRUE;
               TextColor (LightGray);
          END;
     END;
     WriteLn (')');

     WriteLn ('   Reserved:    ',FrameHdr.rsvd);
END;


{--------------------------------------------------------------------------}
{ PrintMsgHdr                                                              }
{                                                                          }
PROCEDURE PrintMsgHdr (VAR MsgHdr : _SQMHDRTYPE);

VAR Lp : BYTE;

BEGIN
     WriteLn ('   -- Message Header --');

     Write   ('   Attr:        $',Long2HexString (MsgHdr.Attr),' -> ');

     IF ((MsgHdr.Attr AND $0001) > 0) THEN Write ('Private ');
     IF ((MsgHdr.Attr AND $0002) > 0) THEN Write ('Crash ');
     IF ((MsgHdr.Attr AND $0004) > 0) THEN Write ('Read ');
     IF ((MsgHdr.Attr AND $0008) > 0) THEN Write ('Sent ');
     IF ((MsgHdr.Attr AND $0010) > 0) THEN Write ('File ');
     IF ((MsgHdr.Attr AND $0020) > 0) THEN Write ('Fwd ');
     IF ((MsgHdr.Attr AND $0040) > 0) THEN Write ('Orphan ');
     IF ((MsgHdr.Attr AND $0080) > 0) THEN Write ('Kill ');
     IF ((MsgHdr.Attr AND $0100) > 0) THEN Write ('Local ');
     IF ((MsgHdr.Attr AND $0200) > 0) THEN Write ('Hold ');
     IF ((MsgHdr.Attr AND $0400) > 0) THEN Write ('XX2 ');
     IF ((MsgHdr.Attr AND $0800) > 0) THEN Write ('Frq ');
     IF ((MsgHdr.Attr AND $1000) > 0) THEN Write ('Rrq ');
     IF ((MsgHdr.Attr AND $2000) > 0) THEN Write ('Cpt ');
     IF ((MsgHdr.Attr AND $4000) > 0) THEN Write ('Arq ');
     IF ((MsgHdr.Attr AND $8000) > 0) THEN Write ('Urq ');

     WriteLn;

     Write   ('   From:        "');
     FOR Lp:=1 TO 36 DO
         IF (MsgHdr.FromWhom[Lp] = #0) THEN
            Break
         ELSE
             IF (MsgHdr.FromWhom[Lp] IN [#32..#126]) THEN
                Write (MsgHdr.FromWhom[Lp])
             ELSE
                 Write ('<',Byte (MsgHdr.FromWhom[Lp]),'>');
     WriteLn ('"');

     Write   ('   To:          "');
     FOR Lp:=1 TO 36 DO
         IF (MsgHdr.ToWhom[Lp] = #0) THEN
            Break
         ELSE
             IF (MsgHdr.ToWhom[Lp] IN [#32..#126]) THEN
                Write (MsgHdr.ToWhom[Lp])
             ELSE
                 Write ('<',Byte (MsgHdr.ToWhom[Lp]),'>');
     WriteLn ('"');

     Write   ('   Subject:     "');
     FOR Lp:=1 TO 72 DO
         IF (MsgHdr.Subj[Lp] = #0) THEN
            Break
         ELSE
             IF (MsgHdr.Subj[Lp] IN [#32..#126]) THEN
                Write (MsgHdr.Subj[Lp])
             ELSE
                 Write ('<',Byte (MsgHdr.Subj[Lp]),'>');
     WriteLn ('"');

     WriteLn ('   Orig:        ',MsgHdr.Orig.Z,':',MsgHdr.Orig.N,'/',MsgHdr.Orig.F,'.',MsgHdr.Orig.P);
     WriteLn ('   Dest:        ',MsgHdr.Dest.Z,':',MsgHdr.Dest.N,'/',MsgHdr.Dest.F,'.',MsgHdr.Dest.P);

     WriteLn ('   DateWritten: $',Long2HexString (MsgHdr.Date_written));
     WriteLn ('   DateArrived: $',Long2HexString (MsgHdr.Date_arrived));
     WriteLn ('   UtsOfs:      ',MsgHdr.Utc_ofs);
     WriteLn ('   ReplyTo:     ',MsgHdr.ReplyTo);

     Write   ('   Replies:     ');
     FOR Lp:=1 TO 5 DO
         Write (Long2HexString (MsgHdr.Replies[Lp]),' ');
     WriteLn;
     Write   ('                ');
     FOR Lp:=1 TO 5 DO
         Write (Long2HexString (MsgHdr.Replies[Lp]),' ');
     WriteLn;

     Write   ('   AzDate:      "');
     FOR Lp:=1 TO 20 DO
         IF (MsgHdr.AzDate[Lp] IN [#32..#126]) THEN
            Write (MsgHdr.AzDate[Lp])
         ELSE
             Write ('<',Byte (MsgHdr.AzDate[Lp]),'>');
     WriteLn ('"');
END;


{--------------------------------------------------------------------------}
{ FindNextNul                                                              }
{                                                                          }
{ Deze routine zoekt vanaf de huidige position naar de volgende NUL in de  }
{ file en geeft terug hoeveel bytes dat waren.                             }
{                                                                          }
FUNCTION FindNextNul : LONGINT;

VAR P         : LONGINT;
    Buf       : ARRAY[1..128] OF BYTE;
    BytesRead : WORD;
    Lp        : BYTE;
    Count     : LONGINT;

LABEL Loop;

BEGIN
     Count:=0;

Loop:
     P:=FilePos (DFile);
     BlockRead (DFile,Buf,128,BytesRead);

     IF (BytesRead = 0) THEN
     BEGIN
          FindNextNul:=Count;
          Exit;
     END;

     FOR Lp:=1 TO BytesRead DO
         IF (Buf[Lp] = 0) THEN
         BEGIN
              FindNextNul:=Count+Lp;
              Seek (DFile,P+Lp);
              Exit;
         END;

     Inc (Count,128);
     GOTO Loop;
END;


{--------------------------------------------------------------------------}
{ PrintFrameList                                                           }
{                                                                          }
PROCEDURE PrintFrameList (Pos : LONGINT; Thread : BOOLEAN);

VAR FrameHdr : _SQFHDRTYPE;
    PrevPos  : LONGINT;
    MsgHdr   : _SQMHDRTYPE;

    Actual1,
    Actual2  : LONGINT;

BEGIN
     WriteLn;
     WriteLn ('-- Frame List -----------------------------');

     IF Thread THEN
        PrevPos:=0
     ELSE
         PrevPos:=-1;

     WHILE (Pos <> 0) AND (Pos < FileSize (DFile)) DO
     BEGIN
          HaveError:=FALSE;

          WriteLn;
          WriteLn ('   --- Frame Pos: ',Pos);

          Seek (DFile,Pos);
          BlockRead (DFile,FrameHdr,SizeOf (_SQFHDRTYPE));
          PrintFrameHdr (FrameHdr,Pos,PrevPos);

          BlockRead (DFile,MsgHdr,SizeOf (_SQMHDRTYPE));
          PrintMsgHdr (MsgHdr);

          Actual1:=FindNextNul;
          Actual2:=FindNextNul;

          WriteLn ('   --- Actual sizes --');
          Write   ('   CLen:        ',Actual1);
          IF (Actual1 = FrameHdr.CLen) THEN
             WriteLn (' (OK)')
          ELSE BEGIN
               TextColor (White);
               WriteLn (' ** ERROR **'); HaveError:=TRUE;
               TextColor (LightGray);
          END;

          WriteLn ('   Body:        ',Actual2);
          WriteLn ('   Sum:         ',Actual1+Actual2);
          Write   ('   +MsgHeader:  ',Actual1+Actual2+SizeOf (_SQMHDRTYPE));
          IF (Actual1+Actual2+SizeOf (_SQMHDRTYPE) = FrameHdr.Msg_length) THEN
             WriteLn (' (OK)')
          ELSE BEGIN
               TextColor (White);
               WriteLn (' ** ERROR **'); HaveError:=TRUE;
               TextColor (LightGray);
          END;

          IF Thread THEN
          BEGIN
               PrevPos:=Pos;
               Pos:=FrameHdr.Next_Frame;
          END ELSE
              Pos:=Pos+SizeOf (_SQFHDRTYPE)+FrameHdr.Frame_Length;

          IF (NOT ErrorOnly) OR HaveError THEN
             IF (ReadKey = kEsc) THEN
                Break;
     END;

     WriteLn ('-- End of Frame List ----------------------');
END;


{--------------------------------------------------------------------------}
{ ViewBase                                                                 }
{                                                                          }
PROCEDURE ViewBase;

VAR BaseHdr : _SQBASETYPE;
    Lp      : BYTE;

BEGIN
     Seek (DFile,0);
     BlockRead (DFile,BaseHdr,SizeOf (_SQBASETYPE));

     WriteLn;
     WriteLn ('-- Base Header ----------------------------');
     WriteLn ('Length:     ',BaseHdr.Len);
     WriteLn ('Reserved:   ',BaseHdr.Rsvd1);
     WriteLn ('NumMsgs:    ',BaseHdr.Num_msg);
     WriteLn ('HighMsg:    ',BaseHdr.High_msg);
     WriteLn ('SkipMsg:    ',BaseHdr.Skip_msg);
     WriteLn ('HighWater:  ',BaseHdr.High_water);
     WriteLn ('Uid:        ',BaseHdr.Uid);
     Write   ('Base:       "');
     FOR Lp:=1 TO 80 DO
     BEGIN
          IF (BaseHdr.Base[Lp] = #0) THEN
             Break;

          IF (BaseHdr.Base[Lp] IN [#32..#126]) THEN
             Write (BaseHdr.Base[Lp])
          ELSE
              Write ('<',Byte (BaseHdr.Base[Lp]),'>');
     END;
     WriteLn ('"');
     WriteLn ('BeginFrame: ',BaseHdr.Begin_Frame);
     WriteLn ('LastFrame:  ',BaseHdr.Last_Frame);
     WriteLn ('FirstFree:  ',BaseHdr.First_Free);
     WriteLn ('LastFree:   ',BaseHdr.Last_Free);
     Write   ('EndFrame:   ',BaseHdr.End_Frame);
     IF (BaseHdr.End_Frame <> FileSize (DFile)) THEN
     BEGIN
          TextColor (White);
          WriteLn (' ** ERROR **');
          TextColor (LightGray);
     END;

     WriteLn ('MaxMsgs:    ',BaseHdr.Max_msg);
     WriteLn ('KeepDays:   ',BaseHdr.Keep_days);
     WriteLn ('sz_sqhdr:   ',BaseHdr.sz_sqhdr);
     WriteLn ('Reserved:   ...');
     WriteLn ('-- End of Base Header ---------------------');

     WriteLn ('Press Enter for messages frame list');
     IF (ReadKey = kRet) THEN
        PrintFrameList (BaseHdr.Begin_frame,TRUE);

     WriteLn ('Press Enter for free frame list');
     IF (ReadKey = kRet) THEN
        PrintFrameList (BaseHdr.First_Free,TRUE);

     WriteLn ('Press Enter to list all frames');
     IF (ReadKey = kRet) THEN
        PrintFrameList (SizeOf (_SQBASETYPE),FALSE);
END;


BEGIN
     TextColor (LightGray);

     WriteLn ('SqView v1.00 - 970427');
     WriteLn ('Written by Ramon van der Winkel');
     WriteLn;

     IF (ParamCount = 0) THEN
     BEGIN
          WriteLn ('Usage: sqview <message base filename>');
          WriteLn ('Example: sqview c:\msgbases\asqbase');
     END ELSE
     BEGIN
          BasePath:=FExpand (ParamStr (1));

          Assign (DFile,BasePath+'.SQD');
          Reset (DFile,1);

          Assign (IFile,BasePath+'.SQI');
          Reset (IFile,1);

          ErrorOnly:=(ParamCount > 1);

          ViewBase;

          Close (IFile);
          Close (DFile);
     END;

     WriteLn;
     WriteLn ('Program ended');
END.
