(bytes: Uint8Array, inflate: UmdInflate)
| 130 | * use `fflate.unzlibSync`; on mobile use `pako.inflate`. |
| 131 | */ |
| 132 | export function parseUmd(bytes: Uint8Array, inflate: UmdInflate): UmdParsed { |
| 133 | checkMagic(bytes); |
| 134 | |
| 135 | const fileLen = bytes.length; |
| 136 | let offset = 4; |
| 137 | |
| 138 | let bookTitle = ""; |
| 139 | let author = ""; |
| 140 | let year = ""; |
| 141 | let month = ""; |
| 142 | let day = ""; |
| 143 | let bookType = ""; |
| 144 | let publisher = ""; |
| 145 | let retailer = ""; |
| 146 | |
| 147 | let contentLen = 0; |
| 148 | let idContentBlocks = 0; |
| 149 | let idTitleList = 0; |
| 150 | let idChapterList = 0; |
| 151 | let idCover = 0; |
| 152 | let hasCover = false; |
| 153 | |
| 154 | // data-block id list, in file order — content reassembly walks this |
| 155 | const dataOrder: number[] = []; |
| 156 | // data-block id → raw payload |
| 157 | const dataById = new Map<number, Uint8Array>(); |
| 158 | |
| 159 | while (offset + 5 <= fileLen) { |
| 160 | const blockType = bytes[offset]!; |
| 161 | |
| 162 | if (blockType === 0x23) { |
| 163 | // Functional block: '#' funcID ?? ?? funcLen content... |
| 164 | const funcID = bytes[offset + 1]!; |
| 165 | const funcLen = bytes[offset + 4]!; |
| 166 | if (funcLen < 5 || offset + funcLen > fileLen) break; |
| 167 | |
| 168 | const content = bytes.subarray(offset + 5, offset + funcLen); |
| 169 | |
| 170 | switch (funcID) { |
| 171 | case 0x01: { |
| 172 | // 1 = text, 2 = comic |
| 173 | if (content.length === 0 || content[0] !== 1) { |
| 174 | throw new Error("UMD: non-text variant not supported"); |
| 175 | } |
| 176 | break; |
| 177 | } |
| 178 | case 0x02: |
| 179 | bookTitle = decodeUtf16LE(content); |
| 180 | break; |
| 181 | case 0x03: |
| 182 | author = decodeUtf16LE(content); |
| 183 | break; |
| 184 | case 0x04: |
| 185 | year = decodeUtf16LE(content); |
| 186 | break; |
| 187 | case 0x05: |
| 188 | month = decodeUtf16LE(content); |
| 189 | break; |
no test coverage detected