Skip to content

Instantly share code, notes, and snippets.

@h1romas4
Last active August 3, 2026 13:54
Show Gist options
  • Select an option

  • Save h1romas4/bd737eb3d792aed148fa2bf9c12370da to your computer and use it in GitHub Desktop.

Select an option

Save h1romas4/bd737eb3d792aed148fa2bf9c12370da to your computer and use it in GitHub Desktop.
PC-88VA PC-Engine にマウント可能な .d88 イメージの生成手順

PC-88VA PC-Engine にマウント可能な .d88 イメージの作成手順

ツール

  • mkfs.fat - Ubuntu 26.04 LTS (Windows の場合は WSL2 にて)
  • Python3
  • d88toolg
  • fat12_cluster_delta.py - PC88VA (PC-Engine)? 用の FAT クラスターパッチ(本 gist に添付)
  • mcopy
$ sudo apt install mtools
$ mcopy --help
Mtools version 4.0.49, dated June 14th, 2025
Usage: mcopy [-spatnmQVBT] [-D clash_option] sourcefile targetfile
       mcopy [-spatnmQVBT] [-D clash_option] sourcefile [sourcefiles...] targetdirectory
       mcopy [-tnvm] MSDOSsourcefile

PC-Engine 向け FAT12 を作成して任意のファイルを .d88 に転送

