Contributor: KELLY DROWN              

{
> I am trying to write a program.  Does anyone have the structures for the
> FIDONET *.MSG format.  ANy help would be greatly appreciated.
}

Unit FidoNet; { Beta Copy - Rev 6/5/89 - Tested 6/20/89  Ver. 0.31 }

           { FIDONET UNIT by Kelly Drown, Copyright (C)1988,89-LCP  }
           {                                   All rights reserved  }
           { If you use this unit in your own programming, I ask    }
           { only that you give me credit in your documentation.    }
           { I ask this instead of money. All of the following code }
           { is covered under the copyright of Laser Computing Co.  }
           { and may be used in your own programming provided the   }
           { terms above have been satisfactorily met.              }

INTERFACE

Uses
  Dos,
  Crt,
  StrnTTT5,  { TechnoJocks Turbo Toolkit v5.0 }
  MiscTTT5;


Type
  NetMsg = Record        { NetMessage Record Structure }
    From,
    Too        : String[35];
    Subject    : String[71];
    Date       : String[19];
    TimesRead,
    DestNode,
    OrigNode,
    Cost,
    OrigNet,
    DestNet,
    ReplyTo,
    Attr,
    NextReply  : Word;
    AreaName   : String[20];
  End;

  PktHeader = Record        { Packet Header of Packet }
    OrigNode,
    DestNode,
    Year,
    Month,
    Day,
    Hour,
    Minute,
    Second,
    Baud,
    OrigNet,
    DestNet  : Word;
  End;

  PktMessage = Record        { Packet Header of each individual message }
    OrigNode,
    DestNode,
    OrigNet,
    DestNet,
    Attr,
    Cost     : Word;
    Date     : String[19];
    Too      : String[35];
    From     : String[35];
    Subject  : String[71];
    AreaName : String[20];
  End;

  ArchiveName = Record        { Internal Record Structure used for     }
    MyNet,                    { determining the name of of an echomail }
    MyNode,                   { archive. i.e. 00FA1FD3.MO1             }
    HisNet,
    HisNode : Word;
  End;

Const                        { Attribute Flags }
  _Private  = $0001;
  _Crash    = $0002;
  _Recvd    = $0004;
  _Sent     = $0008;
  _File     = $0010;
  _Forward  = $0020;     { Also know as In-Transit }
  _Orphan   = $0040;
  _KillSent = $0080;
  _Local    = $0100;
  _Hold     = $0200;
  _Freq     = $0800;

  Status    : Array[1..12] Of String[3] =
                ('Jan','Feb','Mar','Apr','May','Jun',
                 'Jul','Aug','Sep','Oct','Nov','Dec');

Var
  Net  : NetMsg;
  PH   : PktHeader;
  PM   : PktMessage;
  ArcN : ArchiveName;

Function  PacketName : String;
Function  PacketMessage : String;
Function  PacketHeader : String;
Function  NetMessage : String;
Function  GetPath(Var FName : String) : Boolean;
Function  GetNet(GN : String) : String;
Function  GetNode(GN : String) : String;
Function  MsgDateStamp : String;
Function  LastMsgNum(_NetPath : String) : Integer;
Function  Hex(n : word) : String;
Function  ArcName : String;
Procedure ExpandNodeNumbers(Var List : String; VAR TotalNumber : Integer);
Procedure Conv_NetNode(NetNode : String; VAR Net, Node : Word);

IMPLEMENTATION

{-------------------------------------------------------------------------}
Function PacketName : String;
{ Creates and returns a unique Packet name }
Var
  h, m, s,
  hs, yr,
  mo, da,
  dow     : Word;
  WrkStr  : String;
Begin
  WrkStr := '';
  GetTime(h, m, s, hs);
  GetDate(yr, mo, da, dow);

  WrkStr := PadRight(Int_To_Str(da), 2, '0')
           + PadRight(Int_To_Str(h), 2, '0')
           + PadRight(Int_To_Str(m), 2, '0')
           + PadRight(Int_To_Str(s), 2, '0');

  PacketName := WrkStr + '.PKT';
End;
{-------------------------------------------------------------------------}
Function PacketMessage : String;
{ Returns a Packet message header }
Var
  Hdr : String;
Begin
  Hdr := '';

  Hdr := #2#0 { Type #2 packets... Type #1 is obsolete }
         + Chr(Lo(PM.OrigNode)) + Chr(Hi(PM.OrigNode))
         + Chr(Lo(PM.DestNode)) + Chr(Hi(PM.DestNode))
         + Chr(Lo(PM.OrigNet)) + Chr(Hi(PM.OrigNet))
         + Chr(Lo(PM.DestNet)) + Chr(Hi(PM.DestNet))
         + Chr(Lo(PM.Attr)) + Chr(Hi(PM.Attr))
         + Chr(Lo(PM.Cost)) + Chr(Hi(PM.Cost))
         + PM.Date + #0 + PM.Too + #0 + PM.From + #0 + PM.Subject + #0
         + Upper(PM.AreaName);

  PacketMessage := Hdr;
