(* CVT.PAS -- access converted GEOS files
** 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.
*)

UNIT cvt;

INTERFACE

USES global, geos, errors, coding;

CONST
 CVT_EXT    = '.CVT'; (* default file extension *)
 MAX_BUFFER = 256*245-1; (* 256 blocks   254 bytes *)

TYPE
 BUFFER_PTR  = ^BUFFER_TYPE;
 BUFFER_TYPE = ARRAY [0..MAX_BUFFER] OF BYTE;

PROCEDURE cvt_open(fname: STRING);
(* open .cvt file *)

PROCEDURE cvt_close;
(* close it *)

PROCEDURE cvt_info(VAR i: InfoRec);
(* get info record *)

PROCEDURE cvt_class_version(VAR base: STRING; VAR version: BYTE);
(* get base class and version *)

PROCEDURE cvt_dir(VAR d: DirRec);
(* get directory entry *)

FUNCTION cvt_name: STRING;
(* return file name in directory entry [PC] *)

PROCEDURE cvt_vlir(VAR block: InfoRec);
(* get ``VLIR'' record *)

FUNCTION cvt_size(nr: BYTE): LongInt;
(* return chain length or -1 *)

PROCEDURE cvt_chain(nr: BYTE);
(* select a chain *)

FUNCTION cvt_buffer: BUFFER_PTR;
(* return pointer to chain buffer *)

PROCEDURE cvt_seek(pos: WORD);
(* set position in chain *)

FUNCTION cvt_pos: WORD;
(* get position in chain *)

PROCEDURE cvt_resize(len: LongInt);
(* resize current chain *)

PROCEDURE cvt_read(VAR buf; count: WORD; VAR result: WORD);
(* read from chain like BlockRead *)

FUNCTION cvt_eof: BOOLEAN;
(* end of chain? *)

FUNCTION cvt_getch(VAR ch: CHAR): BOOLEAN;
(* read a single char from chain, return success *)

IMPLEMENTATION

CONST
 BLOCK_SIZE = 254; (* size of a data block *)

TYPE
 CVT_BLOCK = ARRAY [0..BLOCK_SIZE-1] OF BYTE;

VAR
 (* the file *)
 cvt_file: FILE;
 dir: DirRec;
 info: InfoRec;
 chain_pos: ARRAY [0..126] OF RECORD off, len: LongInt; END;
 (* the chain and its buffer *)
 chain_nr: BYTE;
 position, soft_length, hard_length: LongInt;
 buffer: BUFFER_PTR;

PROCEDURE cvt_open(fname: STRING);
 VAR block: CVT_BLOCK; result: WORD;
  magic: STRING;
  chain: INTEGER; block_pos: LongInt;
  trk, sec, sec_last: BYTE;
BEGIN
 (* open file *)
 (*$I-*)
 Assign(cvt_file, AddExt(fname, CVT_EXT));
 FileMode := FileMode_RO;
 Reset(cvt_file,1);
 FileMode := FileMode_RW;
 IF IOResult <> 0 THEN FATAL('unable to open file');
 (*$I+*)
 (* handle header *)
 BlockRead(cvt_file, block, SizeOf(block), result);
 IF result <> SizeOf(block) THEN FATAL('not a converted file [size]');
 magic := GetString(block[30], 28);
 IF magic <> 'SEQ formatted GEOS file V1.0' THEN
  IF magic <> 'PRG formatted GEOS file V1.0' THEN
   FATAL('not a coverted file [magic]');
 Move(block[0], dir, SizeOf(dir));
 (* handle info record *)
 BlockRead(cvt_file, info, SizeOf(info), result);
 IF result <> SizeOf(info) THEN FATAL('corrupted converted file [size]');
 (* set up chain_pos[] *)
 FOR chain := 0 TO 126 DO
  WITH chain_pos[chain] DO BEGIN
   off := -1;
   len := -1;
  END; (* with *)
 IF dir.struct = 0 THEN
  (* Sequential structure *)
  WITH chain_pos[0] DO BEGIN
   off := 2*BLOCK_SIZE; (* after header/info *)
   len := FileSize(cvt_file) - off;
  END (* with *)
 ELSE
  (* chain_pos structure *)
  BEGIN
   BlockRead(cvt_file, block, SizeOf(block), result);
   IF result <> SizeOf(block) THEN FATAL('corrupted converted file [size]');
   block_pos := 3; sec_last := 1; (* after header/info/vlir *)
   FOR chain := 0 TO 126 DO BEGIN
    trk := block[2*chain]; sec := block[2*chain+1];
    IF trk > 0 THEN
     WITH chain_pos[chain] DO BEGIN
      off := BLOCK_SIZE*block_pos;
      len := BLOCK_SIZE*(trk-1) + (sec-1);
      Inc(block_pos, trk);
      sec_last := sec;
     END; (* with *)
   END; (* for *)
   IF FileSize(cvt_file) < BLOCK_SIZE*(block_pos-1) + (sec_last-1) THEN
    FATAL('corrupted converted file [size]');
  END; (* else *)
 chain_nr := $FF;
 position :=  0;
 soft_length := -1;
 hard_length := -1;
