#help_index "File/CD DVD"

class CCDVDUserData //Create DVD
{
    I64 loc, path_entry_num, short_dir_blks, long_dir_blks;
};

I64 DVDFileCreate2(CFile *out_file, CDirEntry *tmpde, CISODirEntry *tmpi,
                   CISODirEntry *tmpi2, I64 *_cur_blk, CDirEntry *parent, Bool write,
                   U8 *stage2_filename, I64 *_stage2_blk)
{
    CCDVDUserData   *tmpc;
    CDirEntry       *tmpde1, *tmpde2;
    CFile           *in_file;
    U8              *buf = MAlloc(DVD_BLK_SIZE), *ptr1, *ptr2;
    CISODirEntry    *dir_blk_buf = CAlloc(DVD_BLK_SIZE * 128),
                    *de = dir_blk_buf, *de1,
                    *dir_blk_buf2 = CAlloc(DVD_BLK_SIZE * 128),
                    *de2 = dir_blk_buf2, *de12;
    I64              i, n;

    tmpc = parent->user_data;

    de->len = sizeof(CISODirEntry) - 1;
    de->ext_attr_len = 0;
    FillU16Palindrome(&de->vol_seq_num, 1);
    Date2ISO(&de->date, tmpde->datetime);
    de->flags = ISO_ATTR_DIR;
    de->name_len = 1;
    de->name = 0;
    de->len += de->name_len;
    de(U8 *) += de->len;

    de->len = sizeof(CISODirEntry) - 1;
    de->ext_attr_len = 0;
    FillU32Palindrome(&de->loc, tmpc->loc);
    FillU32Palindrome(&de->size, tmpc->short_dir_blks * DVD_BLK_SIZE);
    FillU16Palindrome(&de->vol_seq_num, 1);
    Date2ISO(&de->date, parent->datetime);
    de->flags = ISO_ATTR_DIR;
    de->name_len = 1;
    de->name = 1;
    de->len += de->name_len;
    de(U8 *) += de->len;

    de2->len = sizeof(CISODirEntry) - 1;
    de2->ext_attr_len = 0;
    FillU16Palindrome(&de2->vol_seq_num, 1);
    Date2ISO(&de2->date, tmpde->datetime);
    de2->flags = ISO_ATTR_DIR;
    de2->name_len = 1;
    de2->name = 0;
    de2->len += de2->name_len;
    de2(U8 *) += de2->len;

    de2->len = sizeof(CISODirEntry) - 1;
    de2->ext_attr_len = 0;
    FillU32Palindrome(&de2->loc, tmpc->loc + tmpc->short_dir_blks);
    FillU32Palindrome(&de2->size, tmpc->long_dir_blks * DVD_BLK_SIZE);
    FillU16Palindrome(&de2->vol_seq_num, 1);
    Date2ISO(&de2->date, parent->datetime);
    de2->flags = ISO_ATTR_DIR;
    de2->name_len = 1;
    de2->name = 1;
    de2->len += de2->name_len;
    de2(U8 *) += de2->len;

    tmpde1 = tmpde->sub;
    while (tmpde1)
    {
        tmpde2 = tmpde1->next;
        if (!write)
            tmpde1->user_data = CAlloc(sizeof(CCDVDUserData));
        de1 = de;
        de12 = de2;
        if (tmpde1->attr & RS_ATTR_DIR)
        {
            n = DVDFileCreate2(out_file, tmpde1, de, de2, _cur_blk,
                    tmpde, write, stage2_filename, _stage2_blk);
            de(U8 *) += sizeof(CISODirEntry) - 1 + n;
            de2(U8 *) += sizeof(CISODirEntry) - 1 + n << 1;
        }
        else
        {
            tmpc = tmpde1->user_data;
            de->len = sizeof(CISODirEntry) - 1;
            de->ext_attr_len = 0;
            FillU32Palindrome(&de->loc, *_cur_blk);
            tmpc->loc = *_cur_blk;
            if (write)
            {
                if (stage2_filename && !StrCompare(tmpde1->full_name, stage2_filename))
                {
                    "$RED$!!! Boot Stage 2 !!!$FG$\n";
                    if (_stage2_blk)
                        *_stage2_blk = *_cur_blk;
                }
                "%X:%s\n", *_cur_blk, tmpde1->full_name;
            }
            FillU32Palindrome(&de->size, tmpde1->size);
            FillU16Palindrome(&de->vol_seq_num, 1);
            Date2ISO(&de->date, tmpde1->datetime);
            de->flags = 0;
            de->name_len = StrLen(tmpde1->name);
            StrCopy(&de->name, tmpde1->name);
            de->len = de->len + de->name_len;
            de(U8 *) += de->len;

            de2->len = sizeof(CISODirEntry) - 1;
            de2->ext_attr_len = 0;
            FillU32Palindrome(&de2->loc, *_cur_blk);
            FillU32Palindrome(&de2->size, tmpde1->size);
            FillU16Palindrome(&de2->vol_seq_num, 1);
            Date2ISO(&de2->date, tmpde1->datetime);
            de2->flags = 0;
            de2->name_len = StrLen(tmpde1->name) << 1;
            ptr1 = &de2->name;
            ptr2 = &tmpde1->name;
            for (i = 0; i < de2->name_len; i = i + 2)
            {
                ptr1++;
                *ptr1++ = *ptr2++;
            }
            de2->len += de2->name_len;
            de2(U8 *) += de2->len;

            in_file = FOpen(tmpde1->full_name, "r");
            for (i = 0; i < (FSize(in_file) + DVD_BLK_SIZE - 1) / DVD_BLK_SIZE; i++)
            {
                n = 4;
                if ((i + 1) << 2 > (FSize(in_file) + BLK_SIZE - 1) >> BLK_SIZE_BITS)
                {
                    n = (FSize(in_file) + BLK_SIZE - 1) >> BLK_SIZE_BITS & 3;
                    MemSet(buf, 0, DVD_BLK_SIZE);
                }
                if (write)
                {
                    FBlkRead(in_file, buf, i << 2, n);
                    FBlkWrite(out_file, buf, *_cur_blk << 2, n);
                }
                *_cur_blk += 1;
            }
            FClose(in_file);
        }
        if ((de1(U8 *) - dir_blk_buf(U8 *)) / DVD_BLK_SIZE !=
            (de(U8 *) - dir_blk_buf(U8 *)) / DVD_BLK_SIZE)
        {
            i = de1->len;
            MemCopy(buf, de1, i);
            MemSet(de1, 0, i);
            de = dir_blk_buf(U8 *) + (de(U8 *) - dir_blk_buf(U8 *)) / DVD_BLK_SIZE * DVD_BLK_SIZE;
            MemCopy(de, buf, i);
            de(U8 *) += i;
        }
        if ((de12(U8 *) - dir_blk_buf2(U8 *)) / DVD_BLK_SIZE !=
            (de2(U8 *) - dir_blk_buf2(U8 *)) / DVD_BLK_SIZE)
        {
            i = de12->len;
            MemCopy(buf, de12, i);
            MemSet(de12, 0, i);
            de2(U8 *) = dir_blk_buf2(U8 *) + (de2(U8 *) -
                dir_blk_buf2(U8 *)) / DVD_BLK_SIZE * DVD_BLK_SIZE;
            MemCopy(de2, buf, i);
            de2(U8 *) += i;
        }
        tmpde1 = tmpde2;
    }

    tmpc = tmpde->user_data;

    tmpi->len = sizeof(CISODirEntry) - 1;
    tmpi->ext_attr_len = 0;
    tmpi->flags = ISO_ATTR_DIR;
    if (!tmpde->name[0])
    {
        tmpi->name_len = 1;
        tmpi->name = 1;
    }
    else
    {
        tmpi->name_len = StrLen(tmpde->name);
        StrCopy(&tmpi->name, tmpde->name);
    }
    tmpi->len += tmpi->name_len;

    n = de(U8 *) + 1 - dir_blk_buf(U8 *);
    n = (n + DVD_BLK_SIZE - 1) / DVD_BLK_SIZE;
    FillU32Palindrome(&tmpi->size, n * DVD_BLK_SIZE);
    FillU32Palindrome(&tmpi->loc, *_cur_blk);
    tmpc->short_dir_blks = n;
    tmpc->loc = *_cur_blk;
    FillU32Palindrome(&dir_blk_buf->size, n * DVD_BLK_SIZE);
    FillU32Palindrome(&dir_blk_buf->loc, *_cur_blk);
    FillU16Palindrome(&tmpi->vol_seq_num, 1);
    Date2ISO(&tmpi->date, tmpde->datetime);
    if (write)
        "%X:%s\n", *_cur_blk, tmpde->full_name;
    if (write)
        FBlkWrite(out_file, dir_blk_buf, *_cur_blk << 2, n << 2);
    *_cur_blk += n;

    tmpi2->len = sizeof(CISODirEntry) - 1;
    tmpi2->ext_attr_len = 0;
    tmpi2->flags = ISO_ATTR_DIR;
    if (!tmpde->name[0])
    {
        tmpi2->name_len = 1;
        tmpi->name = 1;
    }
    else
    {
        tmpi2->name_len = StrLen(tmpde->name) << 1;
        ptr1 = &tmpi2->name;
        ptr2 = &tmpde->name;
        for (i = 0; i < tmpi2->name_len; i = i + 2)
        {
            ptr1++;
            *ptr1++ = *ptr2++;
        }
    }
    tmpi2->len += tmpi2->name_len;
    n = de2(U8 *) + 1 - dir_blk_buf2(U8 *);
    n = (n + DVD_BLK_SIZE - 1) / DVD_BLK_SIZE;
    FillU32Palindrome(&tmpi2->size, n * DVD_BLK_SIZE);
    FillU32Palindrome(&tmpi2->loc, *_cur_blk);
    tmpc->long_dir_blks = n;
    FillU32Palindrome(&dir_blk_buf2->size, n * DVD_BLK_SIZE);
    FillU32Palindrome(&dir_blk_buf2->loc, *_cur_blk);
    FillU16Palindrome(&tmpi2->vol_seq_num, 1);
    Date2ISO(&tmpi2->date, tmpde->datetime);
    if (write)
        "%X:%s\n", *_cur_blk, tmpde->full_name;
    if (write)
        FBlkWrite(out_file, dir_blk_buf2, *_cur_blk << 2, n << 2);
    *_cur_blk += n;

    Free(dir_blk_buf);
    Free(dir_blk_buf2);
    Free(buf);
    return tmpi->name_len;
}