# ワークディレクトリ作成
mkdir images  # イメージ格納用
mkdir install # .d88 に入れたいファイルを入れとく
mkdir tools   # 添付の fat12_cluster_delta.py 入れておく
# d88toolg を clone
git clone https://github.com/gorry/d88toolg.git
# geometry/media を PC-Engine 向けに合わせた FAT12 img を作る
dd if=/dev/zero of=images/pcengine_fat12.img bs=1024 count=1280 status=none
# FAT12 を mkfs - メディアタイプは必ず `-M 0xfe` にすること。異なると PC-Engine から「メディアタイプが違います」と言われる。
mkfs.fat -F 12 -S 1024 -s 1 -f 2 -R 1 -r 224 -g 2/8 -M 0xfe -n "PCENGINE" images/pcengine_fat12.img
# 入れたいファイルを mtools の mcopy で FS に転送
mcopy -i images/pcengine_fat12.img -s install/* ::/
# FAT12 の開始クラスタ番号を本 gist 添付の fat12_cluster_delta.py で +1 パッチする(NEC DOS 仕様?)
python3 tools/fat12_cluster_delta.py images/pcengine_fat12.img images/pcengine_fat12_clusterdelta.img -d 1
# できたイメージを d88toolg の d88make で .d88 にする
python3 d88toolg/d88make.py -a "PCENGINE" -d HD -c 80 -s 2 -r 8 -n 1024 -i images/pcengine_fat12_clusterdelta.img images/pcengine_fat12_clusterdelta.d88
# エミュレーションに images/pcengine_fat12_clusterdelta.d88 をマウントした後 PC-Engine から次の操作でファイルが見えるはず。
B:
DIR /W
TYPE [FILENAME.EXT]
# 例えば HOGE.COM のファイル実行は HOGE だけでいける
HOGE
#!/usr/bin/env python3
import argparse
import sys
from pathlib import Path
def read_le16(data, offset):
return int.from_bytes(data[offset:offset + 2], "little")
def write_le16(data, offset, value):
data[offset:offset + 2] = value.to_bytes(2, "little")
def fat12_get(data, cluster):
offset = (cluster * 3) // 2
if cluster & 1:
return ((data[offset] >> 4) | (data[offset + 1] << 4)) & 0x0FFF
return (data[offset] | ((data[offset + 1] & 0x0F) << 8)) & 0x0FFF
def fat12_set(data, cluster, value):
value &= 0x0FFF
offset = (cluster * 3) // 2
if cluster & 1:
data[offset] = (data[offset] & 0x0F) | ((value & 0x0F) << 4)
data[offset + 1] = (value >> 4) & 0xFF
else:
data[offset] = value & 0xFF
data[offset + 1] = (data[offset + 1] & 0xF0) | ((value >> 8) & 0x0F)
def parse_args():
parser = argparse.ArgumentParser(
description="Renumber FAT12 cluster numbers by a fixed delta in directory entries and FAT chains.",
)
parser.add_argument("input", help="Input FAT12 image")
parser.add_argument("output", help="Output FAT12 image")
parser.add_argument(
"-d",
"--delta",
type=int,
required=True,
help="Signed cluster delta to apply to directory start clusters and FAT entry numbers",
)
return parser.parse_args()
def main():
args = parse_args()
src = Path(args.input)
dst = Path(args.output)
image = bytearray(src.read_bytes())
bytes_per_sector = read_le16(image, 11)
sectors_per_cluster = image[13]
reserved_sectors = read_le16(image, 14)
fat_count = image[16]
root_entries = read_le16(image, 17)
total_sectors = read_le16(image, 19)
sectors_per_fat = read_le16(image, 22)
if bytes_per_sector == 0 or sectors_per_cluster == 0:
print("Error: Invalid BPB in input image.", file=sys.stderr)
return 1
root_dir_sectors = (root_entries * 32 + bytes_per_sector - 1) // bytes_per_sector
data_start_sector = reserved_sectors + fat_count * sectors_per_fat + root_dir_sectors
cluster_count = (total_sectors - data_start_sector) // sectors_per_cluster
max_cluster = cluster_count + 1
fat_size_bytes = sectors_per_fat * bytes_per_sector
fat_offsets = []
fat_start = reserved_sectors * bytes_per_sector
for fat_index in range(fat_count):
fat_offsets.append(fat_start + fat_index * fat_size_bytes)
root_offset = (reserved_sectors + fat_count * sectors_per_fat) * bytes_per_sector
root_size = root_entries * 32
root = image[root_offset:root_offset + root_size]
updated_entries = 0
for offset in range(0, len(root), 32):
entry = root[offset:offset + 32]
if entry[0] in (0x00, 0xE5):
continue
attr = entry[11]
if attr == 0x0F or (attr & 0x08):
continue
start_cluster = read_le16(entry, 26)
if start_cluster == 0:
continue
new_cluster = start_cluster + args.delta
if not (2 <= new_cluster <= max_cluster):
print(f"Error: Directory entry cluster {start_cluster} would become invalid ({new_cluster}).", file=sys.stderr)
return 1
write_le16(root, offset + 26, new_cluster)
updated_entries += 1
image[root_offset:root_offset + root_size] = root
original_fat = bytearray(image[fat_offsets[0]:fat_offsets[0] + fat_size_bytes])
primary_fat = bytearray(original_fat)
updated_fat_entries = 0
for cluster in range(2, max_cluster + 1):
fat12_set(primary_fat, cluster, 0)
for cluster in range(2, max_cluster + 1):
value = fat12_get(original_fat, cluster)
if value == 0:
continue
new_cluster = cluster + args.delta
if not (2 <= new_cluster <= max_cluster):
print(f"Error: FAT entry index {cluster} would become invalid ({new_cluster}).", file=sys.stderr)
return 1
new_value = value
if 2 <= value < 0x0FF0:
new_value = value + args.delta
if not (2 <= new_value <= max_cluster):
print(f"Error: FAT entry {cluster} target {value} would become invalid ({new_value}).", file=sys.stderr)
return 1
fat12_set(primary_fat, new_cluster, new_value)
updated_fat_entries += 1
for fat_offset in fat_offsets:
image[fat_offset:fat_offset + fat_size_bytes] = primary_fat
dst.write_bytes(image)
print(f"Wrote {dst}")
print(f" updated directory entries: {updated_entries}")
print(f" updated FAT entries: {updated_fat_entries}")
print(f" cluster delta: {args.delta}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment