mkepr

Descripción del contenido de la página

Un creador de imágenes de tarjetas EPROM o Intel Flash para emuladores de Z88, escrito en Forth.

Proyecto desarrollado entre 2015-12-05 y 2015-12-17.

Etiquetas:

mkepr es una herramienta de línea de comandos que crea ficheros EPROM para ser usados con los emuladores de Z88.

mkepr está escrito en Forth con Gforth y usa un módulo de Forth Foundation Library. El código fuente y el fichero README.adoc (que se puede leer en la página de mkepr en inglés) incluido en el paquete incluyen más detalles e instrucciones de instalación.

Código fuente

#! /usr/bin/env gforth

\ mkepr.fs

: version  s" 0.4.1+20151217"  ;  \ after http://semver.org

\ ==============================================================
\ Description

\ mkepr is a command line tool that creates EPROM and Intel
\ Flash image files containing files from the host system, ready
\ to be used by the Cambridge Z88 emulators ZEsarUX and OZvm.

\ mkepr webpage:
\ http://programandala.net/en.program.mkepr.html

\ mkepr is written in Forth for Gforth (tested on version
\ 0.7.3):
\ http://gnu.org/software/gforth

\ ==============================================================
\ Author and license

\ Copyright (C) 2015 Marcos Cruz (programandala.net)
\
\ You may do whatever you want with this work, so long as you
\ retain the copyright notice(s) and this license in all
\ redistributed copies and derived works. There is no warranty.

\ ==============================================================
\ Acknowledgments

\ The testings were done on the following Z88 emulators:
\
\ ZEsarUX (snapshot version 3.2-SN), by César Hernández:
\ http://sourceforge.net/projects/zesarux/
\
\ OZvm (version 1.1.5), by Gunter Strube:
\ https://cambridgez88.jira.com/wiki/display/OZVM

\ Thanks to César Hernández for the location of the information
\ about the File Card format, on the website of the Z88
\ Development Team.
\
\ https://cambridgez88.jira.com/wiki/display/DN/Miscellaneous+useful+information#Miscellaneoususefulinformation-FileCardformat

\ Thanks to Garry Lancaster for confirming the format of
\ Intel Flash cards:
\
\ https://www.mail-archive.com/forth-sinclair@yahoogroups.com/msg00038.html

\ Thanks to Anton Erlt for the example how to do `bye` returning an
\ error code to the OS shell:
\
\ http://lists.gnu.org/archive/html/gforth/2015-12/msg00004.html
\ http://www.mail-archive.com/gforth@gnu.org/msg00540.html

\ ==============================================================
\ History

\ See at the end of the file.

\ ==============================================================
\ Installation

\ Make sure the <mkepr.fs> is executable.  Then copy, move or
\ link it to a directory on your path.

\ Example for single-user installation:

\     chmod u+x mkepr.fs
\     ln mkepr.fs ~/bin/mkepr

\ Example for system-wide installation:

\     chmod ugo+x mkepr.fs
\     ln mkepr.fs /usr/local/bin/mkepr

\ ==============================================================
\ Documentation on the File Card format

\ From
\ https://cambridgez88.jira.com/wiki/display/DN/Miscellaneous+useful+information#Miscellaneoususefulinformation-FileCardformat

\ $0000       File entry
\ ...         File entry
\ ... 
\ ...         Latest file entry
\ ...         $FF's until
\ $3FC0       $00's until
\ $3FF7       $01
\ $3FF8       4 byte random id
\ $3FFC       size of card in banks (in units of 16 KiB)
\ $3FFD       sub-type:
\               $7E for 32 KiB cards
\               $7C for 128 KiB (or larger) cards
\ $3FFE       'o'
\ $3FFF       'z' (file eprom identifier, lower case 'oz')

\ A file entry has the form:

\ 1 byte      n           length of filename
\ 1 byte      x           '/' for latest version
\                         $00 for old version (deleted)
\ n-1 bytes   '...'       filename
\ 4 bytes     m           length of file
\                         (least significant byte first)
\ m bytes                 body of file