I64 DVDTableLen(CDirEntry *tmpde, I64 *size1, I64 *size2, I64 cur_depth)
{
    //Returns depth
    CDirEntry *tmpde1 = tmpde->sub;
    I64 max_depth = cur_depth, i;
    while (tmpde1)
    {
        if (tmpde1->attr & RS_ATTR_DIR)
        {
            *size1 += sizeof(CISOPathEntry) - 2 + (StrLen(tmpde1->name) + 1) & -0x2;
            *size2 += sizeof(CISOPathEntry) - 2 + StrLen(tmpde1->name) << 1;
            i = DVDTableLen(tmpde1, size1, size2, cur_depth + 1);
            if (i > max_depth)
                max_depth = i;
        }
        tmpde1 = tmpde1->next;
    }
    return max_depth;
}

U0 DVDFillPathTable(CDirEntry *tmpde,
                    CISOPathEntry **_itabbuf, CISOPathEntry **_itabbuf2,
                    I64 parent_entry_num, Bool big_endian, I64 *first_free,
                    I64 cur_level, I64 output_level)
{
    U8              *ptr1, *ptr2;
    I64              i;
    CISOPathEntry   *tabbuf = *_itabbuf, *tabbuf2 = *_itabbuf2;
    CDirEntry       *tmpde1 = tmpde->sub, *tmpde2;
    CCDVDUserData   *tmpc;

    if (cur_level == output_level)
    {
        while (tmpde1)
        {
            if (tmpde1->attr & RS_ATTR_DIR)
            {
                tmpc = tmpde1->user_data;
                tmpc->path_entry_num = *first_free;
                tabbuf->name_len = StrLen(tmpde1->name);
                if (big_endian)
                {
                    tabbuf->blk = EndianU32(tmpc->loc);
                    tabbuf->parent_entry_num = EndianU16(parent_entry_num);
                }
                else
                {
                    tabbuf->blk = tmpc->loc;
                    tabbuf->parent_entry_num = parent_entry_num;
                }
                StrCopy(&tabbuf->name, tmpde1->name);

                tabbuf(U8 *) += sizeof(CISOPathEntry) - 2 + (StrLen(tmpde1->name) + 1) & -0x2;

                tabbuf2->name_len = StrLen(tmpde1->name) << 1;
                if (big_endian)
                {
                    tabbuf2->blk = EndianU32(tmpc->loc + tmpc->short_dir_blks);
                    tabbuf2->parent_entry_num = EndianU16(parent_entry_num);
                }
                else
                {
                    tabbuf2->blk = tmpc->loc + tmpc->short_dir_blks;
                    tabbuf2->parent_entry_num = parent_entry_num;
                }
                ptr1 = &tabbuf2->name;
                ptr2 = &tmpde1->name;
                for (i = 0; i < tabbuf2->name_len; i = i + 2)
                {
                    ptr1++;
                    *ptr1++ = *ptr2++;
                }
                tabbuf2(U8 *) += sizeof(CISOPathEntry) - 2 + StrLen(tmpde1->name) << 1;
                *first_free += 1;
            }
            tmpde1 = tmpde1->next;
        }
        *_itabbuf = tabbuf;
        *_itabbuf2 = tabbuf2;
    }
    tmpde1 = tmpde->sub;
    while (tmpde1)
    {
        tmpde2 = tmpde1->next;
        if (tmpde1->attr & RS_ATTR_DIR)
        {
            tmpc = tmpde1->user_data;
            DVDFillPathTable(tmpde1, _itabbuf, _itabbuf2, tmpc->path_entry_num,
                big_endian, first_free, cur_level + 1, output_level);
        }
        tmpde1 = tmpde2;
    }
}

