(* GGET.PAS -- Get files from a GEOS disk image
** Copyright (c) 1995,1996 Jochen Metzinger
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2, or (at your option)
** any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*)

PROGRAM Gget;

USES Crt, global, geos, coding, errors, d64, dir;

CONST
 CVT_EXT = '.CVT';


CONST
 FILE_CHARS: SET OF CHAR =
  ['!','#'..')','-','0'..'9','@'..'Z','^'..'{','}','~'];
 DosTypeName: ARRAY [$0..$7] OF STRING[4] =
  ('.DEL', '.SEQ', '.PRG', '.USR', '.REL', '.XX5', '.XX6', '.XX7');

VAR
 d_name, f_name: STRING;
 use_dir: ARRAY [0..9] OF BOOLEAN;
 use_all_dir: BOOLEAN;
 interactiv, no_geos_files, overwrite, SEQ_formatted: BOOLEAN;


PROCEDURE usage;
BEGIN
 WriteLn('GGET  Version 0.3',country,'  Get files from a GEOS disk image');
 WriteLn('Copyright (c) 1995,1996 Jochen Metzinger ');
 WriteLn;
 WriteLn(short_usage);
 WriteLn;
 WriteLn('  image       disk image');
 WriteLn('  filename    file name selector');
 WriteLn('    /1 .. /9    directory page 1 .. 9');
 WriteLn('    /B          border directory');
 WriteLn('    /I          ask for each file');
 WriteLn('    /O          overwrite existed files');
 WriteLn('    /N          do not create GEOS converted files');
 WriteLn('    /S          create SEQ formatted GEOS files');
 Write('    /G          german');
 IF default_code = ge_coding THEN Write(' [default]');
 WriteLn;
 Write('    /E          english');
 IF default_code = uk_coding THEN Write(' [default]');
 WriteLn;
 HALT(1);
END; (* usage *)

PROCEDURE Init;
 VAR i, j: INTEGER; par: STRING;
BEGIN
 short_usage := 'GGET [/?] image filename [options]';
 d_name := '';
 f_name := '';
 FOR i := 0 TO 9 DO
  use_dir[i] := FALSE;
 interactiv := FALSE;
 no_geos_files := FALSE;
 overwrite := FALSE;
 SEQ_formatted := FALSE;
 IF ParamCount = 0 THEN
  usage;
 FOR i := 1 TO ParamCount DO BEGIN
  par := ParamStr(i);
  IF (par[1] = '/') OR (par[1] = '-') THEN
   BEGIN
    IF Length(par) = 1 THEN
     FATAL('unknown option '+par);
    FOR j := 2 TO Length(par) DO
     CASE UpCase(par[j]) OF
      '?', 'H': usage;
      '0'..'9': use_dir[ORD(par[j])-ORD('0')] := TRUE;
      'B': use_dir[0] := TRUE;
      'I': interactiv := TRUE;
      'N': no_geos_files := TRUE;
      'O': overwrite := TRUE;
      'S': SEQ_formatted := TRUE;
      'G': SetCoding(ge_coding);
      'E': SetCoding(uk_coding);
      ELSE FATAL('unknown option '+par);
     END; (* case *)
   END
  ELSE IF d_name = '' THEN
   d_name := AddExt(par, '.D64')
  ELSE IF f_name = '' THEN
   BEGIN
    j := Pos('*',par);
    IF j > 0 THEN Delete(par,j+1,$FF);
    f_name := Copy(par,1,16);
   END
  ELSE
   FATAL('too many arguments');
 END; (* for *)
 IF d_name = '' THEN usage;
 IF f_name = '' THEN f_name := '*';
 d64_open(d_name);
 short_usage := '';
 InitDir;
 use_all_dir := TRUE;
 IF IsGeosDisk THEN
  FOR i := 0 TO 9 DO
   IF use_dir[i] THEN
    use_all_dir := FALSE;
END; (* init *)

FUNCTION MakeDosFileName(gname, ext: STRING): STRING;
(* convert GEOS file names *)
 VAR fn: STRING; i: BYTE;