End;
{-------------------------------------------------------------------------}
Function PacketHeader : String;
{ Returns a Packet Header String }
Var
  Hdr : String;
Begin
  Hdr := '';

  Hdr := Chr(Lo(PH.OrigNode)) + Chr(Hi(PH.OrigNode))
         + Chr(Lo(PH.DestNode)) + Chr(Hi(PH.DestNode))
         + Chr(Lo(PH.Year)) + Chr(Hi(PH.Year))
         + Chr(Lo(PH.Month)) + Chr(Hi(PH.Month))
         + Chr(Lo(PH.Day)) + Chr(Hi(PH.Day))
         + Chr(Lo(PH.Hour)) + Chr(Hi(PH.Hour))
         + Chr(Lo(PH.Minute)) + Chr(Hi(PH.Minute))
         + Chr(Lo(PH.Second)) + Chr(Hi(PH.Second))
         + Chr(Lo(PH.Baud)) + Chr(Hi(PH.Baud))
         + #2#0 + Chr(Lo(PH.OrigNet)) + Chr(Hi(PH.OrigNet))
         + Chr(Lo(PH.DestNet)) + Chr(Hi(PH.DestNet))
         + #0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0   { Null Field Fill Space }
         + #0#0#0#0#0#0#0#0#0#0#0#0#0#0#0;

  PacketHeader := Hdr;
End;
{-------------------------------------------------------------------------}
Function NetMessage : String;
{ Returns a NetMessage header string }
Var
  Hdr : String;
