I have a cople of HP BASIC (HP9000) files that have been saved in a non-ASCII format (HFSLIF?). Does anyone know of a conversion utility to get the code readable? The files are already on a win7 machine and I just want to read the content, not execute it.

Thanks in advance!

Regards, Staffan


Complete reedit due to new information and 30K limit

Ok I tried your new files. the files without extensions should have *.HPI extensions (HP Image) and should be decoded to files first. They have HFSLIF signature at start.

To decode them the easiest is to use emulator. However to do this on your own we need either decoding table + fileformat description or binary+ASCII of the same program so we can infer the file format.

After some googling I found this:

see there the part Background: The LIF File System it looks like the files you linked matches their fileformat description. Here start of FW_UPGRADE in hex:

00000000: | 80 00 48 46 53 4c 49 46 00 00 00 01 10 00 00 00 | € HFSLIF   


8000         magic
4846534c4946 volume label "HFSLIF"
00000001     directory start
1000         LIF ID
0000         unused

So now its just the matter of decoding the DISC/file image into binary and try to detokenize. btw at the same page is also their utility to do this for download...

So the non ASCII files are DISC images not files !!! I tried the hpdir utility from the previous link and this:

hpdir -c -extract FW_UPGRADE.htm

will extract the PROG file from DISC image into file WS_FILE.PROG which is a bit smaller than the original DISC image.

However It looks like there is absolutely no tool to convert the binary into ASCII (all just extract files from the DISC images) nor I can find any TOKEN code table anywhere (with codes not just Keyword listing) so the only way now would be to reverse engineer the encoding. For that its necessary to have ASCII and Binary files of the same program containing all the Keywords and data representations (integer,float,string ...) Which we have now.

At the end of HP9000 BASIC 5.0 Language Reference Vol2 (thx to wizzwizz4) I found table of BASIC keywords supported by HP9000 Sadly without the tokenisation codes :( so a cross-match or inferring is needed (some commands are recognizable like PRINT,REM others need some reversing or pairing with known BASIC ASCII source ...

Here actual FileFormat summary:

// file start
// 16 bit: 0000 FFFF size 4000 0007? 00     00C2     00C2 00 FFFF          0000
// 32 bit: 0000 FFFF size 4000 0007? 00 000000C2 000000C2 40 0000 FFFFFFFF 0000

// 16bit line encoding examples
[02 BA 01 B3]                   RETURN Model$
[B5] 2 [00 02] RP [02 FB BA 01 B3 8E]       RETURN "RP"&File$
[B5] 2 [00 06] 46020A [BA 01 B3 8E]     RETURN "46020A"
[B5] 2 [00 05] OTHER [BA 01 B3]         RETURN "OTHER"
[81 BA 01 B3]                   RETURN 1
[80 BA 01 B3]                   RETURN 0
[84 BD] # [B3]                  WAIT 4
[85 BD] # [B3]                  WAIT 5
B5 31 02 80 B5 18 3F D3 33 33 33 33 33 33 BD 24 02 B3       BEEP 640,.3
[B5] 1 [00 C8 B5 18] ? [E0 00 00 00 00 00 00 BD] $ [02 B3]  BEEP 200,.5
[8B B5 18 8E] ? [E0 00 00 00 00 00 00 BD] $ [02 B3]     BEEP 100,.5

all offsets: are in decadic !!!
[16 bit Header]
08: DWORD   start of last BASIC line
12:     Starting section
17: DWORD   00C200C2
38: DWORD   start of first BASIC line
46: DWORD   start of variable table

[16 bit Section]
00: BYTE    ID 40/48/C0/C8
03: BYTE    intendation (probably ORed with another flags in higher bits)
04: WORD    line umber (0 for starting section which is not code)
14: WORD    offset to next section
28: WORD    offset to next line (inside section)
??:     code

[16 bit line]
00: BYTE    ID 40/48/C0/C8
01: BYTE    length of pevious line (0 for sections)
02: BYTE    length of this line (0 if section)
03: BYTE    intendation (probably ORed with another flags in higher bits)
04: WORD    line umber (0 for starting section)
06:     code
[32 bit Header]
08: DWORD   start of last BASIC line
12:     Starting section
17: DWORD   000000C2
21: DWORD   000000C2

[32 bit Section]

[32 bit line]
00: BYTE    ID 40/48/C0/C8
01: BYTE    length of pevious line (0 for sections)
02: BYTE    length of this line (0 if section)
03: BYTE    intendation (probably ORed with another flags in higher bits)
04: DWORD   line umber (0 for starting section)
08:     code

each line ends with B3 or B3,8E all codes are in [hex]

first byte:

    00      - label: (index of label)
    <01,30)     - index of variable + 1
    <80,8E)     - constant <00,0E)