\ From the source of FlashStore
\ (https://cambridgez88.jira.com/wiki/display/ZFS/FlashStore+%28Z88+popdown%29+Home):
\
\ ____
\ Due to a strange side effect with Intel Flash Chips, a
\ special "NULL" file is saved as the first file to the Card.
\ These bytes occupies the first bytes that otherwise could be
\ interpreted as a random boot command for the Intel chip -
\ the behaviour is an Intel chip suddenly gone into command
\ mode for no particular reason. The NULL file prevents this
\ behaviour by saving a file that avoids any kind of boot
\ commands which sends the chip into command mode when the
\ card has been inserted into a Z88 slot.
\ ____

\ ==============================================================
\ Requirements

only forth definitions
warnings off

\ ----------------------------------------------
\ From Gforth

require string.fs  \ dynamic strings
require random.fs  \ random number generator

\ ----------------------------------------------
\ From the Forth Foundation Library
\ (http://irdvo.github.io/ffl/)

require ffl/arg.fs  \ arguments parser
require ffl/chr.fs  \ char data type

\ ----------------------------------------------
\ From the Galope library
\ (http://programandala.net/en.program.galope.html)

: unslurp-file  ( ca1 len1 ca2 len2 -- )
  w/o create-file throw >r
  r@ write-file throw
  r> close-file throw  ;
  \ Save memory region _ca1 len1_ to file _ca2 len2_.

: $variable  ( "name" -- )
  variable  0 latestxt execute $!len  ;
  \ Create and initialize a dynamic string variable "name".

: d>str  ( d -- ca len )
  tuck dabs <# #s rot sign #>  ;
  \ Convert a double number _d_ to a string _ca len_.

: n>str  ( n -- ca len )
  s>d d>str  ;
  \ Convert number _n_ to a string _ca len_.

: c>str  ( c -- ca len )
  1 allocate throw  tuck c! 1  ;
  \ Convert an ASCII char to a string.

variable (any?)

: any?  ( x0 x1..xn n -- f )
  dup 1+ roll (any?) !  0 swap 0 do  swap (any?) @ = or  loop  ;
  \ Is any _x1..xn_ equal to _x0_?

: sides  { ca1' len1' ca1 len1 len2 -- ca3 len3 ca4 len4 }
  ca1  len1 len1' -             \ left side
  ca1' len2 +  len1' len2 -  ;  \ right side
  \ Convert the result returned by 'search': divide the searched
  \ string at the substring that was searched to.
  \ ca1' len1' = string searched, starting with the first (ca2 len2)
  \ ca1 len1 = original string, before the search
  \ len2 = length of the substring searched for
  \ ca1' len2 = substring found in ca1 len1
  \ ca3 len3 = left side of ca1 len1, until and excluding ca1' len2
  \ ca4 len4 = right side of ca1 len1, after ca1' len2

: /sides  { ca1 len1 ca2 len2 -- ca1 len1' ca3 len3 f }
  ca1 len1 ca2 len2 search dup >r
  if    ca1 len1 len2 sides
  else  over 0  \ fake right side
  then  r>  ;
  \ Search a string _ca1 len1_
  \ for the first occurence of a substring _ca2 len2_.
  \ Divide the string _ca1 len1_ in two parts: return both sides
  \ of the substring _ca2 len2_ (first occurence), excluding the
  \ substring _ca2 len2_ itself.
  \
  \ ca1 len1  = string
  \ ca2 len2  = substring
  \ ca1 len1' = left side (or whole string if not found)
  \ ca3 len3  = right side (or empty string if not found)
  \ f = found?
  \
  \ Note: _ca3 len3_ can be empty also when _f_ is true.

: /slash  ( ca len -- ca#1 len#1 ... ca#n len#n n )
  depth >r
  begin  s" /" /sides 0=  until  2drop
  depth r> 2 - - 2/  ;
  \ Divide a slash separated values string.

: -prefix  ( ca1 len1 ca2 len2 -- ca1' len1' )
  dup >r 2over 2swap string-prefix? r> and /string  ;
  \ Remove a prefix _ca2 len2_ from a string _ca1 len1_.

: typecr ( ca len -- )  type cr  ;

: ?error-bye ( f ca len -- )
  rot if
    ['] typecr stderr outfile-execute 1 (bye)
  then  2drop  ;

\ ==============================================================
\ Card

false value flash-card?
  \ Flag: Create a Flash card instead of an EPROM card?
  \ Default is false.

$variable card-filename

: card-filename!  ( ca len -- )
  card-filename $!  ;

: card-base-filename    ( -- ca len )  s" output"  ;
: flash-card-extension  ( -- ca len )  s" .flash"  ;
: eprom-card-extension  ( -- ca len )  s" .epr"    ;

: default-eprom-card-filename  ( -- ca len )
  card-base-filename eprom-card-extension s+  ;
  \ Default filename for EPROM cards.

: default-flash-card-filename  ( -- ca len )
  card-base-filename flash-card-extension s+  ;
  \ Default filename for Flash cards.

: card-extension  ( -- ca len )
  flash-card? if    flash-card-extension
              else  eprom-card-extension  then  ;
  \ Default card filename extension for the current
  \ type of card.

: default-card-filename  ( -- ca len )
  card-base-filename card-extension s+  ;
  \ Default card filename for the current type of card.

: card-filename@  ( -- ca len )
  card-filename $@len if    card-filename $@
                      else  default-card-filename  then  ;
  \ If the card filename has been set by the command line
  \ option, return it; else return the default filename.

16 constant /filename
  \ Max length of OZ filenames.

: KiB  ( n1 -- n2 )  1024 *  ;
: KiB/  ( n1 -- n2 )  1024 /  ;

 16 KiB constant  16KiB
128 KiB constant 128KiB
512 KiB constant 512KiB

0 value card
  \ Address of the card space (0 if not allocated).

variable >card  0 >card !
  \ Pointer to the current free address in the card.

: string>card  ( ca len  -- )
  dup >r  >card @ swap move  r> >card +!  ;
  \ Add the string _ca len_ to the card;
  \ the length is not saved, only the contents.

: byte>card  ( b -- )
  >card @ c! 1 >card +!  ;
  \ Add _b_ to the card.

 32 KiB constant eprom-card-default-size
512 KiB constant flash-card-default-size

eprom-card-default-size value default-card-size
  \ Default size of the card in bytes.

0 value /card
  \ Size of the card in bytes.

0 value card-header
  \ Address of the card header
  \ (the header is at the end of the file).

64 constant /card-header
  \ Length of the card header in bytes
  \ (the header is at the end of the file).

\ Fields of the card header:
: card-header-1          ( -- ca )  card-header 55 +  ;
: card-header-random-id  ( -- ca )  card-header 56 +  ;
: card-header-size       ( -- ca )  card-header 60 +  ;
: card-header-subtype    ( -- ca )  card-header 61 +  ;
: card-header-oz-id      ( -- ca )  card-header 62 +  ;

: allocate-card  ( -- a )
  /card ?dup 0= if  default-card-size dup to /card  then
  allocate throw  ;
  \ Allocate memory for the card and return its address.

: erase-card  ( -- )
  card /card $FF fill  ;
  \ Erase the card, filling it with $FF.

: erase-card-header  ( -- )
  card-header /card-header erase  ;
  \ Erase the card header, filling it with zeroes.

: calculate-card-header  ( -- )
  card /card + /card-header - to card-header  ;
  \ Calculate the address of the card header.

: random-id  ( -- b1 b2 b3 b4 )
  4 0 do  256 random  loop  ;
  \ Return a card random id: four random bytes.

: store-card-random-id  ( -- )
  random-id card-header-random-id >r
  r@     c!
  r@ 1+  c!
  r@ 2 + c!
  r> 3 + c!  ;
  \ Store the card random id.

: store-card-banks  ( -- )
  /card 16KiB / card-header-size c!  ;
  \ Store the number of 16 KiB banks of the card.

: large-card?  ( -- f )
  /card [ 128KiB 1- ] literal >  ;
  \ Is the card 128 KiB or larger?

: store-card-subtype  ( -- )
  large-card?
  if  $7C  else  $7E  then  card-header-subtype c!  ;
  \ Store the card subtype, depending on its size.

: store-card-oz-id  ( -- )
  'o' card-header-oz-id    c!
  'z' card-header-oz-id 1+ c!  ;
  \ Store the OZ card identifier.

: init-card-header  ( -- )
  erase-card-header  1 card-header-1 c!
  store-card-random-id  store-card-banks
  store-card-subtype  store-card-oz-id  ;
  \ Init the card header, located at the end of the card.

: null-file  ( -- )
  1 byte>card 0 byte>card 0 byte>card
  0 byte>card 0 byte>card 0 byte>card  ;
  \ Create a "null" file, a file entry with a null filename
  \ and zero length.

: format-card  ( -- )
  erase-card init-card-header
  flash-card? if  null-file  then  ;

: create-card  ( -- )
  allocate-card  dup to card  >card !
  calculate-card-header  format-card  ;

: card-needed  ( -- )
  card ?exit create-card  ;
  \ Create a card, if not done yet.

: unused-card  ( -- n )
  card-header >card @ -  ;
  \ Unused space in the card, in bytes.

: save-card  ( -- )
  card /card card-filename@ unslurp-file  ;
  \ Save the card to a file.

: ?card-size-number  ( ca len -- )
  nip 0<> s" Wrong size: not a valid number." ?error-bye  ;
  \ Error if the card size number is wrong.
  \ _ca len_ is the string left by `>number`, and its
  \ length must be zero if the conversion was successful.

: ?card-size-pages  ( n -- )
  16 mod s" Wrong size: it must be a multiple of 16" ?error-bye  ;
  \ Error if the card size _n_ (in KiB) is not a multiple of 16.

: eprom-card-sizes  ( -- x0..xn n )
  32 128 256 3  ;
  \ Return the proper sizes (in Kib) for an EPROM card,
  \ and their count.

: flash-card-sizes  ( -- x0..xn n )
  512 1024  2  ;
  \ Return the proper sizes (in Kib) for a Intel Flash card,
  \ and their count.

: ?card-size-range  ( n -- )
  flash-card? if    flash-card-sizes
              else  eprom-card-sizes  then
  any? 0= s" Wrong size for the card type." ?error-bye  ;
  \ Error if the card size _n_ (in KiB) is not in the range
  \ allowed by the card type.

: check-card-size  ( d ca len -- n )
  ?card-size-number d>s dup ?card-size-pages  dup ?card-size-range  ;
  \ Check if the card size, specified by the `--size` option,
  \ is right.  _d ca len_ are the result of `>number`.
  \ If no error is found, return the size _n_ (in KiB).

: ?no-card-yet  ( -- )
  card s" Invalid option: the card is already created." ?error-bye  ;
  \ Error if the card is already created.

: set-card-size  ( ca len -- )
  ?no-card-yet
  0. 2swap >number check-card-size KiB to /card  ;
  \ Set the card size in KiB, as found in the string _ca len_.

: flash-card  ( -- )
  ?no-card-yet
  true to flash-card?
  flash-card-default-size to default-card-size  ;
  \ Create a Flash card instead of an EPROM.

\ ==============================================================
\ Input files

2variable filename
  \ Address and length of the current input file name.

2variable file-contents
  \ Address and length of the contents of the current input
  \ file.

: get-file  ( ca len -- )
  slurp-file file-contents 2!  ;
  \ Get the contents of input file _ca len_ and store it.

: file-length  ( -- len )
  file-contents @  ;
  \ Length of the current input file.

: free-file  ( -- )
  file-contents 2@ drop free throw  ;
  \ Free the allocated space of the the current input file.

variable files  \ counter

\ ==============================================================
\ Copying a file

: full-card?  ( -- f )
  unused-card file-length <  ;
  \ Is the card full?

: ?space-left  ( -- )
  full-card? s" No space left in the card." ?error-bye  ;
  \ Error if there's no space left in the card.

: valid-char?  ( c -- )
  >r  r@ chr-alnum?
      r@ '-' = or
      r@ '.' = or
      r> chr-ascii? and  ;
  \ Is _c_ a valid filename char?

: ?valid-char  ( c -- )
  dup >r valid-char? 0=
  s" Invalid filename character '" r> c>str s+ s" '." s+ ?error-bye  ;
  \ Error if _c_ is not a valid char.

: ?segment-chars  ( ca len -- )
  bounds ?do  i c@ ?valid-char  loop  ;
  \ Error if any filename char in invalid.

: ?segment-length  ( ca len -- )
  nip /filename > s" Segment too long." ?error-bye  ;
  \ Error if filename length is too long.

: ?segment-dot  ( ca len -- +n | -1 )
  -1 -rot  over swap  ( -1 ca ca len )
  bounds ?do
    i c@ '.' =
    if  over -1 > s" More than one dot in the filename." ?error-bye
        nip i swap - dup  ( +n +n )  then
  loop  drop  ;
  \ Error if filename contains two or more dots.
  \ Return the position _+n_ of the dot (0..len-1), or -1 if
  \ there's no dot.

: ?segment-extension  ( len n )
  - 1- dup 3 > s" Segment extension is too long." ?error-bye
            0= s" Segment extension is empty." ?error-bye  ;
  \ Error if the filename extension is too long or empty; _len_
  \ is the length of the filename and _n_ is the position of the
  \ dot (0...len-1).

: ?segment-without-extension  ( len n -- )
  nip dup 12 > s" Segment without extension is too long." ?error-bye
            0= s" Segment without extension is empty." ?error-bye  ;
  \ Error if the filename without the extension is too long or
  \ empty.  _len_ is the length of the filename and _n_ is the
  \ position of the dot (0...len-1).

: ?segment-format  ( ca len n -- )
  rot drop  dup -1 = if  2drop exit  then  \ exit if no extension
  ( len n ) 2dup ?segment-extension
  ?segment-without-extension  ;
  \ Error if the filename format is not "12.3".
  \ The filename length is supposed to be under 17.
  \ _n_ is the position of the dot (0...len-1); or -1
  \ if there's no dot.

: ?segment  ( ca len -- )
  2dup ?segment-length
  2dup ?segment-chars
  2dup ?segment-dot
       ?segment-format  ;
  \ Error if the segment _ca len_ is invalid.
  \ A segment is each part of a explicit filename divided
  \ by slashes.

: ?filename  ( ca len -- )
  /slash 0 ?do  ?segment  loop  ;
  \ Error if the filename _ca len_ is invalid.

: check-file  ( ca len -- )
  ?filename ?space-left  ;

: /filename  ( -- len )
  filename @  ;
  \ Length of the current input file name.

: filename>card  ( -- )
  /filename 1+ byte>card  '/' byte>card
  filename 2@ string>card  ;
  \ Copy the input file name.

: file-length>card  ( -- )
  file-length dup dup dup
                        256 mod byte>card
                          256 / byte>card
        [ 256 256 * ] literal / byte>card
  [ 256 256 256 * * ] literal / byte>card  ;
  \ Store the length of the current input file on the card;
  \ least significant byte first.

: file-contents>card  ( -- )
  file-contents 2@ string>card  ;
  \ Store the contents of the current input file on the card.

: current-file>card  ( -- )
  filename>card file-length>card  file-contents>card  ;
  \ Store the current input file on the card.

true value verbose?
  \ List the processed input files?

: echo  ( ca len -- )
  verbose? if  type cr  else  2drop  then  ;
  \ Print the string _ca len_, if verbose mode is on,
  \ else discard it.

: adjust-path  ( ca len -- ca' len' )
  s" ./" -prefix  ;
  \ Remove the "./" prefix from filename _ca len_, if present.

: ?file-exists  ( ca len -- )
  2dup file-status s" No such file: " 2rot s+ ?error-bye drop  ;
  \ Error if file _ca len_ does not exist.

: file>card  ( ca len -- )
  2dup ?file-exists
  1 files +!  card-needed
  adjust-path  2dup filename 2!
  2dup get-file  2dup echo
  check-file current-file>card free-file  ;
  \ Copy file _ca len_ to the card, if possible.

\ ==============================================================
\ Argument parser

: KiBs+  ( ca len n -- ca' len' )
  KiB/ n>str s+ s"  KiB" s+  ;
  \ Concatenate the value of _n_ bytes in KiB, including the
  \ unit, to the string _ca len_, giving the result string _ca'
  \ len'_.

\ Program data:
s" mkepr"                              \ name
s" [options] [files]"                  \ usage
version                                \ version
s" Report bugs via programandala.net"  \ extra info
arg-new value mkepr-arguments

\ Add the default help and version options:
mkepr-arguments arg-add-help-option
mkepr-arguments arg-add-version-option

\ Add the -f/--flash option switch:
'f'                                   \ short option
s" flash"                             \ long option
s" create an Intel Flash card instead of an EPROM card"
                                      \ description
true                                  \ a switch
4 dup constant arg.flash-option       \ option id
mkepr-arguments arg-add-option

\ Add the -o/--output option parameter:
'o'                                   \ short option
s" output=FILE"                       \ long option
s" set output file; default: "
  default-eprom-card-filename s+ s"  (or " s+
  default-flash-card-filename s+ s" )" s+

                                      \ description
false                                 \ a parameter
5 dup constant arg.output-option      \ option id
mkepr-arguments arg-add-option

\ Add the -s/--size option parameter:
's'                                   \ short option
s" size=KiB"                          \ long option
s" set the card size; defaults: "
  eprom-card-default-size KiBs+ s"  (EPROM), " s+
  flash-card-default-size KiBs+ s"  (Flash)" s+
                                      \ description
false                                 \ a parameter
6 dup constant arg.size-option        \ option id
mkepr-arguments arg-add-option

\ Add the -q/--quiet option switch:
'q'                                   \ short option
s" quiet"                             \ long option
s" quiet mode: input files will not be listed"  \ description
true                                  \ a switch
7 dup constant arg.quiet-option       \ option id
mkepr-arguments arg-add-option

: show-help  ( -- )
  mkepr-arguments arg-print-help bye  ;

: show-version  ( -- )
  mkepr-arguments arg-print-version  ;

: be-quiet  ( -- )
  false to verbose?  ;
  \ Deactivate the verbose mode.

: option? ( -- ca len n f | n f )
  mkepr-arguments arg-parse  ( ca len n | n )
  dup arg.done <> over arg.error <> and  ;
  \ Parse the next command line option. Return its value _ca
  \ len_, if any; and its number _n_; _f_ is true when the
  \ option is valid.

: option  ( ca len n | n -- )
  case
    arg.non-option      of  file>card       endof
    arg.output-option   of  card-filename!  endof
    arg.size-option     of  set-card-size   endof
    arg.flash-option    of  flash-card      endof
    arg.quiet-option    of  be-quiet        endof
    arg.help-option     of  show-help       endof
    arg.version-option  of  show-version    endof
  endcase  ;
  \ Manage command line option _n_ and its value _ca len_.

: parse-options  ( -- )
  begin  option?  while  option  repeat  ;
  \ Parse and manage all command line options.

\ ==============================================================
\ Boot

' noop is dobacktrace
  \ Don't show the return stack backtrace after an error.

: mkepr  ( -- )
  files off  parse-options
  files @ if  save-card  else  show-help  then  ;

mkepr bye

\ ==============================================================
\ History

\ 2015-12-05: Start. Documentation on the File Card format.
\
\ 2015-12-08: Version 0.0.0. Main structure.
\
\ 2015-12-09: Version 0.0.1. First working version.
\
\ 2015-12-10: Version 0.1.0. New option `--flash` to create
\ Intel Flash cards instead of EPROM cards.
\
\ 2015-12-11: Version 0.2.0. Filenames are checked.
\
\ 2015-12-11: Version 0.3.1.  - Added card size check depending
\ on the card type.  - Fixed the format of file length (only
\ files larger than 16 MiB were affected).  - Fixed the default
\ size of Flash cards.  - Removed the check of maximum file
\ length.  - Improved the help.
\
\ 2015-12-15: Version 0.3.2. Added the explanation about the
\ "null" file required by Intel Flash cards and renamed one word
\ accordingly.
\
\ 2015-12-16: Version 0.4.0. Added support for directories.  No
\ backtrace shown on errors.
\
\ 2015-12-17: Version 0.4.1. No internal information shown on
\ errors. Missing files are detected, right at the start.
\ Improved error on invalid characters. Fixed and improved
\ installation instructions.

\ vim: tw=64

Descarga

También: mkepr en Github.