END; (* cvt_open *)

PROCEDURE cvt_close;
BEGIN
 chain_nr := $FF;
 position :=  0;
 soft_length := -1;
 hard_length := -1;
 Close(cvt_file);
END; (* cvt_close *)

PROCEDURE cvt_info(VAR i: InfoRec);
BEGIN
 i := info;
END; (* cvt_info *)

PROCEDURE cvt_class_version(VAR base: STRING; VAR version: BYTE);
 VAR pos: INTEGER;
BEGIN
 (* raw results *)
 base := TermString(info.class, #0);
 version := 0;
 (* ending with ' [0-9].[0-9]' *)
 pos := Length(base) - 4;
 IF pos <= 0 THEN EXIT;
 IF base[pos+0] <> ' ' THEN EXIT;
 IF base[pos+1] <> 'V' THEN EXIT;
 IF base[pos+2] <  '0' THEN EXIT;
 IF base[pos+2]  > '9' THEN EXIT;
 IF base[pos+3] <> '.' THEN EXIT;
 IF base[pos+4] <  '0' THEN EXIT;
 IF base[pos+4]  > '9' THEN EXIT;
 (* calculate 10 times version *)
 version := 10*(BYTE(base[pos+2])-BYTE('0'))+BYTE(base[pos+4])-BYTE('0');
 (* remove this and ending spaces *)
 REPEAT
  DEC(pos);
 UNTIL base[pos] <> ' ';
 Delete(base, pos+1, $FF);
END; (* cvt_class_version *)

PROCEDURE cvt_dir(VAR d: DirRec);
BEGIN
 d := dir;
END; (* cvt_dir *)

FUNCTION cvt_name: STRING;
BEGIN
 cvt_name := TransStr(TermString(dir.name, #$A0));
END; (* cvt_name *)

PROCEDURE cvt_vlir(VAR block: InfoRec);
 VAR result: WORD;
BEGIN
 IF dir.struct = 0 THEN
  FillChar(block, SizeOf(block), 0)
 ELSE BEGIN
  Seek(cvt_file, 2*BLOCK_SIZE);
  BlockRead(cvt_file, block, SizeOf(block), result);
  IF result <> SizeOf(block) THEN FATAL('read');
 END; (* else *)
END; (* cvt_vlir *)

FUNCTION cvt_size(nr: BYTE): LongInt;
BEGIN
 IF nr > 126
  THEN cvt_size := -1
  ELSE cvt_size := chain_pos[nr].len;
END; (* cvt_size *)

PROCEDURE cvt_chain(nr: BYTE);
 VAR result: WORD;
BEGIN
 position :=  0;
 hard_length := cvt_size(nr);
 IF hard_length < 0 THEN
  chain_nr := $FF
 ELSE BEGIN
  chain_nr := nr;
  (*$I-*)
  Seek(cvt_file, chain_pos[chain_nr].off);
  IF IOResult <> 0 THEN FATAL('seek');
  (*$I+*)
  BlockRead(cvt_file, buffer^, hard_length, result);
  IF result <> hard_length THEN FATAL('read');
  position := 0;
 END; (* else *)
 soft_length := hard_length;
END; (* cvt_chain *)

FUNCTION cvt_buffer: BUFFER_PTR;
BEGIN
 cvt_buffer := buffer;
END; (* cvt_buffer *)

FUNCTION cvt_pos: WORD;
BEGIN
 cvt_pos := position;
END; (* cvt_pos *)

PROCEDURE cvt_seek(pos: WORD);
BEGIN
 position := pos;
END; (* cvt_seek *)

PROCEDURE cvt_resize(len: LongInt);
BEGIN
 IF len < 0           THEN len := 0;
 IF len > hard_length THEN len := hard_length;
 soft_length := len;
END; (* cvt_resize *)

PROCEDURE cvt_read(VAR buf; count: WORD; VAR result: WORD);
 VAR max_count: LongInt;
BEGIN
 max_count := soft_length - position;
 IF max_count <= 0 THEN
  result := 0
 ELSE BEGIN
  IF count > max_count THEN
   count := max_count;
  Move(buffer^[position], buf, count);
  Inc(position, count);
  result := count;
 END; (* else *)
END; (* cvt_read *)

FUNCTION cvt_eof: BOOLEAN;
BEGIN
 cvt_eof := (position >= soft_length);
END; (* cvt_eof *)

FUNCTION cvt_getch(VAR ch: CHAR): BOOLEAN;
 VAR result: WORD;
BEGIN
 IF position >= soft_length THEN
  cvt_getch := FALSE
 ELSE BEGIN
  ch := CHAR(buffer^[position]);
  INC(position);
  cvt_getch := TRUE;
 END; (* else *)
END; (* cvt_getch *)

BEGIN
 New(buffer);
END. (* cvt *)
