• Please review our updated Terms and Rules here

Concurrent CP/M-86 for the Lomas Thunder 186

RichCini

Veteran Member
Joined
Aug 6, 2005
Messages
663
Location
Long Island, NY
All --

The other day I picked up a set of original Concurrent CP/M-86 3.1 5.25" 360K DSDD disks for the Lomas Thunder 186 CPU card. I did a raw Greaseweazle image of them and the reads look to be clean. What I don't know is the format of the disk so I can use cpmtools. The manual provided doesn't mention the disk format at all, other than "The IBM-PC double sided format is supported on 5-1/4" diskettes."

I take this to mean the standard IBM 40 track, 8 sectors per track, 512 bytes per sector but none of the existing diskdefs seem to work. Is there any way to look at an arbitrary disk image and figure out the image? Rather than greaseweazle, should I switch to say ImageDisk and see what that comes up with?

Thanks!
Rich
 
Rich,
Since you did copy the Floppy with the Greasewezle, what type image did you create?
*.IMD, *.RAW, *.IMG, *.SCP, or *.???

If you post the Image we can have a look and see what the format is and locate a Definition
or create one that works with cpmtools.

There are lots of possibilities, but it shouldn't be too hard.

You can find some CP/M Definitions here:
https://github.com/ldkraemer/CPM-Floppy-Definitions

Need at TUTORIAL on cpmtools or cpmtools built with libdsk/
https://forums.debian.net/viewtopic.php?t=112244


Some possible Definition's:
Code:
#
# 22DISK
BEGIN IBM1  IBM PC, CP/M-86 - SSDD 48 tpi 5.25" - 512 x 8
DENSITY MFM, LOW
CYLINDERS 40 SIDES 1 SECTORS 8,512
SIDE1 0 1,2,3,4,5,6,7,8
BSH 3 BLM 7 EXM 0 DSM 155 DRM 63 AL0 0C0H AL1 0 OFS 1
END

# cpmtools
# IBM1  IBM PC, CP/M-86 - SSDD 48 tpi 5.25" - 512 x 8
diskdef ibm1
  seclen 512
  tracks 40
  sectrk 8
  blocksize 1024
  maxdir 64
  skew 1
  boottrk 1
  os 2.2
end

# libdsk
[ibm1]
description = IBM1  IBM PC, CP/M-86 - SSDD 48 tpi 5.25" - 512 x 8
cylinders = 40
heads = 1
secsize = 512
sectors = 8
secbase = 1
datarate = DD

# Flashfloppy/GOTEK
[ibm1]
cyls = 40
heads = 1
secs = 8
interleave = 1
bps = 512
id = 1
#rpm = 360
rpm = 300
rate = 250
mode = mfm
iam = no


# 22DISK
BEGIN IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
DENSITY MFM, LOW
CYLINDERS 40 SIDES 2 SECTORS 8,512
SIDE1 0 1,2,3,4,5,6,7,8
SIDE2 1 1,2,3,4,5,6,7,8
ORDER CYLINDERS
BSH 4 BLM 15 EXM 1 DSM 157 DRM 63 AL0 080H AL1 0 OFS 1
END

# cpmtools
# IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
diskdef ibm2
  seclen 512
  tracks 80
  sectrk 8
  blocksize 2048
  maxdir 64
  skew 1
  boottrk 1
  os 2.2
end

# libdsk
#ORDER CYLINDERS
[ibm2]
description = IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
sides = outback
cylinders = 80
heads = 2
secsize = 512
sectors = 8
secbase = 1
datarate = DD

# Flashfloppy/GOTEK
[ibm2]
cyls = 80
heads = 2
secs = 8
interleave = 1
bps = 512
id = 1
#rpm = 360
rpm = 300
rate = 250
mode = mfm
iam = no

Thanks.

Larry
 
Larry --

I created raw SCP images and then converted them to IMG using HxC. I ran one of them through ImageDisk which identified them as MFM 40 track, 8 sector, double-sided 512 byte disks. I'll put the raw SCP images on my GitHub shortly. I don't think I have some of the diskdefs you are showing above so I'll add them.