any? byte:

    B6      - ! comment (B6,8E,BYTE size, char string[size])
    30/07,D4    - Cprint (BYTE parentis,BYTE ?,WORD size+1,char string[size])
    BC,01,0B/0F - REM (BYTE size,char string[size])
    B9      - END IF
    95,B3,8E    - GOSUB
    9B,A5       - PRINT
    B8,8E,00,00 - ELSE (WORD ??)
    BA,00       - RETURN
    BA,01       - RETURN
    BC,02,12    - DATA (BYTE size,char string[size])
    BC,06       - REAL (index of variable) ends A4
    BC,07       - INTEGER (index of variable) ends A4
    BD,1C       - STOP
    BD,05       - END
    BD,06       - FNEND
    BD,0D       - OFF ERROR
    BD,17       - PAUSE
    BD,23       - WAIT
    BD,24       - BEEP
    BC,02,12    - DATA (BYTE size,char string[size])
    BB,03,0B    - REPEAT
    BB,03,15    - END SELECT
    BB,1F,22    - CLEAR ERROR
    BB,1F,40    - CLEAR SCREEN
    BB,03,10,8E,00,00 CASE ELSE (WORD ??)

not first byte:

    00      - string operand (BYTE size, char string[size])
    09      - n-dimensional array variable declaration (WORD: i0:i1,i2:i3,... BYTE A4)
    B5      - signals combination of more operands
    90      - operand = operand (operands are stored before)

As you can see there are 2 file-formats 16 bit and 32 bit (sadly I got just single 32 bit file and without ASCII one of the first linked firmwares). You can use the 00C200C2/000000C2000000C2 pattern to detect it.

The code is comprised of sections and lines. First section (line number = 0) does not have any code in it but it contains variable table and additional still undetermined stuff.

All other sections contain lines. The last section has negative offset to next section.

Here current decoder C++/VCL:

