hwpkit-dev 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -165,10 +165,11 @@ class BufWriter {
165
165
 
166
166
  function mkRec(tag: number, level: number, data: Uint8Array): Uint8Array {
167
167
  const sz = data.length;
168
- const enc = Math.min(sz, 0xfff);
169
- const hdr = (enc << 20) | ((level & 0x3ff) << 10) | (tag & 0x3ff);
168
+ const isLarge = sz >= 0xfff;
169
+ const enc = isLarge ? 0xfff : sz;
170
+ const hdr = (((enc & 0xfff) * 0x100000) | ((level & 0x3ff) << 10) | (tag & 0x3ff)) >>> 0;
170
171
  const w = new BufWriter().u32(hdr);
171
- if (enc >= 0xfff) w.u32(sz);
172
+ if (isLarge) w.u32(sz);
172
173
  w.bytes(data);
173
174
  return w.build();
174
175
  }
@@ -674,7 +675,9 @@ function mkPageDef(dims: PageDims): Uint8Array {
674
675
  .u32(Metric.ptToHwp(dims.mr))
675
676
  .u32(Metric.ptToHwp(dims.mt))
676
677
  .u32(Metric.ptToHwp(dims.mb))
677
- .zeros(12)
678
+ .u32(dims.headerPt ? Metric.ptToHwp(dims.headerPt) : 0)
679
+ .u32(dims.footerPt ? Metric.ptToHwp(dims.footerPt) : 0)
680
+ .u32(0) // gutter
678
681
  .u32(dims.orient === "landscape" ? 1 : 0)
679
682
  .build(); // 40 bytes
680
683
  }
@@ -1085,7 +1088,7 @@ function mkTableCtrl(
1085
1088
  ): Uint8Array {
1086
1089
  // 표 정렬 속성 플래그 (HWP 표 제어 문자)
1087
1090
  // offset 20-21: 속성 플래그 (align: left=0, center=1, right=2, justify=3)
1088
- const alignFlags = { left: 0, center: 1, right: 2, justify: 3 }[align] ?? 0;
1091
+ const alignFlags = { left: 0, center: 1, right: 2, justify: 3, distribute: 0, distribute_space: 0 }[align] ?? 0;
1089
1092
  return new BufWriter()
1090
1093
  .u32(CTRL_TABLE)
1091
1094
  .u32(0x082a2211)
@@ -1256,7 +1259,7 @@ function encodeGrid(
1256
1259
  const cellWidthHwp = Metric.ptToHwp(cwPt[c] ?? defColPt);
1257
1260
  for (const para of paras) {
1258
1261
  records.push(
1259
- ...encodePara(para as ParaNode, bank, lv + 2, idGen(), cellWidthHwp),
1262
+ ...encodePara(para as ParaNode, bank, lv + 1, idGen(), cellWidthHwp),
1260
1263
  );
1261
1264
  }
1262
1265
  }
@@ -1594,7 +1597,8 @@ function buildHwpOle2(
1594
1597
  section0Data: Uint8Array,
1595
1598
  binImages: BinImage[] = [],
1596
1599
  ): Uint8Array {
1597
- const SS = 512;
1600
+ const SS = 512; // 정규 섹터 크기
1601
+ const MSS = 64; // 미니 섹터 크기
1598
1602
  const ENDOFCHAIN = 0xfffffffe;
1599
1603
  const FREESECT = 0xffffffff;
1600
1604
  const FATSECT = 0xfffffffd;
@@ -1606,73 +1610,193 @@ function buildHwpOle2(
1606
1610
  );
1607
1611
  }
1608
1612
 
1609
- function padSector(d: Uint8Array): Uint8Array {
1610
- const n = Math.ceil(Math.max(d.length, 1) / SS) * SS;
1611
- if (d.length === n) return d;
1612
- const out = new Uint8Array(n);
1613
- out.set(d);
1614
- return out;
1613
+ // 스트림 정의 및 4096바이트 미만(isMini) 여부 분류
1614
+ interface OleStream {
1615
+ name: string;
1616
+ data: Uint8Array;
1617
+ dirIdx: number;
1618
+ isMini: boolean;
1619
+ startSec?: number; // 미니 스트림은 미니 FAT 내 시작 인덱스, 정규 스트림은 regular FAT 내 시작 섹터
1615
1620
  }
1616
1621
 
1617
- const fhPad = padSector(fileHeaderData);
1618
- const diPad = padSector(docInfoData);
1619
- const s0Pad = padSector(section0Data);
1620
- const imgPads = binImages.map((img) => padSector(img.data));
1622
+ const streams: OleStream[] = [];
1623
+ streams.push({
1624
+ name: "FileHeader",
1625
+ data: fileHeaderData,
1626
+ dirIdx: 1,
1627
+ isMini: fileHeaderData.length < 4096,
1628
+ });
1629
+ streams.push({
1630
+ name: "DocInfo",
1631
+ data: docInfoData,
1632
+ dirIdx: 2,
1633
+ isMini: docInfoData.length < 4096,
1634
+ });
1635
+ streams.push({
1636
+ name: "Section0",
1637
+ data: section0Data,
1638
+ dirIdx: 4,
1639
+ isMini: section0Data.length < 4096,
1640
+ });
1641
+
1642
+ for (let i = 0; i < binImages.length; i++) {
1643
+ const img = binImages[i];
1644
+ const name = `BIN${String(img.id).padStart(4, "0")}.${img.ext}`;
1645
+ streams.push({
1646
+ name,
1647
+ data: img.data,
1648
+ dirIdx: 6 + i,
1649
+ isMini: img.data.length < 4096,
1650
+ });
1651
+ }
1652
+
1653
+ // 미니 스트림 패킹 및 Mini FAT 체인 생성
1654
+ const miniStreams = streams.filter((s) => s.isMini);
1655
+ const miniSectorList: number[] = [];
1656
+ let miniStreamDataLength = 0;
1657
+
1658
+ for (const s of miniStreams) {
1659
+ const startSec = miniStreamDataLength / MSS;
1660
+ s.startSec = startSec;
1661
+
1662
+ const len = s.data.length;
1663
+ const numMiniSecs = Math.ceil(len / MSS);
1664
+
1665
+ for (let i = 0; i < numMiniSecs; i++) {
1666
+ const curSec = startSec + i;
1667
+ const nextSec = i === numMiniSecs - 1 ? ENDOFCHAIN : curSec + 1;
1621
1668
 
1622
- const fhN = fhPad.length / SS;
1623
- const diN = diPad.length / SS;
1624
- const s0N = s0Pad.length / SS;
1625
- const imgNs = imgPads.map((p) => p.length / SS);
1626
- const totalImgN = imgNs.reduce((s, n) => s + n, 0);
1669
+ while (miniSectorList.length <= curSec) {
1670
+ miniSectorList.push(FREESECT);
1671
+ }
1672
+ miniSectorList[curSec] = nextSec;
1673
+ }
1674
+
1675
+ miniStreamDataLength += numMiniSecs * MSS;
1676
+ }
1677
+
1678
+ // 미니 스트림 데이터 버퍼 구성
1679
+ const miniStreamData = new Uint8Array(miniStreamDataLength);
1680
+ let miniStreamOffset = 0;
1681
+ for (const s of miniStreams) {
1682
+ miniStreamData.set(s.data, miniStreamOffset);
1683
+ miniStreamOffset += Math.ceil(s.data.length / MSS) * MSS;
1684
+ }
1685
+
1686
+ // 정규 스트림 패딩 및 섹터 개수 계산
1687
+ const regularStreams = streams.filter((s) => !s.isMini);
1688
+ const regPads = regularStreams.map((s) => {
1689
+ const len = s.data.length;
1690
+ const n = Math.ceil(Math.max(len, 1) / SS) * SS;
1691
+ const out = new Uint8Array(n);
1692
+ out.set(s.data);
1693
+ return out;
1694
+ });
1695
+ const regNs = regPads.map((p) => p.length / SS);
1627
1696
 
1697
+ // 디렉토리 섹터 크기 결정 (엔트리 개수 비례)
1628
1698
  const numDirEntries = 5 + (binImages.length > 0 ? 1 + binImages.length : 0);
1629
- const dirN = Math.max(2, Math.ceil(numDirEntries / 4));
1699
+ const dirN = Math.max(1, Math.ceil((numDirEntries * 128) / SS));
1700
+
1701
+ // Mini FAT 섹터 크기 결정 (1 섹터당 128개 FAT 엔트리 수용)
1702
+ const miniFatN = Math.ceil(miniSectorList.length / 128);
1703
+
1704
+ // Mini Stream을 위한 정규 섹터 크기 결정
1705
+ const miniStreamN = Math.ceil(miniStreamData.length / SS);
1706
+
1707
+ // 정규 FAT 섹터 수 (fatN) 계산 루프
1708
+ const totalRegStreamN = regNs.reduce((a, b) => a + b, 0);
1709
+ const neededDataSec = dirN + miniFatN + miniStreamN + totalRegStreamN;
1630
1710
 
1631
1711
  let fatN = 1;
1632
1712
  for (let iter = 0; iter < 10; iter++) {
1633
- const total = fatN + dirN + fhN + diN + s0N + totalImgN;
1634
- const needed = Math.ceil(total / 128);
1635
- if (needed <= fatN) break;
1636
- fatN = needed;
1713
+ const totalSec = fatN + neededDataSec;
1714
+ const neededFat = Math.ceil(totalSec / 128);
1715
+ if (neededFat <= fatN) break;
1716
+ fatN = neededFat;
1637
1717
  }
1638
1718
 
1639
- const dir1Sec = fatN;
1640
- const fhSec = dir1Sec + dirN;
1641
- const diSec = fhSec + fhN;
1642
- const s0Sec = diSec + diN;
1719
+ const totalSec = fatN + neededDataSec;
1643
1720
 
1644
- const imgSecs: number[] = [];
1645
- let curSec = s0Sec + s0N;
1646
- for (const n of imgNs) {
1647
- imgSecs.push(curSec);
1648
- curSec += n;
1721
+ // 정규 섹터 영역의 시작 위치 할당
1722
+ const dirStartSec = fatN;
1723
+ const miniFatStartSec = dirStartSec + dirN;
1724
+ const miniStreamStartSec = miniFatStartSec + miniFatN;
1725
+
1726
+ let curSec = miniStreamStartSec + miniStreamN;
1727
+ for (let i = 0; i < regularStreams.length; i++) {
1728
+ regularStreams[i].startSec = curSec;
1729
+ curSec += regNs[i];
1649
1730
  }
1650
- const totalSec = curSec;
1651
1731
 
1732
+ // 정규 FAT 버퍼 구성
1652
1733
  const fatBuf = new Uint8Array(fatN * SS).fill(0xff);
1653
1734
  const setFat = (i: number, v: number) => {
1654
- fatBuf[i * 4] = v & 0xff;
1655
- fatBuf[i * 4 + 1] = (v >>> 8) & 0xff;
1656
- fatBuf[i * 4 + 2] = (v >>> 16) & 0xff;
1657
- fatBuf[i * 4 + 3] = (v >>> 24) & 0xff;
1735
+ const off = i * 4;
1736
+ fatBuf[off] = v & 0xff;
1737
+ fatBuf[off + 1] = (v >>> 8) & 0xff;
1738
+ fatBuf[off + 2] = (v >>> 16) & 0xff;
1739
+ fatBuf[off + 3] = (v >>> 24) & 0xff;
1740
+ };
1741
+
1742
+ // FAT 섹터 자신 표시
1743
+ for (let i = 0; i < fatN; i++) {
1744
+ setFat(i, FATSECT);
1745
+ }
1746
+
1747
+ // Directory 섹터 체인 연결
1748
+ for (let i = 0; i < dirN; i++) {
1749
+ setFat(
1750
+ dirStartSec + i,
1751
+ i + 1 < dirN ? dirStartSec + i + 1 : ENDOFCHAIN,
1752
+ );
1753
+ }
1754
+
1755
+ // Mini FAT 섹터 체인 연결
1756
+ if (miniFatN > 0) {
1757
+ for (let i = 0; i < miniFatN; i++) {
1758
+ setFat(
1759
+ miniFatStartSec + i,
1760
+ i + 1 < miniFatN ? miniFatStartSec + i + 1 : ENDOFCHAIN,
1761
+ );
1762
+ }
1763
+ }
1764
+
1765
+ // Mini Stream 섹터 체인 연결
1766
+ if (miniStreamN > 0) {
1767
+ for (let i = 0; i < miniStreamN; i++) {
1768
+ setFat(
1769
+ miniStreamStartSec + i,
1770
+ i + 1 < miniStreamN ? miniStreamStartSec + i + 1 : ENDOFCHAIN,
1771
+ );
1772
+ }
1773
+ }
1774
+
1775
+ // 정규 스트림 섹터 체인 연결
1776
+ for (let i = 0; i < regularStreams.length; i++) {
1777
+ const s = regularStreams[i];
1778
+ const n = regNs[i];
1779
+ const start = s.startSec!;
1780
+ for (let j = 0; j < n; j++) {
1781
+ setFat(start + j, j + 1 < n ? start + j + 1 : ENDOFCHAIN);
1782
+ }
1783
+ }
1784
+
1785
+ // Mini FAT 버퍼 생성
1786
+ const miniFatBuf = new Uint8Array(miniFatN * SS).fill(0xff);
1787
+ const setMiniFat = (i: number, v: number) => {
1788
+ const off = i * 4;
1789
+ miniFatBuf[off] = v & 0xff;
1790
+ miniFatBuf[off + 1] = (v >>> 8) & 0xff;
1791
+ miniFatBuf[off + 2] = (v >>> 16) & 0xff;
1792
+ miniFatBuf[off + 3] = (v >>> 24) & 0xff;
1658
1793
  };
1659
1794
 
1660
- for (let i = 0; i < fatN; i++) setFat(i, FATSECT);
1661
- for (let i = 0; i < dirN; i++)
1662
- setFat(dir1Sec + i, i + 1 < dirN ? dir1Sec + i + 1 : ENDOFCHAIN);
1663
- for (let i = 0; i < fhN; i++)
1664
- setFat(fhSec + i, i + 1 < fhN ? fhSec + i + 1 : ENDOFCHAIN);
1665
- for (let i = 0; i < diN; i++)
1666
- setFat(diSec + i, i + 1 < diN ? diSec + i + 1 : ENDOFCHAIN);
1667
- for (let i = 0; i < s0N; i++)
1668
- setFat(s0Sec + i, i + 1 < s0N ? s0Sec + i + 1 : ENDOFCHAIN);
1669
- for (let ii = 0; ii < imgNs.length; ii++) {
1670
- const start = imgSecs[ii];
1671
- const n = imgNs[ii];
1672
- for (let i = 0; i < n; i++)
1673
- setFat(start + i, i + 1 < n ? start + i + 1 : ENDOFCHAIN);
1795
+ for (let i = 0; i < miniSectorList.length; i++) {
1796
+ setMiniFat(i, miniSectorList[i]);
1674
1797
  }
1675
1798
 
1799
+ // 디렉토리 버퍼 생성
1676
1800
  const dirBuf = new Uint8Array(dirN * SS);
1677
1801
  const dv = new DataView(dirBuf.buffer);
1678
1802
 
@@ -1689,11 +1813,12 @@ function buildHwpOle2(
1689
1813
  const base = idx * 128;
1690
1814
  const nl = name.length;
1691
1815
  // OLE2: 이름은 UTF-16LE, (글자수+1)*2 바이트가 길이 필드에 기록됨
1692
- for (let i = 0; i < nl; i++)
1816
+ for (let i = 0; i < nl; i++) {
1693
1817
  dv.setUint16(base + i * 2, name.charCodeAt(i), true);
1818
+ }
1694
1819
  dv.setUint16(base + 64, (nl + 1) * 2, true);
1695
1820
  dirBuf[base + 66] = type;
1696
- dirBuf[base + 67] = 1; // DE_NODE
1821
+ dirBuf[base + 67] = 1; // DE_NODE (블랙 노드)
1697
1822
  dv.setInt32(base + 68, left, true);
1698
1823
  dv.setInt32(base + 72, right, true);
1699
1824
  dv.setInt32(base + 76, child, true);
@@ -1701,7 +1826,7 @@ function buildHwpOle2(
1701
1826
  dv.setUint32(base + 120, size >>> 0, true);
1702
1827
  }
1703
1828
 
1704
- // 초기값 -1 (NOSTREAM)
1829
+ // 모든 디렉토리 엔트리 -1 (NOSTREAM)으로 초기화
1705
1830
  for (let i = 0; i < dirN * 4; i++) {
1706
1831
  const base = i * 128;
1707
1832
  dv.setInt32(base + 68, -1, true);
@@ -1709,142 +1834,163 @@ function buildHwpOle2(
1709
1834
  dv.setInt32(base + 76, -1, true);
1710
1835
  }
1711
1836
 
1712
- /**
1713
- * 트리 구조 설계:
1714
- * 0: Root Entry (child -> 1)
1715
- * 1: FileHeader (left -> -1, right -> 2)
1716
- * 2: DocInfo (left -> -1, right -> 3)
1717
- * 3: BodyText (left -> -1, right -> 5, child -> 4)
1718
- * 4: Section0 (left -> -1, right -> -1)
1719
- * 5: BinData (left -> -1, right -> -1, child -> 6...)
1720
- */
1837
+ // 스트림 인덱스 맵 생성
1838
+ const streamMap = new Map<number, OleStream>();
1839
+ for (const s of streams) {
1840
+ streamMap.set(s.dirIdx, s);
1841
+ }
1842
+
1843
+ // Root Entry (0번): 미니 스트림의 루트 역할을 하며 미니 스트림 데이터를 가리킴
1844
+ writeDirEntry(
1845
+ 0,
1846
+ "Root Entry",
1847
+ 5,
1848
+ -1,
1849
+ -1,
1850
+ 3,
1851
+ miniStreamStartSec,
1852
+ miniStreamData.length,
1853
+ );
1721
1854
 
1855
+ // HWP Root Entry CLSID 설정
1856
+ const HWP_CLSID = [
1857
+ 0x20, 0xe9, 0xe3, 0xc0, 0x46, 0x35, 0xcf, 0x11, 0x8d, 0x81, 0x00, 0xaa,
1858
+ 0x00, 0x38, 0x9b, 0x71,
1859
+ ];
1860
+ for (let i = 0; i < 16; i++) {
1861
+ dirBuf[0 * 128 + 80 + i] = HWP_CLSID[i];
1862
+ }
1863
+
1864
+ // FileHeader (1번)
1865
+ const fhStream = streamMap.get(1)!;
1866
+ writeDirEntry(
1867
+ 1,
1868
+ "FileHeader",
1869
+ 2,
1870
+ -1,
1871
+ -1,
1872
+ -1,
1873
+ fhStream.startSec!,
1874
+ fhStream.data.length,
1875
+ );
1876
+
1877
+ // DocInfo (2번) - 이미지가 있는 경우 BinData(5번)를 left로 지정
1878
+ const diStream = streamMap.get(2)!;
1879
+ const docInfoLeft = binImages.length > 0 ? 5 : -1;
1880
+ writeDirEntry(
1881
+ 2,
1882
+ "DocInfo",
1883
+ 2,
1884
+ docInfoLeft,
1885
+ -1,
1886
+ -1,
1887
+ diStream.startSec!,
1888
+ diStream.data.length,
1889
+ );
1890
+
1891
+ // BodyText (3번) - Root Entry 아래의 검색 허브 역할 (left: DocInfo(2), right: FileHeader(1), child: Section0(4))
1892
+ writeDirEntry(3, "BodyText", 1, 2, 1, 4, ENDOFCHAIN, 0);
1893
+
1894
+ // Section0 (4번)
1895
+ const s0Stream = streamMap.get(4)!;
1896
+ writeDirEntry(
1897
+ 4,
1898
+ "Section0",
1899
+ 2,
1900
+ -1,
1901
+ -1,
1902
+ -1,
1903
+ s0Stream.startSec!,
1904
+ s0Stream.data.length,
1905
+ );
1906
+
1907
+ // BinData (5번)
1722
1908
  if (binImages.length > 0) {
1723
- writeDirEntry(0, "Root Entry", 5, -1, -1, 1, ENDOFCHAIN, 0);
1724
- writeDirEntry(1, "FileHeader", 2, -1, 2, -1, fhSec, fileHeaderData.length);
1725
- writeDirEntry(2, "DocInfo", 2, -1, 3, -1, diSec, docInfoData.length);
1726
- writeDirEntry(3, "BodyText", 1, -1, 5, 4, ENDOFCHAIN, 0);
1727
- writeDirEntry(4, "Section0", 2, -1, -1, -1, s0Sec, section0Data.length);
1728
1909
  writeDirEntry(5, "BinData", 1, -1, -1, 6, ENDOFCHAIN, 0);
1729
- for (let ii = 0; ii < binImages.length; ii++) {
1730
- const img = binImages[ii];
1731
- const streamName = `BIN${String(img.id).padStart(4, "0")}.${img.ext}`;
1732
- const sibling = ii + 1 < binImages.length ? 7 + ii : -1;
1910
+
1911
+ for (let i = 0; i < binImages.length; i++) {
1912
+ const imgStream = streamMap.get(6 + i)!;
1913
+ const sibling = i + 1 < binImages.length ? 7 + i : -1;
1733
1914
  writeDirEntry(
1734
- 6 + ii,
1735
- streamName,
1915
+ 6 + i,
1916
+ imgStream.name,
1736
1917
  2,
1737
1918
  -1,
1738
1919
  sibling,
1739
1920
  -1,
1740
- imgSecs[ii],
1741
- img.data.length,
1921
+ imgStream.startSec!,
1922
+ imgStream.data.length,
1742
1923
  );
1743
1924
  }
1744
- } else {
1745
- writeDirEntry(0, "Root Entry", 5, -1, -1, 1, ENDOFCHAIN, 0);
1746
- writeDirEntry(1, "FileHeader", 2, -1, 2, -1, fhSec, fileHeaderData.length);
1747
- writeDirEntry(2, "DocInfo", 2, -1, 3, -1, diSec, docInfoData.length);
1748
- writeDirEntry(3, "BodyText", 1, -1, -1, 4, ENDOFCHAIN, 0);
1749
- writeDirEntry(4, "Section0", 2, -1, -1, -1, s0Sec, section0Data.length);
1750
1925
  }
1751
1926
 
1752
- // HWP Root Entry CLSID
1753
- const HWP_CLSID = [
1754
- 0x20, 0xe9, 0xe3, 0xc0, 0x46, 0x35, 0xcf, 0x11, 0x8d, 0x81, 0x00, 0xaa,
1755
- 0x00, 0x38, 0x9b, 0x71,
1756
- ];
1757
- for (let i = 0; i < 16; i++) dirBuf[80 + i] = HWP_CLSID[i];
1758
-
1927
+ // 헤더 생성 (512바이트)
1759
1928
  const hdr = new Uint8Array(SS);
1760
1929
  const hdv = new DataView(hdr.buffer);
1761
1930
 
1762
- // OLE2/CFB 헤더 구조:
1763
- // 0-7: Magic number (D0 CF 11 E0 A1 B1 1A E1)
1764
- // 8-23: CLSID (16 bytes)
1765
- // 24-25: Minor version (0x003E = 62)
1766
- // 26-27: Major version (0x0003 = 3)
1767
- // 28-29: Byte order (0x00FE = Little-Endian)
1768
- // 30-31: Sector size exponent (0x0009 = 2^9 = 512)
1769
- // 32-33: Mini sector size exponent (0x0006 = 2^6 = 64)
1770
- // 34-39: Reserved (6 bytes)
1771
- // 40-43: Number of FAT sectors
1772
- // 44-47: Directory start sector location
1773
- // 48-51: Transaction signature number
1774
- // 52-55: Mini stream cutoff size (0x1000 = 4096)
1775
- // 56-59: Mini FAT start sector location
1776
- // 60-63: Number of mini FAT sectors
1777
- // 64-67: FAT start sector location
1778
- // 68-71: Number of backup FAT sectors
1779
- // 72-75: Backup FAT start sector location
1780
- // 76-511: Sector bitmap (109 sectors worth)
1781
-
1782
- // Magic number
1931
+ // OLE2 시그니처 (Magic)
1783
1932
  const MAGIC = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
1784
1933
  MAGIC.forEach((b, i) => {
1785
1934
  hdr[i] = b;
1786
1935
  });
1787
1936
 
1788
- // CLSID is already set in dirBuf[0][80:96] for Root Entry
1789
-
1790
- // Version
1791
1937
  hdv.setUint16(24, 0x003e, true); // Minor version
1792
1938
  hdv.setUint16(26, 0x0003, true); // Major version
1939
+ hdv.setUint16(28, 0xfffe, true); // Byte order (0xFFFE = 리틀 엔디안 BOM)
1940
+ hdv.setUint16(30, 0x0009, true); // Sector size exponent (2^9 = 512)
1941
+ hdv.setUint16(32, 0x0006, true); // Mini sector size exponent (2^6 = 64)
1942
+
1943
+ hdv.setUint32(40, 0, true); // Number of Directory Sectors (Version 3에서는 0으로 채움)
1944
+ hdv.setUint32(44, fatN, true); // Number of FAT Sectors (0x002C)
1945
+ hdv.setUint32(48, dirStartSec, true); // Starting Sector of Directory Stream (0x0030)
1946
+ hdv.setUint32(52, 0, true); // Transaction Signature Number (0x0034)
1947
+ hdv.setUint32(56, 0x1000, true); // Mini Stream Cutoff Size (0x0038)
1948
+ hdv.setUint32(60, miniFatN > 0 ? miniFatStartSec : ENDOFCHAIN, true); // Starting Sector of Mini FAT (0x003C)
1949
+ hdv.setUint32(64, miniFatN, true); // Number of Mini FAT Sectors (0x0040)
1950
+ hdv.setUint32(68, ENDOFCHAIN, true); // Starting Sector of DIFAT (0x0044)
1951
+ hdv.setUint32(72, 0, true); // Number of DIFAT Sectors (0x0048)
1952
+
1953
+ // Sector bitmap (DIFAT) 슬롯 채우기 (처음 109개 FAT 섹터 번호 지정)
1954
+ for (let i = 0; i < 109; i++) {
1955
+ hdv.setUint32(76 + i * 4, i < fatN ? i : FREESECT, true);
1956
+ }
1793
1957
 
1794
- // Byte order
1795
- hdv.setUint16(28, 0x00fe, true); // Little-Endian
1796
-
1797
- // Sector size exponent (2^9 = 512)
1798
- hdv.setUint16(30, 0x0009, true);
1799
-
1800
- // Mini sector size exponent (2^6 = 64)
1801
- hdv.setUint16(32, 0x0006, true);
1802
-
1803
- // Reserved (34-39, 6 bytes) - already zero
1804
-
1805
- // Number of FAT sectors
1806
- hdv.setUint32(40, fatN, true);
1807
-
1808
- // Directory start sector location
1809
- hdv.setUint32(44, dir1Sec, true);
1810
-
1811
- // Transaction signature number
1812
- hdv.setUint32(48, 0, true);
1813
-
1814
- // Mini stream cutoff size
1815
- hdv.setUint32(52, 0x1000, true);
1958
+ // 최종 바이트 어레이 조립 및 출력 생성
1959
+ const out = new Uint8Array(SS + totalSec * SS);
1960
+ let outOff = 0;
1816
1961
 
1817
- // Mini FAT start sector location
1818
- hdv.setUint32(56, ENDOFCHAIN, true);
1962
+ // 1. Header (Sector -1)
1963
+ out.set(hdr, outOff);
1964
+ outOff += SS;
1819
1965
 
1820
- // Number of mini FAT sectors
1821
- hdv.setUint32(60, 0, true);
1966
+ // 2. FAT sectors
1967
+ out.set(fatBuf, outOff);
1968
+ outOff += fatN * SS;
1822
1969
 
1823
- // FAT start sector location
1824
- hdv.setUint32(64, ENDOFCHAIN, true);
1970
+ // 3. Directory sectors
1971
+ out.set(dirBuf, outOff);
1972
+ outOff += dirN * SS;
1825
1973
 
1826
- // Number of backup FAT sectors
1827
- hdv.setUint32(68, 0, true);
1974
+ // 4. Mini FAT sectors
1975
+ if (miniFatN > 0) {
1976
+ out.set(miniFatBuf, outOff);
1977
+ outOff += miniFatN * SS;
1978
+ }
1828
1979
 
1829
- // Backup FAT start sector location
1830
- hdv.setUint32(72, 0, true);
1980
+ // 5. Mini Stream sectors (Root Entry Data)
1981
+ if (miniStreamN > 0) {
1982
+ const miniStreamPad = new Uint8Array(miniStreamN * SS);
1983
+ miniStreamPad.set(miniStreamData);
1984
+ out.set(miniStreamPad, outOff);
1985
+ outOff += miniStreamN * SS;
1986
+ }
1831
1987
 
1832
- // Sector bitmap (76-511)
1833
- for (let i = 0; i < 109; i++) {
1834
- hdv.setUint32(76 + i * 4, i < fatN ? i : FREESECT, true);
1988
+ // 6. Regular Streams sectors
1989
+ for (let i = 0; i < regularStreams.length; i++) {
1990
+ out.set(regPads[i], outOff);
1991
+ outOff += regNs[i] * SS;
1835
1992
  }
1836
1993
 
1837
- const out = new Uint8Array(SS + totalSec * SS);
1838
- out.set(hdr, 0);
1839
- for (let i = 0; i < fatN; i++)
1840
- out.set(fatBuf.subarray(i * SS, (i + 1) * SS), SS + i * SS);
1841
- for (let i = 0; i < dirN; i++)
1842
- out.set(dirBuf.subarray(i * SS, (i + 1) * SS), SS + (dir1Sec + i) * SS);
1843
- out.set(fhPad, SS + fhSec * SS);
1844
- out.set(diPad, SS + diSec * SS);
1845
- out.set(s0Pad, SS + s0Sec * SS);
1846
- for (let ii = 0; ii < imgPads.length; ii++)
1847
- out.set(imgPads[ii], SS + imgSecs[ii] * SS);
1848
1994
  return out;
1849
1995
  }
1850
1996