Rich
 
Ok, I spent a bit of time with this over the weekend, and with the help of both Peter Higgins and ClaudeAI, we discovered that the extent map was trashed -- somehow overwritten by a pattern that doesn't match any CPM setup. But, Peter recovered 12 files. Claude also extracted the same 12, identified which others were missing, and then provided the raw sector data. Even so, asm86.cmd still seem damaged, and ddt86.cmd, systat.cmd and systat1.cmd (not sure why Lomas had two versions) are missing. Manual reconstruction based on sector data would be needed. Grrr.

If anyone has working copies of these, please PM me. I'd rather not try to manually assemble these if I can avoid it. Thanks!
 
Another list member (@newcastle_j) and I have been noodling over this for about a week now and here's what's happening.

With cpmtools I can see the directories on all of the disks (other than disk 1 which is trashed) but when I extract or inject files from/to an image, the files are corrupted. The files appear in the directory, but are mangled or truncated. We've used the Eagle CP/M-86 emulator on PCJS (which is a CP/M 2.2 emulation) to read the Lomas disks and the files appear complete -- if you type an assembly source file, it's complete with no corruption. I'm using the "ibmpc-514ds" definition. I used PCJS to create a new 320k blank disk and copy the files from the Lomas disk to the new disk and they copied fine -- not corrupted. But if I manipulate

So, there has to be an issue with the disk definition. Here's the definition I'm using:

# IBM CP/M-86
# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48
diskdef ibmpc-514ds
seclen 512
tracks 80
sectrk 8
blocksize 2048
maxdir 64
skew 0
boottrk 2
os 2.2
libdsk:format ibm320
end

I've also started with Disk 2 -- which is bootable -- and tried to manipulate the image using cpmtools to create a new Disk 1. No amount of fiddling, cajoling, or poking allows me to create a new disk using this method. I really feel there's something wrong with the definition, but I'm not seeing it.

Any suggestions would be greatly appreciated. Thanks!

Rich
 
Are you sure that the 'skew' parameter should be 0 and that 'boottrk' should be 2?

In the examples you were given by @ldkraemer, skew is 1 and boottrk is 1.


Assuming 5.25" DSDD floppy disks using an IBM PC compatible logical format, the table on the above wiki page aboves gives two options:
- 160/320 kB (8 sectors/track)
- 180/360 kB (9 sectors/track)

Copying the files themselves ought, logically, to work fine as long as the utility produces the correct on-disk format for the data.
 
Hm. I had also copied that definition from elsewhere on github and had tried the ibm2 definition, but that didn't work either. If I use skew 1 and boottrk 1, cpmls gives no directory output. If I change it back to boottrk 2, I get a directory listing and the same corrupted file output when using the cpmcp program. The format is definitely 40/8/512 (320k).

Is there another method or program to manipulate a CP/M image other than cpmtools?

Rich
 
Hm. I had also copied that definition from elsewhere on github and had tried the ibm2 definition, but that didn't work either. If I use skew 1 and boottrk 1, cpmls gives no directory output. If I change it back to boottrk 2, I get a directory listing and the same corrupted file output when using the cpmcp program. The format is definitely 40/8/512 (320k).

Is there another method or program to manipulate a CP/M image other than cpmtools?

Rich
After looking at this https://www.seasip.info/Cpm/cpm86form.html, I think the issue is that cpmtools may not deal properly with CP/M-86 2.x disk formats. More research to be done...
 
I don't know if it matters, but:

https://www.seasip.info/Cpm/format22.html

https://www.seasip.info/Cpm/format31.html

According to the second page, CP/M 3.1 actually defines a usage for S1 (directory entry) as Last Record Byte Count. Of course it may not be used that way in reality.

It also adds a couple fields to the DPB:
Code:
DEFB    psh    ;Physical sector shift, 0 => 128-byte sectors
            ;1 => 256-byte sectors  2 => 512-byte sectors...
DEFB    phm    ;Physical sector mask,  0 => 128-byte sectors
            ;1 => 256-byte sectors, 3 => 512-byte sectors...

