[Scummvm-git-logs] scummvm master -> 50c277ffeb94f3e808f78d29c19bc829eda959e0

sev- noreply at scummvm.org
Mon Jun 29 20:43:20 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
50c277ffeb DEVTOOLS: COMPANION: Add CCD image conversion support


Commit: 50c277ffeb94f3e808f78d29c19bc829eda959e0
    https://github.com/scummvm/scummvm/commit/50c277ffeb94f3e808f78d29c19bc829eda959e0
Author: HabibaMahmoud2005 (bmm87464 at gmail.com)
Date: 2026-06-29T22:43:16+02:00

Commit Message:
DEVTOOLS: COMPANION: Add CCD image conversion support

Add support for CloneCD images in Dumper Companion.

Convert CloneCD IMG sector data into a temporary ISO
before passing it through the existing ISO9660
extraction pipeline. Temporary files are removed
automatically unless --keep-files is specified.

Changed paths:
    devtools/dumper-companion.py


diff --git a/devtools/dumper-companion.py b/devtools/dumper-companion.py
index 7dfc133791e..b7d913254bd 100755
--- a/devtools/dumper-companion.py
+++ b/devtools/dumper-companion.py
@@ -35,6 +35,7 @@ from io import BytesIO, IOBase, StringIO
 from pathlib import Path
 from struct import pack, unpack
 from typing import Any
+from pathlib import Path
 
 import machfs  # type: ignore
 import pycdlib  # type: ignore
@@ -92,7 +93,11 @@ decode_map = {
 SPECIAL_SYMBOLS = '/":*|\\?%<>\x7f'
 APPLE_PM_SIGNATURE = b"PM"
 SECTOR_SIZE = 512
-
+CCD_SECTOR_SIZE = 2352
+CCD_DATA_SIZE = 2048
+CCD_MODE_OFFSET = 15
+CCD_MODE1_DATA_OFFSET = 16
+CCD_MODE2_DATA_OFFSET = 24
 
 class FileSystem(Enum):
     hybrid = "hybrid"
@@ -438,32 +443,95 @@ def check_fs(iso: str) -> FileSystem:
         return FileSystem.hybrid
     return disk_formats[0]
 
+def convert_ccd_to_iso(img_path: Path, iso_path: Path) -> None:
+    """
+    Convert a CloneCD IMG image into an ISO image.
 
+    The converter strips CloneCD sector headers and writes
+    the 2048-byte sector payload expected by ISO9660 tools.
+    """
+    with open(img_path, "rb") as src, open(iso_path, "wb") as dst:
+        while chunk := src.read(CCD_SECTOR_SIZE):
+            if len(chunk) != CCD_SECTOR_SIZE:
+                raise ValueError("Incomplete CCD sector")
+            mode = chunk[CCD_MODE_OFFSET]
+
+            if mode == 1:
+                dst.write(
+                    chunk[
+                        CCD_MODE1_DATA_OFFSET:
+                        CCD_MODE1_DATA_OFFSET + CCD_DATA_SIZE
+                    ]
+                )
+            elif mode == 2:
+                dst.write(
+                    chunk[
+                        CCD_MODE2_DATA_OFFSET:
+                        CCD_MODE2_DATA_OFFSET + CCD_DATA_SIZE
+                    ]
+                )
+            elif mode == 0xE2:
+                raise ValueError(
+                    "Session marker found; only first session is supported"
+                )
+            else:
+                raise ValueError(
+                    f"Unsupported sector mode {mode}"
+                )
+            
 def extract_iso(args: argparse.Namespace) -> None:
-    loglevel: str = args.log
+    temp_iso = None
+    try:
+        # CloneCD metadata is stored in .ccd while sector data
+        # resides in the matching .img file.
+        if args.src.suffix.lower() == ".ccd":
+            img_path = args.src.with_suffix(".img")
+
+            if not img_path.exists():
+                raise FileNotFoundError(
+                    f"Associated IMG file not found: {img_path}"
+                )
+            temp_iso = args.dir / f"{args.src.stem}.iso"
 
-    numeric_level = getattr(logging, loglevel.upper(), None)
-    if not isinstance(numeric_level, int):
-        raise ValueError("Invalid log level: %s" % loglevel)
-    logging.basicConfig(format="%(levelname)s: %(message)s", level=numeric_level)
+            convert_ccd_to_iso(
+                img_path,
+                temp_iso
+            )
 
-    if not args.fs:
-        args.fs = check_fs(args.src)
-        print("Detected filesystem:", args.fs.value)
-    else:
-        args.fs = FileSystem(args.fs)
-    if args.fs in [FileSystem.hybrid, FileSystem.iso9660] and not args.extension:
-        args.extension = check_extension(args)
-        print("Detected extension:", args.extension.value)
-    elif args.extension:
-        args.extension = Extension(args.extension)
+            args.src = temp_iso
+  
+        loglevel: str = args.log
 
-    if args.fs == FileSystem.iso9660:
-        extract_volume_iso(args)
-    elif args.fs == FileSystem.hfs:
-        extract_volume_hfs(args)
-    else:
-        extract_volume_hybrid(args)
+        numeric_level = getattr(logging, loglevel.upper(), None)
+        if not isinstance(numeric_level, int):
+            raise ValueError("Invalid log level: %s" % loglevel)
+        logging.basicConfig(format="%(levelname)s: %(message)s", level=numeric_level)
+
+        if not args.fs:
+            args.fs = check_fs(args.src)
+            print("Detected filesystem:", args.fs.value)
+        else:
+            args.fs = FileSystem(args.fs)
+        if args.fs in [FileSystem.hybrid, FileSystem.iso9660] and not args.extension:
+            args.extension = check_extension(args)
+            print("Detected extension:", args.extension.value)
+        elif args.extension:
+            args.extension = Extension(args.extension)
+
+        if args.fs == FileSystem.iso9660:
+            extract_volume_iso(args)
+        elif args.fs == FileSystem.hfs:
+            extract_volume_hfs(args)
+        else:
+            extract_volume_hybrid(args)
+            
+    finally:
+        if (
+            temp_iso is not None
+            and not args.keep_files
+            and temp_iso.exists()
+        ):
+            temp_iso.unlink()
 
 
 def extract_volume_hfs(args: argparse.Namespace) -> None:
@@ -1121,6 +1189,11 @@ def generate_parser() -> argparse.ArgumentParser:
         metavar="FILE_SYSTEM",
         help="Specify the file system of the ISO",
     )
+    parser_iso.add_argument(
+        "--keep-files",
+        action="store_true",
+        help="Keep temporary files created during CCD to ISO convesrion",
+    )
     parser_iso.add_argument(
         "dir", metavar="OUTPUT", type=Path, help="Destination folder"
     )




More information about the Scummvm-git-logs mailing list