// ISO 9660 映像生成(写入侧)。
//
// 设计:先把「目录树」规整成平坦表,再按 ECMA-119 的布局顺序分配逻辑块:
// 16.. 卷描述符(PVD / 可选 Joliet SVD / 结束符)
// 之后 L、M 路径表(Joliet 时各再一对)
// 之后 每个目录的数据块(ISO 树一份,Joliet 树一份)
// 最后 文件内容
// 目录记录里的名字按三套并存:ISO 9660 level 1(8.3 + ;1)、Joliet(UCS-2 原名)、
// Rock Ridge(NM 保存原名,PX 保存 POSIX 权限)。
///|
pub struct IsoBuilder {
mut volume_id : String
mut joliet : Bool
mut rock_ridge : Bool
/// El Torito 启动映像在映像内的路径;None 表示不生成启动项
mut boot_path : String?
/// EFI 启动映像路径(作为 section header 里的条目);None 表示不生成
mut efi_path : String?
/// 启动映像需要加载的扇区数(写进启动目录)
mut boot_load_sectors : Int32
mut boot_platform : Int32
/// ISO 9660 名字级别:1(8.3)/ 2(≤31 字符)/ 3(≤207 字符)
mut name_level : Int32
/// Rock Ridge 版本:109 或 112
mut rr_version : Int32
dirs : Array[String]
files : Array[(String, Bytes)]
/// 符号链接:路径 -> 目标
symlinks : Array[(String, String)]
}
///|
pub fn IsoBuilder::new() -> IsoBuilder {
{
volume_id: "MOONISO",
joliet: true,
rock_ridge: true,
boot_path: None,
efi_path: None,
boot_load_sectors: 4,
boot_platform: 0,
name_level: 1,
rr_version: 109,
dirs: ["/"],
files: [],
symlinks: [],
}
}
///|
/// 设置 ISO 9660 名字级别(1/2/3),影响写入的 ISO 树名字,不影响 Joliet / Rock Ridge 原名。
pub fn IsoBuilder::name_level(self : IsoBuilder, level : Int32) -> Unit {
self.name_level = level
}
///|
/// 设置 Rock Ridge 版本(109 或 112):1.12 的 PX 记录多 8 字节序列号。
pub fn IsoBuilder::rr_version(self : IsoBuilder, version : Int32) -> Unit {
self.rr_version = version
}
///|
/// 追加一个 UEFI 启动项(写入启动目录的 section header,平台 0xEF)。
pub fn IsoBuilder::boot_image_efi(
self : IsoBuilder,
path : String,
load_sectors : Int32,
) -> Unit {
self.efi_path = Some(normalize_path(path))
if self.boot_path is None {
self.boot_path = Some(normalize_path(path))
}
self.boot_load_sectors = load_sectors
}
///|
/// 指定 El Torito 启动映像(必须是已加入的文件路径),生成可启动 ISO。
pub fn IsoBuilder::boot_image(
self : IsoBuilder,
path : String,
load_sectors : Int32,
platform : Int32,
) -> Unit {
self.boot_path = Some(normalize_path(path))
self.boot_load_sectors = load_sectors
self.boot_platform = platform
}
///|
pub fn IsoBuilder::volume_id(self : IsoBuilder, id : String) -> Unit {
self.volume_id = id
}
///|
pub fn IsoBuilder::joliet(self : IsoBuilder, enable : Bool) -> Unit {
self.joliet = enable
}
///|
pub fn IsoBuilder::rock_ridge(self : IsoBuilder, enable : Bool) -> Unit {
self.rock_ridge = enable
}
///|
/// 规整路径:保证以 / 开头、去掉结尾 /、折叠重复 /。
pub fn normalize_path(path : String) -> String {
let parts : Array[String] = []
for segment in path.split("/") {
let seg = segment.to_owned()
if seg != "" && seg != "." {
parts.push(seg)
}
}
let mut out = ""
for part in parts {
out = out + "/" + part
}
if out == "" {
"/"
} else {
out
}
}
///|
fn last_slash(path : String) -> Int32 {
let chars = path.to_array()
let mut result = -1
for i = 0; i < chars.length(); i = i + 1 {
if chars[i].to_int() == 47 {
result = i
}
}
result
}
///|
fn parent_of(path : String) -> String {
let normalized = normalize_path(path)
if normalized == "/" {
return "/"
}
let idx = last_slash(normalized)
if idx <= 0 {
"/"
} else {
normalized[:idx].to_owned()
}
}
///|
fn base_of(path : String) -> String {
let normalized = normalize_path(path)
let idx = last_slash(normalized)
normalized[idx + 1:].to_owned()
}
///|
/// 把名字转成 ISO 9660 level 1 的 8.3 形式(目录不带扩展名与版本号)。
fn iso_name(
name : String,
is_dir : Bool,
used : Array[String],
level : Int32,
) -> String {
// 先按 d-characters 规整:大写字母、数字、下划线,点号单独处理
let mut base = ""
let mut dots = 0
for c in name.to_array() {
let code = c.to_int()
if code >= 97 && code <= 122 {
base = base + Int32::unsafe_to_char(code - 32).to_string()
} else if (code >= 65 && code <= 90) ||
(code >= 48 && code <= 57) ||
code == 95 {
base = base + c.to_string()
} else if code == 46 {
dots = dots + 1
if level >= 3 {
base = base + "."
} else if dots == 1 && !is_dir {
base = base + "."
} else {
base = base + "_"
}
} else {
base = base + "_"
}
}
if base == "" {
base = if is_dir { "DIR" } else { "FILE" }
}
// 按级别截断
let mut candidate = if level == 1 {
// 8.3:最后一个点之后算扩展名
let mut stem = base
let mut suffix = ""
match base.rev_find(".") {
Some(at) if at > 0 && !is_dir => {
stem = base[:at].to_owned()
suffix = base[at + 1:].to_owned()
}
_ => ()
}
let stem_t = if stem.length() > 8 { stem[:8].to_owned() } else { stem }
let suffix_t = if suffix.length() > 3 {
suffix[:3].to_owned()
} else {
suffix
}
if suffix_t == "" {
stem_t
} else {
stem_t + "." + suffix_t
}
} else if level == 2 {
if base.length() > 31 {
base[:31].to_owned()
} else {
base
}
} else if base.length() > 207 {
base[:207].to_owned()
} else {
base
}
// 同目录去重
let mut suffix_index = 0
while used.contains(candidate) {
suffix_index = suffix_index + 1
let tag = suffix_index.to_string()
let limit = if level == 1 { 8 - tag.length() } else { 31 - tag.length() }
let keep = if candidate.length() > limit {
limit
} else {
candidate.length()
}
candidate = candidate[:keep].to_owned() + tag
}
used.push(candidate)
if is_dir {
candidate
} else {
candidate + ";1"
}
}
///|
/// 目录记录里的名字字段(ASCII 大写)。
fn ascii_bytes(text : String) -> Bytes {
let out = FixedArray::make(text.length(), b'\x00')
let chars = text.to_array()
for i = 0; i < chars.length(); i = i + 1 {
out[i] = chars[i].to_int().to_byte()
}
Bytes::from_array(out[:])
}
///|
/// Joliet 名字(UCS-2 大端)。
fn ucs2be_bytes(text : String) -> Bytes {
let chars = text.to_array()
let out = FixedArray::make(chars.length() * 2, b'\x00')
for i = 0; i < chars.length(); i = i + 1 {
let code = chars[i].to_int()
out[i * 2] = ((code >> 8) & 0xFF).to_byte()
out[i * 2 + 1] = (code & 0xFF).to_byte()
}
Bytes::from_array(out[:])
}
///|
fn both16(value : Int32) -> Bytes {
let out = FixedArray::make(4, b'\x00')
out[0] = (value & 0xFF).to_byte()
out[1] = ((value >> 8) & 0xFF).to_byte()
out[2] = ((value >> 8) & 0xFF).to_byte()
out[3] = (value & 0xFF).to_byte()
Bytes::from_array(out[:])
}
///|
fn both32(value : Int32) -> Bytes {
let out = FixedArray::make(8, b'\x00')
out[0] = (value & 0xFF).to_byte()
out[1] = ((value >> 8) & 0xFF).to_byte()
out[2] = ((value >> 16) & 0xFF).to_byte()
out[3] = ((value >> 24) & 0xFF).to_byte()
out[4] = ((value >> 24) & 0xFF).to_byte()
out[5] = ((value >> 16) & 0xFF).to_byte()
out[6] = ((value >> 8) & 0xFF).to_byte()
out[7] = (value & 0xFF).to_byte()
Bytes::from_array(out[:])
}
///|
fn concat_bytes(parts : Array[Bytes]) -> Bytes {
let mut total = 0
for p in parts {
total = total + p.length()
}
let out = FixedArray::make(total, b'\x00')
let mut at = 0
for p in parts {
for i = 0; i < p.length(); i = i + 1 {
out[at + i] = p[i]
}
at = at + p.length()
}
Bytes::from_array(out[:])
}
///|
/// 生成一条目录记录。
///
/// `name_bytes` 是记录里的名字字段(ISO 为 ASCII,Joliet 为 UCS-2),
/// `susp` 是 Rock Ridge 的 system use area(没有则传空)。
fn make_dir_record(
extent : Int32,
size : Int32,
flags : Int32,
name_bytes : Bytes,
susp : Bytes,
) -> Bytes {
let fixed = FixedArray::make(33, b'\x00')
fixed[1] = b'\x00' // 扩展属性长度
let extent_bytes = both32(extent)
for i = 0; i < 8; i = i + 1 {
fixed[2 + i] = extent_bytes[i]
}
let size_bytes = both32(size)
for i = 0; i < 8; i = i + 1 {
fixed[10 + i] = size_bytes[i]
}
// 记录时间:2026-01-02 03:04:05 UTC
fixed[18] = b'\x7E'
fixed[19] = b'\x01'
fixed[20] = b'\x02'
fixed[21] = b'\x03'
fixed[22] = b'\x04'
fixed[23] = b'\x05'
fixed[24] = b'\x00'
fixed[25] = flags.to_byte()
let seq = both16(1)
for i = 0; i < 4; i = i + 1 {
fixed[28 + i] = seq[i]
}
fixed[32] = name_bytes.length().to_byte()
let name = name_bytes
let padding = if name_bytes.length() % 2 == 0 {
Bytes::from_array([b'\x00'][:])
} else {
Bytes::new(0)
}
let total = concat_bytes([Bytes::from_array(fixed[:]), name, padding, susp])
let out = FixedArray::make(total.length(), b'\x00')
for i = 0; i < total.length(); i = i + 1 {
out[i] = total[i]
}
out[0] = total.length().to_byte()
Bytes::from_array(out[:])
}
///|
/// Rock Ridge 的 NM + PX 条目。
fn rr_susp(
name : String,
mode : Int32,
symlink : String?,
rr_version : Int32,
) -> Bytes {
let rr = Bytes::from_array([b'R', b'R', b'\x05', b'\x01', b'\x01'][:])
// Rock Ridge 的 NM 按惯例是 UTF-8 字节,不能用 ASCII 截断
let nm_bytes = @utf8.encode(name)
let nm = concat_bytes([
Bytes::from_array(
[b'N', b'M', (nm_bytes.length() + 5).to_byte(), b'\x01', b'\x00'][:],
),
nm_bytes,
])
// 1.09 的 PX 是 4 个字段(32 字节),1.12 多一个 8 字节序列号
let px_body = if rr_version >= 112 {
concat_bytes([both32(mode), both32(1), both32(0), both32(0), both32(0)])
} else {
concat_bytes([both32(mode), both32(1), both32(0), both32(0)])
}
let px = concat_bytes([
Bytes::from_array(
[b'P', b'X', (px_body.length() + 4).to_byte(), b'\x01'][:],
),
px_body,
])
// TF:只写修改时间(短格式 7 字节),与 ISO 记录里的日期一致
let tf_body = Bytes::from_array(
[b'\x02', b'\x7E', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x00'][:],
)
let tf = concat_bytes([
Bytes::from_array(
[b'T', b'F', (tf_body.length() + 4).to_byte(), b'\x01'][:],
),
tf_body,
])
let sl = match symlink {
Some(target) => sl_entry(target)
None => Bytes::new(0)
}
concat_bytes([rr, nm, px, tf, sl])
}
///|
/// Rock Ridge 的 SL 条目:把目标路径拆成 root 组件 + 各级名字。
fn sl_entry(target : String) -> Bytes {
let parts : Array[Bytes] = []
let mut rest = target
if rest.has_prefix("/") {
// 绝对路径:第一个组件是 root(flags = 8,长度 0)
parts.push(Bytes::from_array([b'\x08', b'\x00'][:]))
rest = rest[1:].to_owned()
}
for segment in rest.split("/") {
let seg = segment.to_owned()
if seg == "" {
continue
}
let raw = @utf8.encode(seg)
parts.push(
concat_bytes([
Bytes::from_array([b'\x00', raw.length().to_byte()][:]),
raw,
]),
)
}
let body = concat_bytes([Bytes::from_array([b'\x00'][:]), concat_bytes(parts)])
concat_bytes([
Bytes::from_array([b'S', b'L', (body.length() + 4).to_byte(), b'\x01'][:]),
body,
])
}
///|
/// 根目录「.」记录里的 SP + ER,声明本映像带 Rock Ridge。
fn rr_sp_er(rr_version : Int32) -> Bytes {
let sp = Bytes::from_array(
[b'S', b'P', b'\x07', b'\x01', b'\xBE', b'\xEF', b'\x00'][:],
)
let ext_id = if rr_version >= 112 { "IEEE_P1282" } else { "RRIP_1991A" }
let id = ascii_bytes(ext_id)
let desc = ascii_bytes("THE ROCK RIDGE INTERCHANGE PROTOCOL")
// ER:4 个长度/版本字节 + 扩展名 + 描述 + 来源
let body = concat_bytes([
Bytes::from_array(
[id.length().to_byte(), desc.length().to_byte(), b'\x00', b'\x01'][:],
),
id,
desc,
])
let er = concat_bytes([
Bytes::from_array([b'E', b'R', (body.length() + 4).to_byte(), b'\x01'][:]),
body,
])
concat_bytes([sp, er])
}
///|
/// 按 ECMA-119 要求把若干记录打包成一个或多个逻辑块(记录不得跨扇区)。
fn pack_records(records : Array[Bytes], sector : Int32) -> Bytes {
let out = FixedArray::make(records.length() * 256 + sector, b'\x00')
let mut pos = 0
for rec in records {
if pos % sector + rec.length() > sector {
pos = (pos / sector + 1) * sector
}
for i = 0; i < rec.length(); i = i + 1 {
out[pos + i] = rec[i]
}
pos = pos + rec.length()
}
let size = if pos % sector == 0 { pos } else { (pos / sector + 1) * sector }
let trimmed = FixedArray::make(size, b'\x00')
for i = 0; i < size; i = i + 1 {
trimmed[i] = out[i]
}
Bytes::from_array(trimmed[:])
}
///|
fn ceil_sectors(size : Int32, sector : Int32) -> Int32 {
if size <= 0 {
1
} else {
(size + sector - 1) / sector
}
}
///|
/// 生成过程中的目录项。
priv struct OutDir {
name : String
mut iso_name : String
parent : Int32
mut lba : Int32
mut size : Int32
mut data : Bytes
}
///|
/// 生成过程中的文件项。
priv struct OutFile {
path : String
name : String
mut iso_name : String
dir : Int32
mut lba : Int32
size : Int32
data : Bytes
/// 符号链接目标;普通文件为 None
symlink : String?
}
///|
fn index_of(arr : Array[String], value : String) -> Int32 {
for i = 0; i < arr.length(); i = i + 1 {
if arr[i] == value {
return i
}
}
-1
}
///|
/// 按名字插入排序(ISO 9660 要求目录记录按名字升序)。
fn sort_names(names : Array[String]) -> Array[String] {
let out : Array[String] = []
for name in names {
let mut at = out.length()
for i = 0; i < out.length(); i = i + 1 {
if name < out[i] {
at = i
break
}
}
out.insert(at, name)
}
out
}
///|
pub fn IsoBuilder::add_directory(self : IsoBuilder, path : String) -> Unit {
let normalized = normalize_path(path)
if index_of(self.dirs, normalized) < 0 {
// 先补齐所有父目录
let parent = parent_of(normalized)
if parent != normalized && index_of(self.dirs, parent) < 0 {
self.add_directory(parent)
}
self.dirs.push(normalized)
}
}
///|
/// 添加一个 Rock Ridge 符号链接(目标按原样写进 SL 条目)。
pub fn IsoBuilder::add_symlink(
self : IsoBuilder,
path : String,
target : String,
) -> Unit {
let normalized = normalize_path(path)
let parent = parent_of(normalized)
if index_of(self.dirs, parent) < 0 {
self.add_directory(parent)
}
self.symlinks.push((normalized, target))
}
///|
pub fn IsoBuilder::add_file(
self : IsoBuilder,
path : String,
data : Bytes,
) -> Unit {
let normalized = normalize_path(path)
let parent = parent_of(normalized)
if index_of(self.dirs, parent) < 0 {
self.add_directory(parent)
}
self.files.push((normalized, data))
}
///|
/// 生成完整的 ISO 9660 映像字节。
pub fn IsoBuilder::build(self : IsoBuilder) -> Bytes raise IsoError {
let sector = SECTOR_SIZE
let dirs : Array[OutDir] = [
OutDir::{
name: "",
iso_name: "",
parent: -1,
lba: 0,
size: 0,
data: Bytes::new(0),
},
]
let paths : Array[String] = ["/"]
let ordered = sort_names(self.dirs)
for path in ordered {
if path == "/" {
continue
}
let parent = index_of(paths, parent_of(path))
if parent < 0 {
raise BadDescriptor(0)
}
paths.push(path)
dirs.push(OutDir::{
name: base_of(path),
iso_name: "",
parent,
lba: 0,
size: 0,
data: Bytes::new(0),
})
}
let files : Array[OutFile] = []
for entry in self.files {
let (path, data) = entry
let directory = index_of(paths, parent_of(path))
if directory < 0 {
raise BadDescriptor(0)
}
files.push(OutFile::{
path,
name: base_of(path),
iso_name: "",
dir: directory,
lba: 0,
size: data.length(),
data,
symlink: None,
})
}
// 符号链接在 ISO 树里也是一条"文件"记录,内容长度 0
for entry in self.symlinks {
let (path, target) = entry
let directory = index_of(paths, parent_of(path))
if directory < 0 {
raise BadDescriptor(0)
}
files.push(OutFile::{
path,
name: base_of(path),
iso_name: "",
dir: directory,
lba: 0,
size: 0,
data: Bytes::new(0),
symlink: Some(target),
})
}
// 每个目录内部各自去重地分配 ISO 9660 level 1 名字
for d = 0; d < dirs.length(); d = d + 1 {
let used : Array[String] = []
for i = 0; i < dirs.length(); i = i + 1 {
if dirs[i].parent == d {
dirs[i].iso_name = iso_name(dirs[i].name, true, used, self.name_level)
}
}
for i = 0; i < files.length(); i = i + 1 {
if files[i].dir == d {
files[i].iso_name = iso_name(
files[i].name,
false,
used,
self.name_level,
)
}
}
}
// 先按占位 extent 生成一遍,得到每个目录数据块的大小
for d = 0; d < dirs.length(); d = d + 1 {
let records = build_dir_records(
d,
dirs,
files,
false,
false,
[],
self.rr_version,
)
dirs[d].data = pack_records(records, sector)
dirs[d].size = dirs[d].data.length()
}
// 布局:卷描述符 -> 路径表 -> ISO 目录 -> Joliet 目录 -> 文件内容
// 描述符顺序:PVD(16) -> 启动记录(17,El Torito 要求紧跟 PVD) -> 补充卷 -> 结束符
let mut descriptor_lba = 17
let has_boot = self.boot_path is Some(_) || self.efi_path is Some(_)
let boot_record_lba = if has_boot {
let lba = descriptor_lba
descriptor_lba += 1
lba
} else {
-1
}
let svd_lba = if self.joliet {
let lba = descriptor_lba
descriptor_lba += 1
lba
} else {
-1
}
let terminator_lba = descriptor_lba
let mut cursor = terminator_lba + 1
let iso_l_pt = cursor
cursor += 1
let iso_m_pt = cursor
cursor += 1
let mut joliet_l_pt = 0
let mut joliet_m_pt = 0
if self.joliet {
joliet_l_pt = cursor
cursor += 1
joliet_m_pt = cursor
cursor += 1
}
for d = 0; d < dirs.length(); d = d + 1 {
dirs[d].lba = cursor
cursor += ceil_sectors(dirs[d].size, sector)
}
let joliet_dirs : Array[OutDir] = []
if self.joliet {
for d = 0; d < dirs.length(); d = d + 1 {
let records = build_dir_records(d, dirs, files, true, false, [], 109)
let data = pack_records(records, sector)
joliet_dirs.push(OutDir::{
name: dirs[d].name,
iso_name: dirs[d].iso_name,
parent: dirs[d].parent,
lba: cursor,
size: data.length(),
data,
})
cursor += ceil_sectors(data.length(), sector)
}
}
for f = 0; f < files.length(); f = f + 1 {
files[f].lba = cursor
cursor += ceil_sectors(files[f].size, sector)
}
let mut boot_catalog_lba = -1
if has_boot {
boot_catalog_lba = cursor
cursor += 1
}
let total_sectors = cursor
// 用真实 extent 重新生成目录数据
for d = 0; d < dirs.length(); d = d + 1 {
dirs[d].data = pack_records(
build_dir_records(
d,
dirs,
files,
false,
self.rock_ridge,
[],
self.rr_version,
),
sector,
)
}
if self.joliet {
for d = 0; d < joliet_dirs.length(); d = d + 1 {
joliet_dirs[d].data = pack_records(
build_dir_records(d, dirs, files, true, false, joliet_dirs, 109),
sector,
)
}
}
let image = FixedArray::make(total_sectors * sector, b'\x00')
let write_at = fn(lba : Int32, data : Bytes) -> Unit {
for i = 0; i < data.length(); i = i + 1 {
image[lba * sector + i] = data[i]
}
}
for d = 0; d < dirs.length(); d = d + 1 {
write_at(dirs[d].lba, dirs[d].data)
}
if self.joliet {
for d = 0; d < joliet_dirs.length(); d = d + 1 {
write_at(joliet_dirs[d].lba, joliet_dirs[d].data)
}
}
for f = 0; f < files.length(); f = f + 1 {
write_at(files[f].lba, files[f].data)
}
if has_boot {
let find_lba = fn(path : String) -> Int32 raise IsoError {
let mut found = -1
for f = 0; f < files.length(); f = f + 1 {
if files[f].path == path {
found = files[f].lba
}
}
if found < 0 {
raise NotFound(path)
}
found
}
let bios = match self.boot_path {
Some(path) => Some((find_lba(path), self.boot_load_sectors))
None => None
}
let efi = match self.efi_path {
Some(path) => Some((find_lba(path), self.boot_load_sectors))
None => None
}
write_at(
boot_catalog_lba,
boot_catalog_sector(bios, efi, self.boot_platform),
)
write_at(boot_record_lba, boot_record_sector(boot_catalog_lba))
}
// 路径表
let iso_table = path_table_bytes(dirs, false, false)
write_at(iso_l_pt, iso_table)
write_at(iso_m_pt, path_table_bytes(dirs, true, false))
if self.joliet {
write_at(joliet_l_pt, path_table_bytes(joliet_dirs, false, true))
write_at(joliet_m_pt, path_table_bytes(joliet_dirs, true, true))
}
// 卷描述符
write_at(
16,
make_pvd(
self,
dirs[0],
iso_table.length(),
iso_l_pt,
iso_m_pt,
total_sectors,
),
)
if self.joliet {
write_at(
svd_lba,
make_svd(
self,
joliet_dirs[0],
path_table_bytes(joliet_dirs, false, true).length(),
joliet_l_pt,
joliet_m_pt,
),
)
}
write_at(terminator_lba, terminator_sector())
Bytes::from_array(image[:])
}
///|
/// 生成某个目录的记录集合。
///
/// `joliet_tree` 为真时用 Joliet 的目录 extent 与 UCS-2 名字;
/// 否则用 ISO 树,并在 rr 为真时附上 Rock Ridge 条目。
fn build_dir_records(
d : Int32,
dirs : Array[OutDir],
files : Array[OutFile],
joliet_tree : Bool,
rr : Bool,
joliet_dirs : Array[OutDir],
rr_version : Int32,
) -> Array[Bytes] {
let extent_of = fn(index : Int32) -> Int32 {
if joliet_tree && index < joliet_dirs.length() {
joliet_dirs[index].lba
} else {
dirs[index].lba
}
}
let size_of = fn(index : Int32) -> Int32 {
if joliet_tree && index < joliet_dirs.length() {
joliet_dirs[index].size
} else {
dirs[index].size
}
}
let self_extent = extent_of(d)
let self_size = size_of(d)
let parent = dirs[d].parent
let parent_index = if parent < 0 { d } else { parent }
let records : Array[Bytes] = []
// "."(根目录额外带 SP + ER)
let self_susp = if rr && d == 0 {
rr_sp_er(rr_version)
} else {
Bytes::new(0)
}
records.push(
make_dir_record(
self_extent,
self_size,
2,
Bytes::from_array([b'\x00'][:]),
self_susp,
),
)
records.push(
make_dir_record(
extent_of(parent_index),
size_of(parent_index),
2,
Bytes::from_array([b'\x01'][:]),
Bytes::new(0),
),
)
// 子目录 + 文件,按名字升序
let keys : Array[String] = []
let kinds : Array[Int32] = []
let indexes : Array[Int32] = []
for i = 0; i < dirs.length(); i = i + 1 {
if dirs[i].parent == d && i != 0 {
keys.push(if joliet_tree { dirs[i].name } else { dirs[i].iso_name })
kinds.push(0)
indexes.push(i)
}
}
for i = 0; i < files.length(); i = i + 1 {
if files[i].dir == d {
keys.push(if joliet_tree { files[i].name } else { files[i].iso_name })
kinds.push(1)
indexes.push(i)
}
}
// 简单插入排序,保持记录升序
let order : Array[Int32] = []
for i = 0; i < keys.length(); i = i + 1 {
let mut at = order.length()
for j = 0; j < order.length(); j = j + 1 {
if keys[i] < keys[order[j]] {
at = j
break
}
}
order.insert(at, i)
}
for k in order {
let index = indexes[k]
if kinds[k] == 0 {
let name = if joliet_tree {
ucs2be_bytes(dirs[index].name)
} else {
ascii_bytes(dirs[index].iso_name)
}
let susp = if rr {
rr_susp(dirs[index].name, 0o040755, None, rr_version)
} else {
Bytes::new(0)
}
records.push(
make_dir_record(extent_of(index), size_of(index), 2, name, susp),
)
} else {
let name = if joliet_tree {
ucs2be_bytes(files[index].name + ";1")
} else {
ascii_bytes(files[index].iso_name)
}
let flags = 0
let mode = if files[index].symlink is Some(_) {
0o120777
} else {
0o100644
}
let susp = if rr {
rr_susp(files[index].name, mode, files[index].symlink, rr_version)
} else {
Bytes::new(0)
}
records.push(
make_dir_record(files[index].lba, files[index].size, flags, name, susp),
)
}
}
records
}
///|
/// 路径表要求的顺序:根在前,其后按「父目录号、名字」升序(这里用 BFS 实现)。
fn path_table_order(dirs : Array[OutDir]) -> Array[Int32] {
let order : Array[Int32] = [0]
let mut at = 0
while at < order.length() {
let current = order[at]
at += 1
let children : Array[Int32] = []
for i = 1; i < dirs.length(); i = i + 1 {
if dirs[i].parent == current && !order.contains(i) {
children.push(i)
}
}
// 按名字排序后追加
let names : Array[String] = []
for c in children {
names.push(dirs[c].iso_name)
}
let sorted = sort_names(names)
for name in sorted {
for c in children {
if dirs[c].iso_name == name && !order.contains(c) {
order.push(c)
break
}
}
}
}
order
}
///|
/// 生成路径表(`big_endian` 为真时输出 M 表)。
fn path_table_bytes(
dirs : Array[OutDir],
big_endian : Bool,
joliet : Bool,
) -> Bytes {
let order = path_table_order(dirs)
let parts : Array[Bytes] = []
for position = 0; position < order.length(); position = position + 1 {
let index = order[position]
let dir = dirs[index]
let is_root = index == 0
let name = if is_root {
Bytes::from_array([b'\x00'][:])
} else if joliet {
// Joliet 路径表同样使用 UCS-2 名字
ucs2be_bytes(dir.name)
} else {
ascii_bytes(dir.iso_name)
}
let extent = both32(dir.lba)
let parent_number = if is_root {
1
} else {
let p = dir.parent
let mut number = 1
for i = 0; i < order.length(); i = i + 1 {
if order[i] == p {
number = i + 1
break
}
}
number
}
let parent = both16(parent_number)
let record = concat_bytes([
Bytes::from_array([name.length().to_byte(), b'\x00'][:]),
if big_endian {
Bytes::from_array(
[extent[4], extent[5], extent[6], extent[7], parent[2], parent[3]][:],
)
} else {
Bytes::from_array(
[extent[0], extent[1], extent[2], extent[3], parent[0], parent[1]][:],
)
},
name,
])
let padded = if record.length() % 2 == 0 {
record
} else {
concat_bytes([record, Bytes::from_array([b'\x00'][:])])
}
parts.push(padded)
}
concat_bytes(parts)
}
///|
fn descriptor_base(
kind : Int32,
volume_id : String,
total_sectors : Int32,
table_size : Int32,
l_table : Int32,
m_table : Int32,
root : OutDir,
rr : Bool,
joliet : Bool,
rr_version : Int32,
) -> Bytes {
let sector = FixedArray::make(SECTOR_SIZE, b'\x00')
sector[0] = kind.to_byte()
let magic = ascii_bytes("CD001")
for i = 0; i < 5; i = i + 1 {
sector[1 + i] = magic[i]
}
sector[6] = b'\x01'
for i = 8; i < 40; i = i + 1 {
sector[i] = b' '
}
let id_bytes = if joliet {
ucs2be_bytes(volume_id)
} else {
ascii_bytes(volume_id)
}
for i = 0; i < id_bytes.length() && i < 32; i = i + 1 {
sector[40 + i] = id_bytes[i]
}
for i = 40 + id_bytes.length(); i < 72; i = i + 1 {
sector[i] = b' '
}
let space = both32(total_sectors)
for i = 0; i < 8; i = i + 1 {
sector[80 + i] = space[i]
}
let set_size = both16(1)
for i = 0; i < 4; i = i + 1 {
sector[120 + i] = set_size[i]
}
for i = 0; i < 4; i = i + 1 {
sector[124 + i] = set_size[i]
}
let block = both16(SECTOR_SIZE)
for i = 0; i < 4; i = i + 1 {
sector[128 + i] = block[i]
}
let size = both32(table_size)
for i = 0; i < 8; i = i + 1 {
sector[132 + i] = size[i]
}
sector[140] = (l_table & 0xFF).to_byte()
sector[141] = ((l_table >> 8) & 0xFF).to_byte()
sector[142] = ((l_table >> 16) & 0xFF).to_byte()
sector[143] = ((l_table >> 24) & 0xFF).to_byte()
sector[148] = ((m_table >> 24) & 0xFF).to_byte()
sector[149] = ((m_table >> 16) & 0xFF).to_byte()
sector[150] = ((m_table >> 8) & 0xFF).to_byte()
sector[151] = (m_table & 0xFF).to_byte()
if joliet {
// Joliet level 3 的转义序列 %/E
sector[88] = b'%'
sector[89] = b'/'
sector[90] = b'E'
}
let root_record = make_dir_record(
root.lba,
root.size,
2,
Bytes::from_array([b'\x00'][:]),
if rr {
rr_sp_er(rr_version)
} else {
Bytes::new(0)
},
)
for i = 0; i < root_record.length(); i = i + 1 {
sector[156 + i] = root_record[i]
}
for i = 190; i < 318; i = i + 1 {
sector[i] = b' '
}
for i = 318; i < 446; i = i + 1 {
sector[i] = b' '
}
for i = 446; i < 574; i = i + 1 {
sector[i] = b' '
}
for i = 574; i < 702; i = i + 1 {
sector[i] = b' '
}
for start in [702, 739, 776] {
for i = start; i < start + 37; i = i + 1 {
sector[i] = b' '
}
}
for start in [813, 830, 847, 864] {
for i = start; i < start + 16; i = i + 1 {
sector[i] = b'0'
}
}
sector[881] = b'\x01'
Bytes::from_array(sector[:])
}
///|
fn make_pvd(
builder : IsoBuilder,
root : OutDir,
table_size : Int32,
l_table : Int32,
m_table : Int32,
total_sectors : Int32,
) -> Bytes {
descriptor_base(
1,
builder.volume_id,
total_sectors,
table_size,
l_table,
m_table,
root,
builder.rock_ridge,
false,
builder.rr_version,
)
}
///|
fn make_svd(
builder : IsoBuilder,
root : OutDir,
table_size : Int32,
l_table : Int32,
m_table : Int32,
) -> Bytes {
descriptor_base(
2,
builder.volume_id,
0,
table_size,
l_table,
m_table,
root,
false,
true,
109,
)
}
///|
fn terminator_sector() -> Bytes {
let sector = FixedArray::make(SECTOR_SIZE, b'\x00')
sector[0] = b'\xFF'
let magic = ascii_bytes("CD001")
for i = 0; i < 5; i = i + 1 {
sector[1 + i] = magic[i]
}
sector[6] = b'\x01'
Bytes::from_array(sector[:])
}
///|
/// El Torito 启动记录卷描述符(类型 0),指向启动目录所在逻辑块。
fn boot_record_sector(catalog_lba : Int32) -> Bytes {
let sector = FixedArray::make(SECTOR_SIZE, b'\x00')
sector[0] = b'\x00'
let magic = ascii_bytes("CD001")
for i = 0; i < 5; i = i + 1 {
sector[1 + i] = magic[i]
}
sector[6] = b'\x01'
let id = ascii_bytes("EL TORITO SPECIFICATION")
for i = 0; i < id.length(); i = i + 1 {
sector[7 + i] = id[i]
}
sector[71] = (catalog_lba & 0xFF).to_byte()
sector[72] = ((catalog_lba >> 8) & 0xFF).to_byte()
sector[73] = ((catalog_lba >> 16) & 0xFF).to_byte()
sector[74] = ((catalog_lba >> 24) & 0xFF).to_byte()
Bytes::from_array(sector[:])
}
///|
/// El Torito 启动目录:校验项(含校验和)+ 默认启动项。
fn boot_catalog_sector(
bios : (Int32, Int32)?,
efi : (Int32, Int32)?,
platform : Int32,
) -> Bytes {
let sector = FixedArray::make(SECTOR_SIZE, b'\x00')
sector[0] = b'\x01'
// 没有 BIOS 启动项时,校验项的平台号用 EFI(0xEF)
let header_platform = match bios {
Some(_) => platform
None => 0xEF
}
sector[1] = header_platform.to_byte()
let id = ascii_bytes("mooniso boot")
for i = 0; i < id.length(); i = i + 1 {
sector[4 + i] = id[i]
}
// 校验和:让校验项的前 16 个小端 16 位字之和为 0
// 0x55AA 这两个固定字节也参与求和,必须先写进去
sector[30] = b'\x55'
sector[31] = b'\xAA'
let mut sum = 0
for i = 0; i < 16; i = i + 1 {
let lo = sector[i * 2].to_uint().reinterpret_as_int()
let hi = sector[i * 2 + 1].to_uint().reinterpret_as_int()
sum = (sum + (lo | (hi << 8))) & 0xFFFF
}
let checksum = (0x10000 - sum) & 0xFFFF
sector[28] = (checksum & 0xFF).to_byte()
sector[29] = ((checksum >> 8) & 0xFF).to_byte()
// 默认启动项:可启动、无仿真、加载扇区数与加载地址
let default_entry = match bios {
Some(pair) => Some(pair)
None => efi
}
match default_entry {
Some(pair) => {
let (lba, sectors) = pair
sector[32] = b'\x88'
sector[33] = b'\x00'
sector[38] = (sectors & 0xFF).to_byte()
sector[39] = ((sectors >> 8) & 0xFF).to_byte()
sector[40] = (lba & 0xFF).to_byte()
sector[41] = ((lba >> 8) & 0xFF).to_byte()
sector[42] = ((lba >> 16) & 0xFF).to_byte()
sector[43] = ((lba >> 24) & 0xFF).to_byte()
}
None => ()
}
// UEFI:section header(0x90 = 最后一个,平台 0xEF)+ 一个条目
match (bios, efi) {
(Some(_), Some(pair)) => {
let (lba, sectors) = pair
let base = 64
sector[base] = b'\x90'
sector[base + 1] = b'\xEF'
sector[base + 2] = b'\x01'
sector[base + 3] = b'\x00'
let sid = ascii_bytes("EFI")
for i = 0; i < sid.length(); i = i + 1 {
sector[base + 4 + i] = sid[i]
}
let mut header_sum = 0
for i = 0; i < 16; i = i + 1 {
let lo = sector[base + i * 2].to_uint().reinterpret_as_int()
let hi = sector[base + i * 2 + 1].to_uint().reinterpret_as_int()
header_sum = (header_sum + (lo | (hi << 8))) & 0xFFFF
}
let header_checksum = (0x10000 - header_sum) & 0xFFFF
sector[base + 28] = (header_checksum & 0xFF).to_byte()
sector[base + 29] = ((header_checksum >> 8) & 0xFF).to_byte()
sector[base + 32] = b'\x88'
sector[base + 33] = b'\x00'
sector[base + 38] = (sectors & 0xFF).to_byte()
sector[base + 39] = ((sectors >> 8) & 0xFF).to_byte()
sector[base + 40] = (lba & 0xFF).to_byte()
sector[base + 41] = ((lba >> 8) & 0xFF).to_byte()
sector[base + 42] = ((lba >> 16) & 0xFF).to_byte()
sector[base + 43] = ((lba >> 24) & 0xFF).to_byte()
}
_ => ()
}
Bytes::from_array(sector[:])
}