Incorrectly breaking up larger sectors into 128-byte pieces might explain file mangling.
 
For Disk 2 & Disk 3, I can get good directory listings:
Code:
$ cpmls -f ibm2 -Trawob,ibm2 -D CPM_D2.RAW
     Name    Bytes   Recs  Attr     update             create
------------ ------ ------ ---- -----------------  -----------------
ABORT   .RSP     2K     11    
BDOS    .CON    16K    123    
CCPM    .SYS    70K    552    
CIO     .CON     6K     43    
CLOCK   .RSP     2K      5    
DIR     .RSP     4K     21    
GENCCPM .CMD    52K    411    
GENCCPM1.CMD    52K    411    
HELP    .HLP    46K    361    
MEM     .CON     6K     48    
PIN     .RSP     4K     26    
RTM     .CON     8K     59    
SUP     .CON    10K     68    
SYSDAT  .CON     4K     26    
TMP     .RSP     2K     16    
VOUT    .RSP     4K     23    
XIOS    .CON     8K     58    
   17 Files occupying    296K,      22K Free.

and

Code:
$ cpmls -f ibm2 -Trawob,ibm2 -D CPM_D3.RAW
     Name    Bytes   Recs  Attr     update             create
------------ ------ ------ ---- -----------------  -----------------
CLOCK   .A86     6K     44    
CONFIG  .A86     2K     14    
ECHO    .A86     4K     28    
FORMAT  .A86    10K     73    
HARDDISK.I86    10K     77    
INIT186 .A86     2K     15    
MFORMAT .A86    10K     72    
MTRACK0 .        4K     32    
PGEN    .IN      2K      2    
PGENH   .IN      2K      2    
T186    .LIB     8K     52    
T186MON .A86    58K    450    
T525LOAD.A86    30K    237    
TCOPY   .A86     6K     42    
TFLPYIO .A86    16K    122    
TMP     .A86    16K    124    
WFRMT   .A86    12K     88    
XIOS    .A86    52K    412    
   18 Files occupying    250K,      68K Free.

But, I can't extract any files.