BEGIN
 FOR i := 1 TO Length(gname) DO
  IF (gname[i] < #32) OR (#127 < gname[i]) THEN
   gname[i] := 'X';
 fn := Copy(TransStr(gname),1,8);
 FOR i := 1 TO Length(fn) DO
  IF fn[i] IN FILE_CHARS THEN
   fn[i] := UpCase(fn[i])
  ELSE IF fn[i] = ' ' THEN
   fn[i] := '_'
  ELSE
   fn[i] := 'X';
 MakeDosFileName := fn + ext;
END; (* MakeDosFileName *)

FUNCTION FileExists(FileName: STRING): BOOLEAN;
(* exists file? *)
 VAR f: FILE;
BEGIN
 (*$I-*)
 Assign(f, FileName);
 FileMode := FileMode_RO;
 Reset(f);
 FileMode := FileMode_RW;
 Close(f);
 (*$I+*)
 FileExists := (IOResult = 0) and (FileName <> '');
END; (* FileExists *)

FUNCTION GetFileHandler(VAR f: FILE; gname, ext: STRING): BOOLEAN;
(* Open file with checking *)
 VAR fn, fn0: STRING; ch, dummy: CHAR;
BEGIN
 GetFileHandler := FALSE;
 fn := MakeDosFileName(gname, ext);
 Write(' -> ',fn);
 IF interactiv THEN BEGIN
  Write(' (Y,n)? ');
  REPEAT
   ch := UpCase(ReadKey);
   IF ch = #0 THEN dummy := ReadKey;
   IF ch = 'N' THEN BEGIN
    WriteLn('N');
    EXIT;
   END; (* if *)
  UNTIL ch IN ['Y',#13];
  Write('Y');
 END; (* if *)
 WriteLn;
 IF NOT overwrite THEN BEGIN
  ch := 'X';
  WHILE (ch <> 'Y') AND FileExists(fn) DO BEGIN
   Write('GGET: Warning! ', fn, ' already exists. Overwrite (y/n/a/r)? ');
   REPEAT
    ch := UpCase(ReadKey);
    IF ch = #0 THEN dummy := ReadKey;
   UNTIL ch IN ['Y','N','A','R'];
   WriteLn(ch);
   CASE ch OF
    'N': EXIT;
    'Y': (* nothing *);
    'A': BEGIN
      ch := 'Y';
      overwrite := TRUE;
     END;
    'R': BEGIN
      Write('New name? ');
      ReadLn(fn0);
      IF fn0 <> '' THEN fn := AddExt(fn0, ext);
     END;
   END; (* case *)
  END; (* while *)
 END; (* if *)
 (*$I-*)
 Assign(f, fn);
 ReWrite(f, 1);
 (*$I+*)
 IF IOResult <> 0
  THEN error('unable to create file')
  ELSE GetFileHandler := TRUE;
END; (* GetFileHandler *)

FUNCTION CopySEQ(VAR t, s: BYTE; VAR f: FILE): WORD;
 VAR block: d64.BLOCK; cnt, res: WORD;
BEGIN
 CopySEQ := 0;
 cnt := 0;
 WHILE t <> 0 DO BEGIN
  d64_read(t,s,block);
  IF is_err THEN EXIT;
  t := block[0];
  s := block[1];
  BlockWrite(f, block[2], SizeOf(block)-2, res);
  IF res <> SizeOf(block)-2 THEN EXIT;
  INC(cnt);
  IF cnt > $FF THEN BEGIN
   ERROR('chain/file too large');
   EXIT;
  END; (* if *)
 END; (* while *)
 t := cnt;
 IF cnt > 0 THEN BEGIN
  IF s < 2 THEN BEGIN
   ERROR('sector used byte counter wrong');
   s := $FF;
  END; (* if *)
  CopySEQ := $FF - s;
 END; (* if *)
END; (* CopySEQ *)

PROCEDURE CbmFile(VAR dr: DirRec);
 VAR ext, m_ext: STRING; head: d64.BLOCK;
  cbm_file: FILE; back: INTEGER;
BEGIN
 ext := DosTypeName[dr.dostype AND $07];
 IF NOT no_geos_files THEN BEGIN
  (* converted file? *)
  d64_read(dr.tr_1st, dr.sc_1st, head);
  IF is_err THEN BEGIN
   WriteLn;
   EXIT;
  END; (* if *)
  IF GetString(head[$23], 25) = ' formatted GEOS file V1.0' THEN BEGIN
   m_ext := GetString(head[$20], 3);
   IF (m_ext = 'SEQ') OR (m_ext = 'PRG') THEN
    ext := CVT_EXT;
  END; (* if *)
 END; (* if *)
 (* Transfer sequential file *)
 IF NOT GetFileHandler(cbm_file,DirFileName(dr),ext) THEN EXIT;
 back := CopySEQ(dr.tr_1st, dr.sc_1st, cbm_file);
 IF back > 0 THEN BEGIN
  Seek(cbm_file, FileSize(cbm_file)-back);
  Truncate(cbm_file);
 END; (* if *)
 Close(cbm_file);
END; (* CbmFile *)

PROCEDURE GeosFile(dr: DirRec);
 VAR cvt_file: FILE;
  head, info, vlir: d64.BLOCK;
  res, back, chain: WORD;
BEGIN
 IF NOT GetFileHandler(cvt_file,DirFileName(dr),CVT_EXT) THEN
  EXIT;
 (* Output pre-header *)
 FillChar(head, SizeOf(head), 0);
 Move(dr, head[$02], SizeOf(dr));
 BlockWrite(cvt_file, head[2], SizeOf(head)-2, res);
 IF res <> SizeOf(head)-2 THEN FATAL('unable to write');
 (* Output info rec *)
 d64_read(dr.tr_info, dr.sc_info, info);
 IF is_err THEN EXIT;
 BlockWrite(cvt_file, info[2], SizeOf(info)-2, res);
 IF res <> SizeOf(info)-2 THEN FATAL('unable to write');
 (* VLIR/SEQ structure *)
 IF dr.struct = 0 THEN
  (* SEQ format *)
  back := CopySEQ(dr.tr_1st, dr.sc_1st, cvt_file)
 ELSE (* VLIR format *) BEGIN
  (* Output pre-vlir *)
  d64_read(dr.tr_1st, dr.sc_1st, vlir);
  IF is_err THEN EXIT;
  BlockWrite(cvt_file, vlir[2], SizeOf(vlir)-2, res);
  IF res <> SizeOf(vlir)-2 THEN FATAL('unable to write');
  (* Transfer chains *)
  back := 0;
  FOR chain := 1 TO 127 DO
   IF vlir[2*chain] <> 0 THEN BEGIN
    back := CopySEQ(vlir[2*chain], vlir[2*chain+1], cvt_file);
    IF is_err THEN EXIT;
   END; (* if *)
  (* Output vlir *)
  Seek(cvt_file, 2*254);
  BlockWrite(cvt_file, vlir[2], SizeOf(vlir)-2, res);
  IF res <> SizeOf(vlir)-2 THEN FATAL('unable to write');
 END; (* else *)
 (* Cut unused last bytes *)
 IF back > 0 THEN BEGIN
  Seek(cvt_file, FileSize(cvt_file)-back);
  Truncate(cvt_file);
 END; (* if *)
 (* Output header *)
 Seek(cvt_file,0);
 PutString(head[$20], 'PRG formatted GEOS file V1.0'#0);
 IF SEQ_formatted THEN PutString(head[$20], 'SEQ');
 PutString(head[$A0], 'JOCHEN METZINGER''S GGET V3.0'#0);
 BlockWrite(cvt_file, head[2], SizeOf(head)-2, res);
 IF res <> SizeOf(head)-2 THEN FATAL('unable to write');
 Close(cvt_file);
END; (* GeosFile *)

PROCEDURE TransferFile(dr: DirRec);
BEGIN
 Write('"',TransStr(DirFileName(dr)),'"');
 WITH dr DO
  IF (dostype AND $07 = 4) OR (geostype = 0) THEN
   CbmFile(dr)
  ELSE IF no_geos_files THEN
   CbmFile(dr)
  ELSE IF struct <= 1 THEN
   GeosFile(dr)
  ELSE BEGIN
   WriteLn;
   ERROR('neither SEQ nor VLIR');
  END; (* else *)
 IF is_err THEN
  err_recover;
END; (* TransferFile *)

FUNCTION IsInPage(page: INTEGER): BOOLEAN;
BEGIN
 IsInPage := TRUE;
 IF use_all_dir THEN EXIT;
 IsInPage := FALSE;
 IF (page < 0) OR (page > 9) THEN EXIT;
 IsInPage := use_dir[page];
END; (* IsInPage *)

FUNCTION LIKE(fn, pt: STRING): BOOLEAN;
 VAR i: INTEGER;
BEGIN
 LIKE := FALSE;
 FOR i := 1 TO Length(pt) DO BEGIN
  IF pt[i] = '*' THEN BEGIN
   LIKE := TRUE;
   EXIT;
  END; (* if *)
  IF Length(fn) < i THEN
   EXIT;
  IF pt[i] <> '?' THEN
   IF pt[i] <> fn[i] THEN
     EXIT;
 END; (* for *)
 LIKE := (Length(fn) = Length(pt));
END; (* LIKE *)

PROCEDURE DoTransfer;
VAR dr: DirRec; page: WORD;
 found: BOOLEAN;
BEGIN
 DiskName := TransStr(DiskName);
 DiskID := TransStr(DiskID);
 DiskOS := TransStr(DiskOS);
 IF IsGeosDisk THEN Write('GEOS disk') ELSE Write('Disk');
 WriteLn(' "',DiskName,'", ',DiskID,', ',DiskOS,' <',d_name,'>');
 found := FALSE;
 RewindDir;
 WHILE ReadDir(dr, page) DO
  IF dr.dostype <> 0 THEN
   IF IsInPage(page) THEN
    IF LIKE(TransStr(DirFileName(dr)), f_name) THEN BEGIN
     TransferFile(dr);
     found := TRUE;
    END; (* if *)
 IF NOT found THEN
  WriteLn('no file');
END; (* DoTransfer *)

BEGIN
 Init;
 DoTransfer;
 d64_close;
END. (* Gget *)