Begin
  Hdr := '';

  Hdr := PadLeft(Net.From, 36, #0);
  Hdr := Hdr + PadLeft(Net.Too, 36, #0)
             + PadLeft(Net.Subject, 72, #0)
             + PadRight(Net.Date, 19, ' ') + #0
             + Chr(Lo(Net.TimesRead)) + Chr(Hi(Net.TimesRead))
             + Chr(Lo(Net.DestNode)) + Chr(Hi(Net.DestNode))
             + Chr(Lo(Net.OrigNode)) + Chr(Hi(Net.OrigNode))
             + Chr(Lo(Net.Cost)) + Chr(Hi(Net.Cost))
             + Chr(Lo(Net.OrigNet)) + Chr(Hi(Net.OrigNet))
             + Chr(Lo(Net.DestNet)) + Chr(Hi(Net.DestNet))
             + #0#0#0#0#0#0#0#0
             + Chr(Lo(Net.ReplyTo)) + Chr(Hi(Net.ReplyTo))
             + Chr(Lo(Net.Attr)) + Chr(Hi(Net.Attr))
             + Chr(Lo(Net.NextReply)) + Chr(Hi(Net.NextReply))
             + Upper(Net.AreaName);

  NetMessage := Hdr;
End;
{-------------------------------------------------------------------------}
Function GetPath(Var FName : String) : Boolean;
{ Returns the FULL Path and filename for a filename if the file  }
{ is found in the path. }
Var
  Str1,
  Str2    : String;
  NR      : Byte;
  HomeDir : String;
Begin
  HomeDir := FExpand(FName);
  If Exist(HomeDir) Then
  Begin
    FName   := HomeDir;
    GetPath := True;
    Exit;
  End;

  Str1 := GetEnv('PATH');
  For NR := 1 to Length(Str1) DO
    IF Str1[NR] = ';' Then
      Str1[NR] := ' ';

  For NR := 1 to WordCnt(Str1) DO
  Begin
    Str2 := ExtractWords(NR, 1, Str1) + '\' + FName;
    IF Exist(Str2) Then
    Begin
      FName   := Str2;
      GetPath := True;
      Exit;
    End;
  End;
  GetPath := False;
End;

{-------------------------------------------------------------------------}
Function MsgDateStamp : String;  { Creates Fido standard- 01 Jan 89 21:05:18 }
Var                              { Standard message header time/date stamp   }
  h, m, s,
  hs, y, mo,
  d, dow    : Word;
  Tmp, o1,
  o2, o3    : String;

Begin
  o1  := '';
  o2  := '';
  o3  := '';
  tmp := '';
  GetDate(y, mo, d, dow);
  GetTime(h, m, s, hs);
  o1  := PadRight(Int_To_Str(d), 2, '0');
  o2  := Status[mo];
  o3  := Last(2,Int_To_Str(y));
  Tmp := Concat(o1, ' ', o2, ' ', o3,'  ');
  o1  := PadRight(Int_To_Str(h), 2, '0');
  o2  := PadRight(Int_To_Str(m), 2, '0');
  o3  := PadRight(Int_To_Str(s), 2, '0');
  Tmp := Tmp + Concat(o1, ':', o2, ':', o3);
  MsgDateStamp := Tmp;
End;

{-------------------------------------------------------------------------}
Function MsgToNum(Fnm : String) : Integer; { Used Internally by LastMsgNum }
Var
  p : Byte;
Begin
  p        := Pos('.', Fnm);
  Fnm      := First(p - 1, Fnm);
  MsgToNum := Str_To_Int(Fnm);
End;
{-------------------------------------------------------------------------}

Function LastMsgNum(_NetPath : String) : Integer;
{ Returns the highest numbered xxx.MSG in NetPath directory }
Var
  _Path,
  Temp1,
  Temp2   : String;
  Len     : Byte;
  DxirInf : SearchRec;
  Num,
  Num1    : Integer;

Begin
  Num   := 0;
  Num1  := 0;
  Temp1 := '';
  Temp2 := '';
  _Path := '';
  _Path := _NetPath + '\*.MSG';

  FindFirst(_Path, Archive, DxirInf);
  While DosError = 0 DO
  Begin
    Temp1 := DxirInf.Name;
    Num1 := MsgToNum(Temp1);
    IF Num1 > Num Then
      Num := Num1;
    FindNext(DxirInf);
  End;

  IF Num = 0 Then
    Num := 1;
  LastMsgNum := Num;
End;

{-------------------------------------------------------------------------}
Function Hex(N : Word) : String;
{ Converts an integer or word to it's Hex equivelent }
Var
  L   : string[16];
  BHi,
  BLo : byte;

Begin
  L   := '0123456789ABCDEF';
  BHi := Hi(n);
  BLo := Lo(n);
  Hex := copy(L,succ(BHi shr 4),  1) +
         copy(L,succ(BHi and 15), 1) +
         copy(L,succ(BLo shr 4),  1) +
         copy(L,succ(BLo and 15), 1);
End;

{-------------------------------------------------------------------------}
Function ArcName : String;
{ Returns the proper name of an echomail archive }
Var
  C1, C2 : LongInt;
Begin
  C1 := 0;
  C2 := 0;
  C1 := ArcN.MyNet - ArcN.HisNet;
  C2 := ArcN.MyNode - ArcN.HisNode;
  If C1 < 0 Then
    C1 := 65535 + C1;
  If C2 < 0 Then
    C2 := 65535 + C2;
  ArcName := Hex(C1) + Hex(C2);
End;

{-------------------------------------------------------------------------}
Function GetNet(GN : String) : String;
{ Returns the NET portion of a Net/Node string }
Var
  P : Byte;
Begin
  P := Pos('/', GN);
  GetNet := First(P - 1, GN);
End;

{-------------------------------------------------------------------------}
Function GetNode(GN : String) : String;
{ Returns the NODE portion of a Net/Node string }
Var
  P : Byte;
Begin
  P := Pos('/', GN);
  GetNode := Last(Length(GN) - P, GN);
End;
{-------------------------------------------------------------------------}
Procedure ExpandNodeNumbers(Var List : String; VAR TotalNumber : Integer );
{ Expands a list of short form node numbers to thier proper       }
{ Net/Node representations. Example:                              }
{ The string: 170/100 101 102 5 114/12 15 17 166/225 226          }
{ Would return: 170/100 170/101 170/102 170/5 114/12 114/15 etc.. }
Var
  Net,
  NetNode  : String[10];
  HoldStr,
  WS1      : String;
  N1       : Integer;

Begin
  Net := '';
  NetNode := '';
  HoldStr := '';
  WS1 := '';
  N1  := 0;
  TotalNumber := 0;
  TotalNumber := WordCnt(List);

  For N1 := 1 to TotalNumber DO
  Begin
    WS1 := ExtractWords(N1, 1, List);
    IF Pos('/', WS1) <> 0 Then
    Begin
      Net := GetNet(WS1) + '/';
      NetNode := WS1;
    End
    ELSE
      NetNode := Net + WS1;
    HoldStr := HoldStr + ' ' + Strip('A', ' ', NetNode);
  End;
End;

{-------------------------------------------------------------------------}
Procedure Conv_NetNode(NetNode : String; VAR Net, Node : Word);
{ Returns NET and NODE as words from a Net/Node string }
Var
  WStr : String[6];
Begin
  Wstr := GetNet(NetNode);
  Net  := Str_To_Int(Wstr);
  Wstr := GetNode(NetNode);
  Node := Str_To_Int(Wstr);
End;
{-------------------------------------------------------------------------}

Begin
  { Initialize the data structures }

  FillChar(Net, SizeOf(Net), #0);
  FillChar(PM, SizeOf(PM), #0);
  FillChar(PH, SizeOf(PH), #0);
  FillChar(ArcN, SizeOf(ArcN), #0);

End. {Unit}