Code:
Directory starts at 0x2000 = 8192 (512 x 16) = Track 2
00002000   00 47 45 4E  43 43 50 4D  20 43 4D 44  01 00 00 80  .GENCCPM CMD....
00002010   01 02 03 04  05 06 07 08  09 0A 0B 0C  0D 0E 0F 10  ................
00002020   00 47 45 4E  43 43 50 4D  20 43 4D 44  03 00 00 1B  .GENCCPM CMD....
00002030   11 12 13 14  15 16 17 18  19 1A 00 00  00 00 00 00  ................
00002040   00 53 55 50  20 20 20 20  20 43 4F 4E  00 00 00 44  .SUP     CON...D
00002050   1B 1C 1D 1E  1F 00 00 00  00 00 00 00  00 00 00 00  ................
00002060   00 53 59 53  44 41 54 20  20 43 4F 4E  00 00 00 1A  .SYSDAT  CON....
00002070   20 21 00 00  00 00 00 00  00 00 00 00  00 00 00 00   !..............
00002080   00 42 44 4F  53 20 20 20  20 43 4F 4E  00 00 00 7B  .BDOS    CON...{
00002090   22 23 24 25  26 27 28 29  00 00 00 00  00 00 00 00  "#$%&'()........
000020A0   00 43 49 4F  20 20 20 20  20 43 4F 4E  00 00 00 2B  .CIO     CON...+
000020B0   2A 2B 2C 00  00 00 00 00  00 00 00 00  00 00 00 00  *+,.............
000020C0   00 52 54 4D  20 20 20 20  20 43 4F 4E  00 00 00 3B  .RTM     CON...;
000020D0   2D 2E 2F 30  00 00 00 00  00 00 00 00  00 00 00 00  -./0............
000020E0   00 4D 45 4D  20 20 20 20  20 43 4F 4E  00 00 00 30  .MEM     CON...0
000020F0   31 32 33 00  00 00 00 00  00 00 00 00  00 00 00 00  123.............
00002100   E5 58 49 4F  53 20 20 20  20 43 4F 4E  00 00 00 3F  .XIOS    CON...?
00002110   34 35 36 37  00 00 00 00  00 00 00 00  00 00 00 00  4567............
00002120   00 43 4C 4F  43 4B 20 20  20 52 53 50  00 00 00 05  .CLOCK   RSP....
00002130   38 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  8...............



#BEGIN IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
#DENSITY MFM, LOW
#CYLINDERS 40 SIDES 2 SECTORS 8,512
#SIDE1 0 1,2,3,4,5,6,7,8
#SIDE2 1 1,2,3,4,5,6,7,8
#ORDER CYLINDERS LABEL IBM2
#BSH 4 BLM 15 EXM 1 DSM 157 DRM 63 AL0 080H AL1 0 OFS 1
#END

# IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
diskdef ibm2
  seclen 512
  tracks 80
  sectrk 8
  sides outback    #= Order of Cylinder
  blocksize 2048
  maxdir 64
  skew 1
#  boottrk 2
  offset 8192
  boottrk 0
  os 2.2
end

# libdsk data below
[ibm2]
description = IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
sides = outback
cylinders = 80
heads = 2
secsize = 512
sectors = 8
secbase = 1
datarate = DD


Larry
 

Attachments

I was playing with Google AI and had it write a Python Script to correct the CP/M 3
Directory entries on your Boot Image. The script is attached and the repaired File
is also attached.

Code:
import os

# CP/M 3 Constants for standard 8-inch or 5.25-inch disks
# Adjust these based on your specific disk format geometry
DIR_START_OFFSET = 0x2000  # Example offset where directory tracks start
DIR_ENTRY_SIZE = 32
MAX_DIR_ENTRIES = 64
EMPTY_ENTRY = 0xE5

def repair_cpm_extents(image_path, output_path):
    if not os.path.exists(image_path):
        print(f"Error: File {image_path} not found.")
        return

    with open(image_path, "rb") as f:
        disk_data = bytearray(f.read())

    file_counters = {}
    repaired_count = 0

    print("Scanning CP/M directory entries...")

    for i in range(MAX_DIR_ENTRIES):
        offset = DIR_START_OFFSET + (i * DIR_ENTRY_SIZE)
        entry = disk_data[offset : offset + DIR_ENTRY_SIZE]

        # Skip empty/deleted entries
        if entry[0] == EMPTY_ENTRY:
            continue

        # Extract User Code, Filename, and Extension
        user_code = entry[0]
        filename = entry[1:9].decode("ascii", errors="ignore").strip()
        extension = entry[9:12].decode("ascii", errors="ignore").strip()
        full_name = f"{user_code}:{filename}.{extension}"

        # CP/M Directory Fields
        current_ex = entry[12]  # Extent counter byte
        current_s2 = entry[14]  # High-byte of extent for large files
        current_rc = entry[15]  # Record count for this extent

        # Initialize tracking for new files found
        if full_name not in file_counters:
            file_counters[full_name] = {"expected_ex": 0, "expected_s2": 0}

        expected = file_counters[full_name]

        # Validate and repair Extent (EX) sequential order
        if current_ex != expected["expected_ex"] or current_s2 != expected["expected_s2"]:
            print(f"Fixing {full_name} entry {i}: Extent changed from {current_ex} to {expected['expected_ex']}")
            disk_data[offset + 12] = expected["expected_ex"]
            disk_data[offset + 14] = expected["expected_s2"]
            repaired_count += 1

        # Force valid Record Count (RC) if it exceeds maximum limits (128 records per extent)
        if current_rc > 128:
            print(f"Fixing {full_name} entry {i}: Capping invalid RC ({current_rc}) to 128")
            disk_data[offset + 15] = 128
            repaired_count += 1

        # Increment expected values for the next extent of this file
        expected["expected_ex"] += 1
        if expected["expected_ex"] > 31:  # CP/M extent byte rolls over at 32 into S2
            expected["expected_ex"] = 0
            expected["expected_s2"] += 1

    # Save repaired disk image
    with open(output_path, "wb") as f:
        f.write(disk_data)

    print(f"\nRepair complete. Fixed {repaired_count} anomalies. Saved to {output_path}")

# Run the repair tool
if __name__ == "__main__":
    # Replace with your actual file names
    input_disk = "CPM_D1.RAW"
    output_disk = "CPMFIX_D1.RAW"
   
    # Create a dummy test file if you don't have one yet
    if not os.path.exists(input_disk):
        with open(input_disk, "wb") as dummy:
            dummy.write(b"\x00" * 256 * 1024) # Blank 256KB disk image
           
    repair_cpm_extents(input_disk, output_disk)

Here is what it repaired/Fixed:
Code:
$ python3 cpm_repair.py
Scanning CP/M directory entries...
Fixing 3:``. entry 0: Extent changed from 0 to 0
Fixing 3:. entry 1: Extent changed from 0 to 0
@. entry 3: Extent changed from 0 to 0
Fixing 18:$T. entry 4: Extent changed from 255 to 0
Fixing 0:. entry 5: Extent changed from 0 to 0
Fixing 8:. entry 6: Extent changed from 4 to 0
Fixing 8:. entry 6: Capping invalid RC (133) to 128
Fixing 14:. entry 7: Extent changed from 20 to 0
Fixing 14:. entry 7: Capping invalid RC (133) to 128
Fixing 29:.! entry 8: Extent changed from 1 to 0
Fixing 4:.@ entry 9: Extent changed from 65 to 0
Fixing 4:.@ entry 9: Capping invalid RC (135) to 128
Fixing 75:LM. entry 10: Extent changed from 36 to 0
Fixing 75:LM. entry 10: Capping invalid RC (133) to 128
Fixing 0:␦ntry 11: Extent changed from 1 to 0
Fixing 5. entry 12: Extent changed from 11 to 0
. entry 13: Extent changed from 17 to 0
. entry 13: Capping invalid RC (132) to 128
Fixing 3:MXLoad. entry 14: Extent changed from 1 to 0
Fixing 0:SYSTAT.D entry 18: Extent changed from 1 to 0
Fixing 0:ASM86.D entry 19: Extent changed from 1 to 0
Fixing 0:SYSTAT1.D entry 25: Extent changed from 1 to 0

Repair complete. Fixed 22 anomalies. Saved to CPMFIX_D1.RAW
larry@larry-HP-EliteBook-8470p:~/IMDs/Lomas-Thunder$ hexedit CPMFIX_D1.RAW

[1]+  Stopped                 hexedit CPMFIX_D1.RAW


Hopefully, it will be repaired properly.


Larry
 

Attachments

  • CPMFIX_D1.png
    CPMFIX_D1.png
    408.5 KB · Views: 2
  • CPMFIX_D1.zip
    CPMFIX_D1.zip
    920.8 KB · Views: 0
@ldkraemer Is an RC value > 80H (128) really invalid? The format descriptions say that such a case means the extent controlled by that directory entry is full and the file itself may use another extent.

Obviously a single 8-bit byte can only store values in the range 0-255, but values like 84H (132) are still inside that range.

Shouldn't you also check for and skip over any directory entry that might be a disc label, date stamp, etc?
 
Istarian,
I was way over my head using Gemini to create the Python script. If you are versed in
CP/M 3 how about a bit of help modifying the Python code. I'm not familiar with the
Directory structure of CP/M 3. That's why I posted the code that Gemini conjured up.

THANKS.

Larry
 
I can write Python code just fine, but alas I don't know much about CP/M beyond what materials I can read online.

I was just looking at this page (mentioned in previous post) and noting that it claims multiple types of directory entry.


Probably you can just tack some more cases onto the entry skip code, like this:

Python:
DATE_ENTRY = 0x21

if entry[0] == EMPTY_ENTRY or entry[0] == DATE_ENTRY:
    continue

I'll try having a look at his file in a hex editor.
 
Last edited:
Looking at 'ccpm86_31_d1_scp.img' shows what looks like 28 used and 36 empty/erased directory entries between 0x2000 and 0x2800.

From 0x2200 to 0x273F there are 12 entries for a file of type CMD:
VCMODE, ED, SYSTAT, ASM86, TYPE, PIP, SET, DATE, CHSET, SYSTAT1, FORMAT, MFORMAT

The preceding 16 entries from 0x2000 to 0x21FF look like gibberish to the eye, especially without human readable name data. But I really don't know what they should look like or if I should expect that they refer to a region of bootable code. The readable text 'MXLoad' is the only sensible text up there.

NOTE:
bit 7 (0-index) of T1, T2, and T3 are used as file attributes and the actual character is just 7-bit ASCII. (e.g. ÃÍD -> CMD),
doing a bit-wise and of each of those bytes against 0x7F (bit mask) would clear bit 7 if you wanted to clean it up before decoding

all of the entries occupying 0x2380 to 0x27FF are filled with 0xE5

EDIT
The text 'DIGITAL RESEARCH CONCURRENT CP/M-86 2.0' appears at least 14 times, so I guess a lot of files must have stayed the same in between version changes.

I agree with the ClaudeAI summary that the first 16 entries have been trashed/corrupted in some fashion...

------

The copies of Concurrent CP/M-86 v3.1 on the above linked site (noted as IBM PC XT, Starlink) have directory entries with the following file names and types (written as ms-dos 8.3 style naming) on Disk 1.

MUCONFIG.CMD
OSMOVE.BAT
CCPM.SYS (x5 entries)
DATA.PFK
MUINSTL.CMD
TERM.BAT
PIP.CMD
HDMAINT.CMD
DSKMAINT.CMD
OSM2.BAT
DIR.CMD
BATCH.CMD
GLASS.SUB
CARDDECK.SUB
CLEAR.SUB
EXAMPLE.LTR
README.DOC
WORDPROC.SUB
MUSW.SYS
 
Last edited:
The preceding 16 entries from 0x2000 to 0x21FF look like gibberish to the eye, especially without human readable name data. But I really don't know what they should look like or if I should expect that they refer to a region of bootable code. The readable text 'MXLoad' is the only sensible text up there.
This may be timestamps and other meta data. IIRC the cp/m 3 approach was to reserve directory entries for this.
 
I changed my input to Gemini to include the information on CP/M 3 as per John Elliot's page,
along with the IBM2 Definition. Then ask it to write a python script that would check and repair
the CP/M ver 3 Image.

Code:
BEGIN IBM2  IBM PC, CP/M-86 - DSDD 48 tpi 5.25" - 512 x 8
DENSITY MFM, LOW
CYLINDERS 40 SIDES 2 SECTORS 8,512
SIDE1 0 1,2,3,4,5,6,7,8
SIDE2 1 1,2,3,4,5,6,7,8
ORDER CYLINDERS LABEL IBM2
BSH 4 BLM 15 EXM 1 DSM 157 DRM 63 AL0 080H AL1 0 OFS 1
END
[code]

I got the attached python script.

It reported a LOG file that I attached and says it has repaired the floppy.  But, I can't get a descent directory
with cpmtools.


Larry
 

Attachments

This may be timestamps and other meta data. IIRC the cp/m 3 approach was to reserve directory entries for this.

I'm aware of the possibility, but there are nonsense values in the initial field of some entries like 60h and 4Bh. And virtually none of the bytes (Fn) for the filename contain anything sensible either.

The whole chunk of mess (offset 0x2000 - 0x21FF) looks nearly identical to another part of the file (offset 0x103A0 - 0x10570). I find that rather odd, but I'm not sure what would cause the latter to have been copied over the former.

I suspect that a close examination of the blocks not allocated to the 12 files with valid entries and trying to group that data up into potentially valid executables would be more useful.

There may also simply be some data loss.
 
I wrote your img files to floppies with dskimage and using 22disk with def IBM2 was able to read all files on disks 2 and 3 but disk 1 was corrupt.
I wrote the Claude disk 1 image to disk and all files ok. Claude must know what (he/she/it ???) is doing.

Larry G
 
Back
Top