public I64 ISO9660ISO(U8 *_filename = NULL, U8 *src_files_find_mask,
                      U8 *fu_flags = NULL, U8 *_stage2_filename = NULL)
{
    //See ::/Misc/DoDistro.ZC
    //Use "C:/Distro/*" if you want all files in the C:/Distro directory.
    //Default flags are "+r" recurse.
    CISOPriDesc     *iso_pri = CAlloc(DVD_BLK_SIZE),
                    *iso_boot = CAlloc(DVD_BLK_SIZE),
                    *iso_sup = CAlloc(DVD_BLK_SIZE),
                    *iso_term = CAlloc(DVD_BLK_SIZE);
    CDirEntry       *headdir = CAlloc(sizeof(CDirEntry));
    I64              i, j, stage2_blk = (20 << 2 + 1 << 2 + DVD_BOOT_LOADER_SIZE / BLK_SIZE) >> 2,
                     stage2_size, cur_blk = 0, tabsize, tabsize2, first_free, max_depth, fuf_flags = 0;
    U32             *d;
    CElTorito       *et = CAlloc(DVD_BLK_SIZE);
    U8              *filename, *stage2_filename,
                    *stage1_buf = CAlloc(DVD_BOOT_LOADER_SIZE),
                    *zero_buf = CAlloc(DVD_BLK_SIZE);
    CISOPathEntry   *tabbuf = NULL, *tabbuf2 = NULL, *itabbuf, *itabbuf2;
    CFile           *out_file = NULL;
    CISODirEntry    *tmpi;
    CCDVDUserData   *tmpc;

    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), "+r");
    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), fu_flags);

    if (!_filename)
        _filename = blkdev.default_iso_filename;
    filename = ExtDefault(_filename, "ISO");

    if (_stage2_filename)
        stage2_filename = FileNameAbs(_stage2_filename);
    else
        stage2_filename = NULL;

    headdir->attr = RS_ATTR_DIR;
    headdir->sub = FilesFind(src_files_find_mask, fuf_flags);
    headdir->datetime = Now;
    headdir->user_data = CAlloc(sizeof(CCDVDUserData));
    tmpc = headdir->user_data;
    tmpc->path_entry_num = 1;

    cur_blk = 20 << 2 >> 2;
    if (stage2_filename)    //preboot and bootloader
        cur_blk += 1 + DVD_BOOT_LOADER_SIZE / DVD_BLK_SIZE;
    DVDFileCreate2(out_file, headdir, &iso_pri->root_dir_record, &iso_sup->root_dir_record,
        &cur_blk, headdir, FALSE, stage2_filename, &stage2_blk);
    tabsize = sizeof(CISOPathEntry);
    tabsize2 = sizeof(CISOPathEntry);
    max_depth = DVDTableLen(headdir, &tabsize, &tabsize2, 1);
    FillU32Palindrome(&iso_pri->path_table_size, tabsize);
    FillU32Palindrome(&iso_sup->path_table_size, tabsize2);
    tabsize = (tabsize + DVD_BLK_SIZE - 1) / DVD_BLK_SIZE;
    cur_blk += tabsize << 1;
    tabsize2 = (tabsize2 + DVD_BLK_SIZE - 1) / DVD_BLK_SIZE;
    cur_blk += tabsize2 << 1;

    if (FileAttr(filename) & RS_ATTR_CONTIGUOUS)
        out_file = FOpen(filename, "wc", cur_blk << 2);
    else
        out_file = FOpen(filename, "w", cur_blk << 2);
    cur_blk = 0;
    if (!out_file)
        goto cf_done;

    while (cur_blk < 20 << 2 >> 2)
        FBlkWrite(out_file, zero_buf, cur_blk++ << 2, 4);

    iso_pri->type = ISOT_PRI_VOL_DESC;
    StrCopy(iso_pri->id, "CD001");
    iso_pri->version = 1;
    FillU16Palindrome(&iso_pri->vol_set_size, 1);
    FillU16Palindrome(&iso_pri->vol_seq_num, 1);
    FillU16Palindrome(&iso_pri->log_block_size, DVD_BLK_SIZE);
    iso_pri->file_structure_version = 1;

    iso_sup->type = ISOT_SUPPLEMENTARY_DESC;
    StrCopy(iso_sup->id, "CD001");
    iso_sup->version = 1;
    FillU16Palindrome(&iso_sup->vol_set_size, 1);
    FillU16Palindrome(&iso_sup->vol_seq_num, 1);
    FillU16Palindrome(&iso_sup->log_block_size, DVD_BLK_SIZE);
    iso_sup->file_structure_version = 1;

    iso_boot->type = ISOT_BOOT_RECORD;
    StrCopy(iso_boot->id, "CD001");
    iso_boot->version = 1;
    StrCopy(iso_boot(U8 *) + 7, "EL TORITO SPECIFICATION");

    cur_blk = 20 << 2 >> 2;

    if (stage2_filename)
    {
        d = iso_boot(U8 *) + 0x47;
        *d = cur_blk;
        et->w[0] = 1;
        StrCopy(&et->w[2], "ZealOS");
        et->w[15] = 0xAA55;
        j = 0;
        for (i = 0; i < 16; i++)    //Checksum
            j += et->w[i];
        et->w[14] = -j;
        et->bootable = 0x88;
        et->media_type = 0; //0=no emu 2=1.44meg 4=hard drive
        et->sect_count = 4; //5 seems like the limit, 4 is safer
        et->load_rba = cur_blk + 1;
        "%X: Pre Boot Blk\n", cur_blk;
        FBlkWrite(out_file, et, cur_blk++ << 2, 4);
        "%X: Boot Stage 1\n", cur_blk;
        cur_blk += DVD_BOOT_LOADER_SIZE / DVD_BLK_SIZE;
    }

    DVDFileCreate2(out_file, headdir, &iso_pri->root_dir_record, &iso_sup->root_dir_record,
        &cur_blk, headdir, TRUE, stage2_filename, &stage2_blk);

    tabbuf = CAlloc(tabsize * DVD_BLK_SIZE);
    iso_pri->type_l_path_table = cur_blk;
    tabbuf->name_len = 2;   //Fill-in root entry
    tmpi = &iso_pri->root_dir_record;
    tabbuf->blk = tmpi->loc.little;
    tabbuf->parent_entry_num = 1;
    tabbuf2 = CAlloc(tabsize2 * DVD_BLK_SIZE);
    iso_sup->type_l_path_table = cur_blk + tabsize;
    tabbuf2->name_len = 2;  //Fill-in root entry
    tmpi = &iso_sup->root_dir_record;
    tabbuf2->blk = tmpi->loc.little;
    tabbuf2->parent_entry_num = 1;
    itabbuf = tabbuf + 1;
    itabbuf2 = tabbuf2 + 1;
    first_free = 2;
    for (i = 1; i <= max_depth; i++)
        DVDFillPathTable(headdir, &itabbuf, &itabbuf2, 1, FALSE, &first_free, 1, i);
    "%X: Path Table 0\n", cur_blk;
    FBlkWrite(out_file, tabbuf, cur_blk << 2, tabsize << 2);
    cur_blk += tabsize;
    "%X: Path Table 1\n", cur_blk;
    FBlkWrite(out_file, tabbuf2, cur_blk << 2, tabsize2 << 2);
    cur_blk += tabsize2;

    MemSet(tabbuf, 0, tabsize * DVD_BLK_SIZE);
    iso_pri->type_m_path_table = EndianU32(cur_blk);
    tabbuf->name_len = 2;   //Fill-in root entry
    tmpi = &iso_pri->root_dir_record;
    tabbuf->blk = tmpi->loc.big;
    tabbuf->parent_entry_num = EndianU16(1);
    MemSet(tabbuf2, 0, tabsize2 * DVD_BLK_SIZE);
    iso_sup->type_m_path_table = EndianU32(cur_blk + tabsize);
    tabbuf2->name_len = 2;  //Fill-in root entry
    tmpi = &iso_sup->root_dir_record;
    tabbuf2->blk = tmpi->loc.big;
    tabbuf2->parent_entry_num = EndianU16(1);
    itabbuf = tabbuf + 1;
    itabbuf2 = tabbuf2 + 1;
    first_free = 2;
    for (i = 1; i <= max_depth; i++)
        DVDFillPathTable(headdir, &itabbuf, &itabbuf2, 1, TRUE, &first_free, 1, i);
    "%X: Path Table 2\n", cur_blk;
    FBlkWrite(out_file, tabbuf, cur_blk << 2, tabsize << 2);
    cur_blk += tabsize;
    "%X: Path Table 3\n", cur_blk;
    FBlkWrite(out_file, tabbuf2, cur_blk << 2, tabsize2 << 2);
    cur_blk += tabsize2;

    DirTreeDel2(headdir);
    FillU32Palindrome(&iso_pri->vol_space_size, cur_blk);
    FillU32Palindrome(&iso_sup->vol_space_size, cur_blk);
    FBlkWrite(out_file, iso_pri, 16 << 2, 4);

    iso_term->type = ISOT_TERMINATOR;
    StrCopy(iso_term->id, "CD001");
    iso_term->version = 1;
    if (stage2_filename)
    {
        FBlkWrite(out_file, iso_boot, 17 << 2, 4);
        FBlkWrite(out_file, iso_sup, 18 << 2, 4);
        FBlkWrite(out_file, iso_term, 19 << 2, 4);
        stage2_size = (Size(stage2_filename, "+s") + DVD_BLK_SIZE - 1) / DVD_BLK_SIZE;
        MemCopy(stage1_buf, BDVD_START, BDVD_END - BDVD_START);
        *(BDVD_BLK_COUNT - BDVD_START + stage1_buf)(U16 *) = stage2_size;
        *(BDVD_BLK_LO - BDVD_START + stage1_buf)(U32 *) = stage2_blk;
        "$RED$!!! Boot Stage 2 !!! %X-%X$FG$\n", stage2_blk, stage2_blk + stage2_size - 1;
        FBlkWrite(out_file, stage1_buf, 20 << 2 + 1 << 2, DVD_BOOT_LOADER_SIZE / BLK_SIZE);
    }
    else
    {
        FBlkWrite(out_file, iso_sup, 17 << 2, 4);
        FBlkWrite(out_file, iso_term, 18 << 2, 4);
    }

cf_done:
    FClose(out_file);
    Free(tabbuf);
    Free(tabbuf2);
    Free(stage2_filename);
    Free(filename);
    Free(zero_buf);
    Free(stage1_buf);
    Free(et);
    Free(iso_pri);
    Free(iso_boot);
    Free(iso_sup);
    Free(iso_term);
    return cur_blk;
}