AnsiString decode(AnsiString file)  // read HP9000 *.PROG file and convert it to text
    // view config
    bool _enable_wordwrap=true;     // turn word wrapping on/off
    bool _enable_hexview=true;      // turn hexview on/off
    // view end columns
    const int _sz_lin=13;           // adr: lno
    const int _sz_bas=140;          // adr: lno WAIT 4
    const int _sz_hex=140+7+(32*3); // adr: lno WAIT 4 | adr: C0 0C 0A 23 ...
    const int _ops=32;              // max operands
    // variables
    DWORD dw;
    BYTE *dat=NULL,c,*cmd;
    int a0,a1,a,adrv,adrb,adre;
    int i,l,_hex,_txt,_ok,_beg,_16bit=0,_32bit=0;
    int hnd,siz,adr,adr0,adr1,adrh,len,lno,tab,ops;
    bool eof,eos,sos;               // end of file, end of section, start of section
    AnsiString lin,txt="",op[_ops],cm,s,slin,sbas;

    for (slin="",i=0;i<_sz_lin;i++) slin+=" ";  // code intendation
    for (sbas="",i=0;i<_sz_bas;i++) sbas+=" ";  // hex view intendation

    // load file to memory
    hnd=FileOpen(file,fmOpenRead);  // open source file
    siz=FileSeek(hnd,0,2);          // get its length
        FileSeek(hnd,0,0);          // and point to start again
    dat=new BYTE[siz];              // create buffer
    FileRead(hnd,dat,siz);          // load to file into it
    FileClose(hnd);                 // close file

    // detect 16/32 bit
    dw =dat[17]; dw<<=8;
    dw+=dat[18]; dw<<=8;
    dw+=dat[19]; dw<<=8;

    // adr of last BASIC line
    dw =dat[ 8]; dw<<=8;
    dw+=dat[ 9]; dw<<=8;
    dw+=dat[10]; dw<<=8;
    dw+=dat[11]; adre=dw;
    if (adre+8>siz) adre=siz-8;
    // adr of first BASIC line
    dw =dat[38]; dw<<=8;
    dw+=dat[39]; dw<<=8;
    dw+=dat[40]; dw<<=8;
    dw+=dat[41]; adrb=dw+12;
    if (adrb>adre) adrb=adre;
    // adr of variables table
    dw =dat[46]; dw<<=8;
    dw+=dat[47]; dw<<=8;
    dw+=dat[48]; dw<<=8;
    dw+=dat[49]; adrv=dw+12;
    if (adrv>adrb) adrv=adrb;

    // decode variables to text
    txt+="[Variables]\r\n adr sz ix:\r\n";
    for (l=0,adr=adrv;adr<adrb;adr++)
     if (dat[adr])  // size
        len=dat[adr]; adr++;
        txt+=AnsiString().sprintf("%04X %02X %02X: ",adr-1,len,l);
        for (lin="",i=0;(i<len)&&(adr<adrb);i++)
            c=dat[adr]; adr++;
            if (c<32) c='.';
            } adr--;
        txt+=lin+"\r\n"; l++;
        } txt+="\r\n";

    // decode BASIC to text
    if (_16bit) txt+="[Code 16bit]\r\n";
    if (_32bit) txt+="[Code 32bit]\r\n";
    txt+=" adr:    lno code\r\n";
    for (adrh=0,adr=0x000C,eof=false;!eof;)         // process all sections
        // section start line info
        adr0=adr;                                   // store starting line
        tab=dat[adr+ 3];                            // intendation
        if (_16bit)                                 // line number
            dw= dat[adr+4]; dw<<=8;
            dw+=dat[adr+5]; lno=dw;
        if (_32bit)
            dw =dat[adr+4]; dw<<=8;
            dw+=dat[adr+5]; dw<<=8;
            dw+=dat[adr+6]; dw<<=8;
            dw+=dat[adr+7]; lno=dw;
        sos=(lno>0);                                // section start (except first line (lno = 0)
        dw =dat[adr+28]; dw<<=8;                    // offset to next line (normal line length)
        dw+=dat[adr+29]; len=dw;
        dw =dat[adr+14]; dw<<=8;                    // offset to next section (section length)
        if (dw>=0x8000)                             // last size iso negative
        else adr1=adr0+dw;
        if (!sos)
            adr+=len;                               // first line of code offset
        if (sos)
        for (eos=false;!eos;)                       // process section lines
            // inside section lin info
            if (!sos)
                len=dat[adr+ 1];                    // line length
                tab=dat[adr+ 3];                    // intendation
                if (_16bit)                         // line number
                    dw= dat[adr+4]; dw<<=8;
                    dw+=dat[adr+5]; lno=dw;
                if (_32bit)
                    dw =dat[adr+4]; dw<<=8;
                    dw+=dat[adr+5]; dw<<=8;
                    dw+=dat[adr+6]; dw<<=8;
                    dw+=dat[adr+7]; lno=dw;

            // ----------------------------------------------------------------------------------
            // process line decoded line lno from dat[adr+0] to dat[adr+len] with intendation tab
            // ----------------------------------------------------------------------------------
            // line start
            lin=AnsiString().sprintf("%04X: ",adr);
            // line number
            lin+=AnsiString().sprintf("%6u ",lno);
            // intendation
            if (tab<=32) for (i=0;i<tab;i++) lin+=" ";

            // decode tokenized line to text
            a0=adr; a1=adr+len;
            if (_16bit) a0+=6;
            if (_32bit) a0+=8;
            ops=0;      // no operands yet
            cm="";      // no command yet

            if (lno==22)

            for (_hex=0,_txt=0,_ok=1,_beg=1;a0<a1;)
                cmd=dat+a0;                             // pointer just to simplify relative decoding for more BYTE tokens
                c=dat[a0]; a0++;                        // actual processed BYTE
                if (_ok)                                // no discrepancy yet?
                    if ((c==0x00)&&( _beg))             // Label:
                        a0++; lin+=AnsiString().sprintf("label%02X:",cmd[1]); continue;
                    if ((c==0x00)&&(!_beg))             // string operand
                        l=dat[a0]; a0++;                // size
                        if (a0+l>a1) l=a1-a0;
                        for (op[ops]="\"",i=0;i<l;i++,a0++)
                        ops++; if (ops>=_ops) break; continue;
                    if ((c>=0x01)&&(c<0x30)&&(_beg))    // variable operand
                        ops++; if (ops>=_ops) break; continue;
                    if ((c>=0x80)&&(c<0x8E)&&(_beg))    // small integer constant perand
                        ops++; if (ops>=_ops) break; continue;
                    if (c==0x8E)                        // end of line [B3 8E]
                        lin+=" |8E|";
                    if ((c==0x90)&&(ops==2))            // op1 = op2
                        lin+=op[0]+"="+op[1]; ops=0; continue;
                    if (c==0xB3)                        // end of line [B3 8E]
                        if (cm!="") lin+=cm;
                        for (i=0;i<ops;i++) lin+=" "+op[i];
                        lin+=" |B3|";
                    if (c==0xB5)                        // combination of operands
                        a0++; _beg=1;
                    if (c==0xB6)                        // ! comment
                        a0+=2;                          // skip [B6 8E 07]
                        l=dat[a0]; a0++;                // size
                        if (a0+l>a1) l=a1-a0;
                        for (lin+="! ",i=0;i<l;i++,a0++)
                    if ((cmd[0]==0x30)||(cmd[0]==0x07)) // Cprint
                     if (cmd[1]==0xD4)
                        c=' ';
                        if (cmd[2]==0xB5) c='"';        // parentis
                        dw =cmd[4]; dw<<=8;             // string size
                        dw+=cmd[5]; l=dw-1;
                        lin+="Cprint("; lin+=char(c);
                        a0+=5; if (a0+l>=a1) l=a1-a0-1;
                        for (i=0;i<=l;i++,a0++) lin+=char(dat[a0]);
                    if ((cmd[0]==0xBC)&&(cmd[1]==0x01)&&(cmd[2]==0x0B)||(cmd[2]==0x0F))
                        lin+="REM "; l=cmd[3];
                        a0+=3; if (a0+l>a1) l=a1-a0;
                        for (i=0;i<l;i++,a0++) lin+=char(dat[a0]);
                    if ((cmd[0]==0xBC)&&(cmd[1]==0x06)) // REAL variable declaration
                        lin+="REAL "+AnsiString().sprintf("var%02X",cmd[2]-1); a0+=2; continue;
                    if ((cmd[0]==0xBC)&&(cmd[1]==0x07)) // INTEGER variable declaration
                        lin+="INTEGER "+AnsiString().sprintf("var%02X",cmd[2]-1); a0+=2; continue;
                    if ((cmd[0]==0xBC)&&(cmd[1]==0x02)&&(cmd[2]==0x12)) // DATA
                        lin+="DATA "; l=cmd[3]; 
                        a0+=3; if (a0+l>a1) l=a1-a0;
                        for (i=0;i<l;i++,a0++) lin+=char(dat[a0]);
                    if (cmd[0]==0x09)                   // n dimensional array ???
                        for (;a0+4<=a1;)
                            if (dat[a0]==0xA4) break;   // end of variable declaration
                            dw =dat[a0]; a0++; dw<<=8;
                            dw+=dat[a0]; a0++; lin+=AnsiString().sprintf("%i:",dw);
                            dw =dat[a0]; a0++; dw<<=8;
                            dw+=dat[a0]; a0++; lin+=AnsiString().sprintf("%i,",dw);
                        lin[lin.Length()]=')'; continue;
                    if (cmd[0]==0xB9){ cm="END IF"; continue; }
                    if ((cmd[0]==0x9B)&&(cmd[1]==0xA5)){ a0+=1; cm="PRINT"; continue; }
                    if ((cmd[0]==0xBA)&&(cmd[1]==0x00)){ a0+=1; cm="RETURN"; continue; }
                    if ((cmd[0]==0xBA)&&(cmd[1]==0x01)){ a0+=1; cm="RETURN"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x1C)){ a0+=1; cm="STOP"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x05)){ a0+=1; cm="END"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x06)){ a0+=1; cm="FNEND"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x0D)){ a0+=1; cm="OFF ERROR"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x17)){ a0+=1; cm="PAUSE"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x23)){ a0+=1; cm="WAIT"; continue; }
                    if ((cmd[0]==0xBD)&&(cmd[1]==0x24)){ a0+=1; cm="BEEP"; continue; }
                    if ((cmd[0]==0x95)&&(cmd[1]==0xB3)&&(cmd[2]==0x8E)){ a0+=2; lin+="GOSUB"; continue; }
                    if ((cmd[0]==0xBB)&&(cmd[1]==0x03)&&(cmd[2]==0x0B)){ a0+=2; lin+="REPEAT"; continue; }
                    if ((cmd[0]==0xBB)&&(cmd[1]==0x03)&&(cmd[2]==0x15)){ a0+=2; lin+="END SELECT"; continue; }
                    if ((cmd[0]==0xBB)&&(cmd[1]==0x1F)&&(cmd[2]==0x22)){ a0+=2; lin+="CLEAR ERROR"; continue; }
                    if ((cmd[0]==0xBB)&&(cmd[1]==0x1F)&&(cmd[2]==0x40)){ a0+=2; lin+="CLEAR SCREEN"; continue; }
                    if ((cmd[0]==0xB8)&&(cmd[1]==0x8E)&&(cmd[2]==0x00)&&(cmd[3]==0x00)){ a0+=3+2; lin+="ELSE"; continue; }
                    if ((cmd[0]==0xBB)&&(cmd[1]==0x03)&&(cmd[2]==0x10)&&(cmd[3]==0x8E)&&(cmd[4]==0x00)&&(cmd[5]==0x00)){ a0+=5+2; lin+="CASE ELSE"; continue; }

                if ((c<32)||(c>127))    // non ASCII character
                    if ( _txt){ _txt=0; lin+="' "; }
                    if (!_hex){ _hex=1; lin+=" ["; } else lin+=" ";
                else{                   // ASCII character
                    if ( _hex){ _hex=0; lin+="] "; }
                    if (!_txt){ _txt=1; lin+=" '"; }
            if (_hex){ _hex=0; lin+="] "; }
            if (_txt){ _txt=0; lin+="' "; }

            if ((cm!="")||(ops))        // forced end of line [B3 8E]
                if (cm!="") lin+=cm;
                for (i=0;i<ops;i++) lin+=" "+op[i];
                lin+=" |!!|";
            // word wrap + hex debug
            for (i=1;;)     // proces wrapped lines
                if (_enable_wordwrap)   // cut code
                    if (i==1) s=lin.SubString(i,_sz_bas);
                    else      s=lin.SubString(i,_sz_bas-_sz_lin);
                    if (i==1) s=lin;
                    else      s="";
                l=s.Length(); if (l==0) break;
                if (i!=1) s=slin+s; i+=l;
                if (_enable_hexview)
                    while (s.Length()<_sz_bas) s+=" ";  // align to end of basic column
                    // add hex view
                    for (s+=AnsiString().sprintf("| %04X:",adrh);(adrh<a1)&&((s.Length()<=_sz_hex-3)||(!_enable_wordwrap));adrh++) s+=AnsiString().sprintf(" %02X",dat[adrh]);
            // hex view (if not finished yet)
            if (_enable_hexview) for (;adrh<a1;)
                for (s=sbas+AnsiString().sprintf("| %04X:",adrh);(adrh<a1)&&((s.Length()<=_sz_hex-3)||(!_enable_wordwrap));adrh++) s+=AnsiString().sprintf(" %02X",dat[adrh]);
            // ----------------------------------------------------------------------------------
            // ----------------------------------------------------------------------------------
            // ----------------------------------------------------------------------------------

            // next line offset
            if (!sos)
                adr+=len;                               // point out next line first byte position
                if (((c!=0x40)&&(c!=0x48)&&(c!=0xC0)&&(c!=0xC8))||(adr==adr1))  //check if this is 0x40/0xC0 or not!
                    eos=true;                           // if it is not or we are past the lines for this SUB/FN, done for this SUB/FN
                    adr=adr1;                           // quick jump to next start of SUB/FN
            // secnd line in section offset
                adr+=len;                               // increment to next real line!

    // remove zeros from txt
    for (l=txt.Length(),i=1;i<=l;i++)
     if (txt[i]==0) txt[i]='.';

    delete[] dat;
    return txt;

I think this could get you started quite nicely. Simple commands has 4 BYTE codes where first BYTE also tells if the command use constant (0x80+constant up to unknown range probably just 0..9 or similar) or variable operand (0x00+variable_index). End of line usually ends with 0xB3 0x8E.

Here few decoded lines outputted from the decoder (PS_EDI~1.PROG):

3250:    385          END SELECT |B3|                | 3250: 40 0A 5E 09 01 81 BB 03 15 B3
325A:    386          WAIT 5 |B3|                    | 325A: 40 0A 0A 09 01 82 85 BD 23 B3
3264:    387           [D4 B2 00 B3 8E]  var09 |!!|  | 3264: 40 0C 0A 09 01 83 0A D4 B2 00 B3 8E
3270:    388          RETURN 0 |B3|                  | 3270: 40 0A 0C 09 01 84 80 BA 01 B3
327A:    389        FNEND |B3| |8E|                  | 327A: 40 0A 0A 07 01 85 BD 06 B3 8E

and the same from ASCII reference file:

385     END SELECT
386     WAIT 5
387     Title_screen
388     RETURN 0
389   FNEND
| improve this answer | |
  • @Staffan link works as should, I reedited my answer with new info and code – Spektre Jul 27 '19 at 12:37
  • @Staffan looks like BYTE offset 002Eh points to 32bit value that is variable table start-12. The variables are in format 1 BYTE string size, string. You need to skip zeros (by scanning for next non zero BYTE) and there can be also consequent strings (no zeros between them). But only for 16bit 32 bit format seems to have different encoding including the code sections !!! – Spektre Aug 5 '19 at 12:18
  • @Staffan I updated code (have finally merged your BASIC ported to C++ with mine into the decoder) its a bit more complex but the added aligned code and hex views side by side pays off a lot ... the decoder also outputs the content of variable table (before the Basic code) but the indexes does not seem to match the encoding in code ... I cleared obsolete comments here (the stuff starts to be unreadable you should delete your obsolete ones too) – Spektre Aug 18 '19 at 17:25
  • @Spektre If you find yourself hitting the 30k limit whilst drastically editing the content of your answer, it's probably best to post another one, and leave the existing version alone. Having multiple answers is fine if they're very different. – wizzwizz4 Aug 19 '19 at 14:40
  • @wizzwizz4 I removed the commands list ... they are useless without proper order or tokens anyway but I left the link to it so its searchable again... I am hitting 30K time to time especially for more complex codes ... but this one is caused also by the fact that the decoded ASCII output is above 100K itself ... – Spektre Aug 19 '19 at 14:43


Many thanks for your suggestions. I have received some more code and been able to figure out a little more from your investigation.

Also at the start of file is some kind of header starting with:

// 16 bit: 0000 FFFF size 4000 0007 00 00C2 00C2

// 32 bit: 0000 FFFF size 4000 0007 00 000000C2 000000C2

Yes. Size is in fact the position of last basic code line in the file. It is offset from the page (typically 0x200) where the header sits. This may seem strange, but there may be a clue to this.

After some small elaboration and hex viewing I found out that that are two formats (16 and 32 bit) in the files you linked. Lines are encoded like this in hex:

ID 40,48 or C8 for valid line start unknown 2 Bytes 07 linenum 4 Bytes (32 bit) or 2 Bytes (16 bit) usually B68E07 but not always so to detect start of line just search for 40/48/C8 followed by 07 after 2 Byte gap.

Each basic line seems to start with 0x40, 0x48, 0xC0 or 0xC8. Then follows: xx yy zz lineno. xx is the count of bytes for the current line (so no line is longer than 255 bytes in tokenized format). yy is count of previous line and zz is the indentation of this line (characters before the basic text starts.

Thus, knowing where the last line starts, one can track backwards until the first line, where the previous line has the length 0. Data before this should be configuration information I guess.

[Edit1] some more info.

A bit of update on the file structure. I have uploaded two files that now combine each ASCII line with corresponding binary data. The link is https://ulozto.net/!iCk2A6VJl6e1/combined-files-zip. Combined files

In summary (taken from notes - some repetition from @Spektre):

Note: the below discussion relates to a disc image file, possibly containing several basic files (.PROG files). A true .PROG file does not contain a header as mentioned below. The discussion for a .PROG file is valid, just subtract 0x200 from the address.

All files seem to be have a header from 0x0000 to 0x01FF. The actual code begins at 0x0200 with the following structure:

00000200: 0000 0000 ffff ffff 0001 e1da 4000 0007  ............@...
00000210: 0000 c200 c200 ffff 0000 1292 1000 0000  ................
00000220: 01aa 0000 ffff 0000 06fc 0000 124e 0000  .............N..
00000230: 0352 03aa 0000 1258 003a 0000 0352 004f  .R.....X.:...R.O
00000240: 0000 124e 0000 0000 0018 0000 0704 2020  ...N..........

Important locations: 0x020c: 0x40 - start of a valid code line. A code line starts with 0x40 or 0xC0.

This byte is followed by the following bytes:

aa bb cc dddd, where

aa - number of bytes in this line

bb - number of bytes in previous line

cc - number of characters (including line number) that the BASIC line is indented

dddd - BASIC line number

In the above example, aa and bb are both zero, signifying that this is a special line. Most likely beginning of a section (e.g., SUB or FN definition - or in this case the beginning of the code). Each section seems to have the following structure:

Offset 0x0C from start location (0x020C), the offset to next section is stored as a 32 bit number (here: 0x00001292). 0x020C+0x1292 = 0x149E, being the start of next section (not shown here, but identified).

Offset 0x1C from start location, the offset to next valid line within this section is stored as a 16 bit number (here: 0x06fc). 0x020C + 0x06fC = 0x0908 - the first actual line! Possibly, the number is 32 bit starting at 0x1A.

Offset 0x24 from start location, the offset to the first internal string is stored as a 16 bit number (here: 0x0352). 0x020C + 0x0352 = 0x055E where the first string is located! Actually, the first byte that is pointed out identifies the length of the following string, i.e., byte at 0x055E indicates the length of the following string.

Also, special to the first section of the program, in pos 0x208 (the four bytes just ahead of the first line), an offset from start location showing first byte outside the actual BASIC file is stored as a 32 bit value (here:0x0001E1DA). 0x020C + 0x1E1DA = 0x1E3E6, happening to be the first byte outside the actual file...

Minor addition. In several locations, the byte 0x8E seems to exist for no apparent reason. It is likely a filling byte making sure next relevant byte (new line etc) starts on an even address.

There seem to be some very simple read protect of Basic files. As discussed in the following thread: https://groups.io/g/VintHPcom/topic/33005145#2030 it appears as the first bit (highest bit in first byte perhaps) in a program line has its value flipped - speaking for a 0xC0 or 0xC8 instead of 0x40 or 0x48 as start of line.



| improve this answer | |
  • @Spektre, I would be happy to upload a package with files - any hint on how? Is there any file area here? – Staffan Jul 26 '19 at 6:56
  • no I usually use https://ulozto.net as it does not require any registration for upload nor download (just chose slow download and write the 4 characters for anti-bot check and that is it for download, upload is direct) just zip the files to one file (and change the names so corresponding bin and ASCII files have same name only different extention) Then just comment here the generated link for the file download ... – Spektre Jul 26 '19 at 7:51
  • 1
    @Spektre, thanks for the suggestion. Here is the link: ulozto.net/!HPLYtSyXMtdg/basic-ascii-zip hope it works... The files Manager and PS_EDI seems to be regular Basic files whereas the rest is 'addins' – Staffan Jul 27 '19 at 7:03
  • @Spektre, referring to comment on PS_EDI~.PROG above (can't add comment there for some reason). Check for example line 131, found at 0x130C. At an offset of 0x1C from this location (0x1328), there is an offset to next line (0xB8, or rather 0x0000 00B8?). Adding 0xB8 to 0x130C gives 0x13C4, which is the first 'real' line. Likewise for offset 0x0C from 0x130C where the offset 0x013C is found. Adding 0x013C to 0x130C gives 0x1448, which is beginning of next section - line 139. – Staffan Aug 6 '19 at 7:55
  • 1
    @Staffan It looks like you've accidentally created two accounts – 14580 and 14679. This is a little confusing for us and causes issues with editing your own posts, so please request an account merger. – wizzwizz4 Aug 7 '19 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.