code wiki / _hdl_build / nx_manga_get.nx

nx_manga_get.nx source

↩ module page · 335 lines · 19197 B

1// nx_manga_get.nx -- R2a of the native manga DOWNLOAD ladder (census row URL): a generic gallery/chapter PAGE URL 2// -> ordered page images in a book dir + cbx.json, sovereign end-to-end (no python/gallery-dl/curl). Composes the 3// PROVEN organs: nx_https_fetch_follow (own TLS-1.3 GET) + nx_html_extract_imgs (extract every <img src>, RESOLVED 4// relative->absolute via nx_url_resolve) + nx_reader_library (land in the downloader's PRIVATE area). FORK-PER-IMAGE 5// so each image fetch's TLS mmaps die with the child (the researcher's proven per-fetch-process pattern) -> a 6// 200-page gallery never leaks. Writes reader/u_<ukey>/<slug>/page000.<ext>... + cbx.json (the reader/zoom viewer's 7// manifest), then rl_add_personal(ukey, slug, title). The daemon calls this (fork) on an "Add manga" POST; also a 8// CLI. LEGAL POSTURE: general capability; smoke on public-domain/legal pages; the operator points it where they like. 9// license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_x509_trust_store.nx" 12import "nx_trust_store_load_from_certdata.nx" 13import "nx_https_fetch_follow.nx" 14import "nx_img_harvest.nx" // nx_img_harvest = s-class multi-strategy extractor (img/lazy/srcset/JSON) [+ nx_html_extract_imgs/nx_url_resolve transitively] 15import "nx_manga_sites.nx" // R2b per-site adapters: na_detect + na_nhentai_api/build + na_mangadex_api/build 16import "nx_reader_library.nx" // rl_personal_slug_dir / rl_add_personal (+ zs_* transitively) 17import "nx_img_dims.nx" // nx_img_long_edge = REAL pixel size from the header (junk/icon filter) 18const MG_MAGIC_2048: i64 = 2048 19const MG_MAGIC_65536: i64 = 65536 20const MG_MAGIC_2166136261: i64 = 2166136261 21const MG_MAGIC_16777619: i64 = 16777619 22const MG_MAGIC_1048576: i64 = 1048576 23const MG_MAGIC_4096: i64 = 4096 24const MG_MAGIC_4095: i64 = 4095 25const MG_MAGIC_4194304: i64 = 4194304 26 27const MG_MAX_IMGS: i64 = 512 28const MG_IMG_CAP: i64 = 16777216 // 16MB per image cap (fetch buffer, per child) 29const MG_HTML_CAP: i64 = 8388608 // 8MB page HTML cap 30const MG_MIN_BYTES: i64 = 1024 // floor: below this = tracker/spacer/blank, never a real manga page 31const MG_MIN_EDGE: i64 = 100 // reject nav icons/spacers/tracking-pixels: long edge < 100px is never content (real pages/photos are bigger) 32 33func mg_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 34func mg_putn(v: i64) -> i64 { if v==0 { sys_write(1,"0" as *u8,1); return 0 } var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let d:*u8=sys_mmap(24); var k:i64=0; while m>0{d[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i:i64=k-1; while i>=0{let o:*u8=sys_mmap(1);o[0]=d[i];sys_write(1,o,1);i=i-1} return 0 } 35func mg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 36func mg_apps(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[o+i]=s[i]; i=i+1 } return o+i } 37func mg_num(dst: *u8, o: i64, v: i64) -> i64 { let t: *u8=sys_mmap(24); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var w: i64=o; var i: i64=0; while i<k{dst[w]=t[k-1-i]; w=w+1; i=i+1} return w } 38 39// 3-digit zero-padded page filename: page<NNN><ext> into out 40func mg_page_name(idx: i64, ext: *u8, out: *u8) -> i64 { 41 var o: i64 = mg_apps(out, 0, "page" as *u8) 42 out[o] = (48 + ((idx / 100) % 10)) as u8; o = o + 1 43 out[o] = (48 + ((idx / 10) % 10)) as u8; o = o + 1 44 out[o] = (48 + (idx % 10)) as u8; o = o + 1 45 o = mg_apps(out, o, ext) 46 out[o] = 0 as u8 47 return o 48} 49 50func mg_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]==(0 as u8){return 1} return 0 } 51func mg_ext_ok(e: *u8) -> i64 { 52 if mg_streq(e, ".jpg" as *u8)==1 { return 1 } 53 if mg_streq(e, ".jpeg" as *u8)==1 { return 1 } 54 if mg_streq(e, ".png" as *u8)==1 { return 1 } 55 if mg_streq(e, ".webp" as *u8)==1 { return 1 } 56 if mg_streq(e, ".gif" as *u8)==1 { return 1 } 57 if mg_streq(e, ".avif" as *u8)==1 { return 1 } 58 return 0 59} 60func mg_default_jpg(out: *u8) -> i64 { out[0]=46 as u8; out[1]=106 as u8; out[2]=112 as u8; out[3]=103 as u8; out[4]=0 as u8; return 4 } 61// image extension from a URL (strips ?query#frag; lowercases). Defaults .jpg. Recognizes jpg/jpeg/png/webp/gif/avif. 62func mg_ext_from_url(url: *u8, out: *u8) -> i64 { 63 let n: i64 = mg_slen(url) 64 // end = first '?' or '#', else n 65 var end: i64 = n 66 var i: i64 = 0 67 while i < n { let c: i64 = url[i] as i64; if c == 63 { if end == n { end = i } } if c == 35 { if end == n { end = i } } i = i + 1 } 68 // last '.' before end 69 var dot: i64 = 0 - 1 70 var j: i64 = 0 71 while j < end { if url[j] == (46 as u8) { dot = j } j = j + 1 } 72 if dot < 0 { return mg_default_jpg(out) } 73 let el: i64 = end - dot 74 if el > 6 { return mg_default_jpg(out) } 75 var o: i64 = 0 76 var k: i64 = dot 77 while k < end { var c: i64 = url[k] as i64; if c >= 65 { if c <= 90 { c = c + 32 } } out[o] = c as u8; o = o + 1; k = k + 1 } 78 out[o] = 0 as u8 79 if mg_ext_ok(out) == 0 { return mg_default_jpg(out) } 80 return o 81} 82 83// the FIRST two bytes must be a real image magic (JPEG ffd8 / PNG 8950 / GIF 4749 / RIFF-webp 5249 / etc) -- 84// the no-fake-green guard so an HTML error page saved as page000.jpg is rejected. 85func mg_img_magic_ok(b: *u8, n: i64) -> i64 { 86 if n < 4 { return 0 } 87 if b[0]==(0xff as u8) { if b[1]==(0xd8 as u8) { return 1 } } // JPEG 88 if b[0]==(0x89 as u8) { if b[1]==(0x50 as u8) { return 1 } } // PNG 89 if b[0]==(0x47 as u8) { if b[1]==(0x49 as u8) { return 1 } } // GIF 90 if b[0]==(0x52 as u8) { if b[1]==(0x49 as u8) { return 1 } } // RIFF (webp) 91 return 0 92} 93 94// ★ COMPLETE image check (integrity: a valid FULL file, not blank/empty/TRUNCATED/false). Requires the header 95// magic AND the format's end-of-file trailer AND >= MG_MIN_BYTES. A truncated download (valid header, cut off 96// before the trailer) is REJECTED -> never lands as a broken/blank page. This is the "actually downloads a valid 97// full file" guarantee: JPEG must end ff d9; PNG must end with the IEND chunk (49 45 4e 44 + crc); GIF must end 98// 3b; WEBP(RIFF) must have received at least its declared RIFF payload length. 99func mg_img_complete(b: *u8, n: i64) -> i64 { 100 if n < MG_MIN_BYTES { return 0 } 101 // JPEG: ff d8 ... ff d9 102 if b[0]==(0xff as u8) { if b[1]==(0xd8 as u8) { 103 if b[n-2]==(0xff as u8) { if b[n-1]==(0xd9 as u8) { return 1 } } 104 return 0 105 } } 106 // PNG: 89 50 4e 47 ... 49 45 4e 44 <4-byte crc> (last 8 bytes = IEND + crc) 107 if b[0]==(0x89 as u8) { if b[1]==(0x50 as u8) { if b[2]==(0x4e as u8) { if b[3]==(0x47 as u8) { 108 if b[n-8]==(0x49 as u8) { if b[n-7]==(0x45 as u8) { if b[n-6]==(0x4e as u8) { if b[n-5]==(0x44 as u8) { return 1 } } } } 109 return 0 110 } } } } 111 // GIF: 47 49 46 ... 3b 112 if b[0]==(0x47 as u8) { if b[1]==(0x49 as u8) { if b[2]==(0x46 as u8) { 113 if b[n-1]==(0x3b as u8) { return 1 } 114 return 0 115 } } } 116 // WEBP: RIFF <size LE> WEBP ... ; complete iff we received >= (size + 8) bytes 117 if b[0]==(0x52 as u8) { if b[1]==(0x49 as u8) { if b[2]==(0x46 as u8) { if b[3]==(0x46 as u8) { 118 if n >= 12 { if b[8]==(0x57 as u8) { if b[9]==(0x45 as u8) { if b[10]==(0x42 as u8) { if b[11]==(0x50 as u8) { 119 let riffsize: i64 = (b[4] as i64) | ((b[5] as i64) << 8) | ((b[6] as i64) << 16) | ((b[7] as i64) << 24) 120 if riffsize + 8 <= n { return 1 } 121 } } } } } 122 return 0 123 } } } } 124 return 0 125} 126 127// fetch ONE image URL and write it to <dir>/<pagename>. Runs in a FORKED CHILD (mmaps reclaimed on exit). 128// Returns via exit code: 0 = wrote a real image, 1 = fetch/magic/write fail. Parent reads the page file to confirm. 129func mg_fetch_one(url: *u8, store: *TrustStore, dir: *u8, pagename: *u8) -> i64 { 130 let buf: *u8 = sys_mmap(MG_IMG_CAP) 131 let status: *i64 = sys_mmap(8) as *i64 132 let n: i64 = nx_https_fetch_follow_chrome(url, store, buf, MG_IMG_CAP, 6, status) 133 if n <= 0 { return 1 } 134 if status[0] != 200 { return 1 } // reject 403/404/redirect-HTML error bodies up front 135 if mg_img_complete(buf, n) == 0 { return 1 } // valid FULL image only (header+trailer+size); truncated/blank REJECTED 136 if nx_img_long_edge(buf, n) < MG_MIN_EDGE { return 1 } // junk filter: measure REAL pixels, drop icons/spacers (unknown formats KEPT) 137 let path: *u8 = sys_mmap(MG_MAGIC_2048) 138 var o: i64 = mg_apps(path, 0, dir) 139 path[o] = 47 as u8; o = o + 1 140 o = mg_apps(path, o, pagename) 141 path[o] = 0 as u8 142 let fd: i64 = sys_openat_wr(path, 0x1a4) 143 if fd < 0 { return 1 } 144 sys_write(fd, buf, n) 145 sys_close(fd) 146 return 0 147} 148 149// content fingerprint of a written page file: FNV-1a-32(bytes) packed with the byte-size (63-bit, forced non-zero). 150// Two files match iff same content-hash AND same size -> a robust DEDUP key (no adversary; just avoid re-saving the 151// same image linked twice / at multiple srcset sizes). Streams in 64KB chunks (no huge buffer). 0 = file missing. 152func mg_file_fp(path: *u8) -> i64 { 153 let fd: i64 = sys_openat_rd(path) 154 if fd < 0 { return 0 } 155 let chunk: i64 = MG_MAGIC_65536 156 let buf: *u8 = sys_mmap(chunk) 157 var h: i64 = MG_MAGIC_2166136261 // FNV-1a-32 offset basis 158 var total: i64 = 0 159 var run: i64 = 1 160 while run == 1 { 161 let n: i64 = sys_read(fd, buf, chunk) 162 if n <= 0 { run = 0 } else { 163 var j: i64 = 0 164 while j < n { h = h ^ (buf[j] as i64 & 0xff); h = (h * MG_MAGIC_16777619) & 0xffffffff; j = j + 1 } 165 total = total + n 166 } 167 } 168 sys_close(fd) 169 sys_munmap(buf as *u8, chunk) 170 return ((h & 0xffffffff) | ((total & 0x7fffffff) << 32)) | 1 171} 172 173// write <dir>/cbx.json listing the pages (the reader/zoom viewer manifest). exts[i] = page i's extension ptr. 174func mg_write_cbx(dir: *u8, title: *u8, npages: i64, exts: *i64) -> i64 { 175 let jp: *u8 = sys_mmap(MG_MAGIC_2048) 176 var o: i64 = mg_apps(jp, 0, dir); o = mg_apps(jp, o, "/cbx.json" as *u8); jp[o] = 0 as u8 177 let fd: i64 = sys_openat_wr(jp, 0x1a4) 178 if fd < 0 { return 0 - 1 } 179 let hb: *u8 = sys_mmap(MG_MAGIC_65536) 180 var b: i64 = mg_apps(hb, 0, "{\"title\":\"" as *u8) 181 var t: i64 = 0; while title[t] != (0 as u8) { let c: i64 = title[t] as i64; if c >= 32 { if c != 34 { if c != 92 { hb[b] = title[t]; b = b + 1 } } } t = t + 1 } 182 b = mg_apps(hb, b, "\",\"format\":\"web-gallery\",\"pages\":[" as *u8) 183 let pn: *u8 = sys_mmap(64) 184 var i: i64 = 0 185 while i < npages { 186 if i > 0 { hb[b] = 44 as u8; b = b + 1 } 187 b = mg_apps(hb, b, "{\"idx\":" as *u8) 188 b = mg_num(hb, b, i) 189 b = mg_apps(hb, b, ",\"file\":\"" as *u8) 190 mg_page_name(i, exts[i] as *u8, pn) 191 b = mg_apps(hb, b, pn) 192 b = mg_apps(hb, b, "\"}" as *u8) 193 i = i + 1 194 } 195 b = mg_apps(hb, b, "],\"npages\":" as *u8) 196 b = mg_num(hb, b, npages) 197 b = mg_apps(hb, b, "}" as *u8) 198 sys_write(fd, hb, b) 199 sys_close(fd) 200 return npages 201} 202 203// write <dir>/dl.json = {"total":T,"done":D} -- the live progress file the /api/dlstatus endpoint reads so the UI 204// can show "downloading 12/45" (operator 2026-07-04: modern UI wants PROGRESS). Cheap (~30 bytes) per page. 205func mg_write_dl(dir: *u8, total: i64, done: i64) -> i64 { 206 let jp: *u8 = sys_mmap(MG_MAGIC_2048) 207 var o: i64 = mg_apps(jp, 0, dir); o = mg_apps(jp, o, "/dl.json" as *u8); jp[o] = 0 as u8 208 let fd: i64 = sys_openat_wr(jp, 0x1a4) 209 if fd < 0 { return 0 } 210 let b: *u8 = sys_mmap(128); var q: i64 = mg_apps(b, 0, "{\"total\":" as *u8) 211 q = mg_num(b, q, total); q = mg_apps(b, q, ",\"done\":" as *u8); q = mg_num(b, q, done); q = mg_apps(b, q, "}" as *u8) 212 sys_write(fd, b, q); sys_close(fd) 213 return 0 214} 215 216// ORCHESTRATE: page_url -> dest_dir/pageNNN + cbx.json. Returns pages written (>=0), or negative error 217// (-1 html fetch, -2 no images). store must be loaded by the caller (inherited by the fork children). 218// title_out (cap 512) is filled with the EFFECTIVE title: the page's own <title>/og:title if found, else the 219// caller's fallback `title` -- so the operator gets the real title automatically ("just focus on reading"). 220func mg_gallery_to_pages(page_url: *u8, dest_dir: *u8, title: *u8, store: *TrustStore, title_out: *u8) -> i64 { 221 let url_buf: *u8 = sys_mmap(MG_MAGIC_1048576) 222 let offs: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64 223 let lens: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64 224 let status: *i64 = sys_mmap(8) as *i64 225 let pn: i64 = mg_slen(page_url) 226 var nimg: i64 = 0 227 // default the effective title to the caller's fallback; page-based paths overwrite it with the real title 228 var ti: i64 = 0; while title[ti] != (0 as u8) { title_out[ti] = title[ti]; ti = ti + 1 } title_out[ti] = 0 as u8 229 // ---- R2b: PER-SITE ADAPTER dispatch. nhentai = parse the gallery PAGE (its v1 API is deprecated -> 403); 230 // mangadex = the at-home API (public, stable). Else generic multi-strategy harvest. ---- 231 let site: i64 = na_detect(page_url, pn) 232 if site == 1 { 233 // nhentai: fetch the gallery PAGE, grab the title, derive full-res page URLs from its thumbnails 234 let html: *u8 = sys_mmap(MG_HTML_CAP) 235 let hn: i64 = nx_https_fetch_follow_chrome(page_url, store, html, MG_HTML_CAP, 6, status) 236 if hn > 0 { if status[0] == 200 { 237 if na_html_title(html, hn, title_out, 500) <= 0 { var t2: i64 = 0; while title[t2]!=(0 as u8){ title_out[t2]=title[t2]; t2=t2+1 } title_out[t2]=0 as u8 } 238 let c: i64 = na_nhentai_from_page(html, hn, url_buf, MG_MAGIC_1048576, offs, lens, MG_MAX_IMGS) 239 if c > 0 { nimg = c } 240 } } 241 } else { if site == 2 { 242 let api: *u8 = sys_mmap(256) 243 if na_mangadex_api(page_url, pn, api) > 0 { 244 let jb: *u8 = sys_mmap(MG_HTML_CAP) 245 let jn: i64 = nx_https_fetch_follow_chrome(api, store, jb, MG_HTML_CAP, 6, status) 246 if jn > 0 { if status[0] == 200 { let c: i64 = na_mangadex_build(jb, jn, url_buf, MG_MAGIC_1048576, offs, lens, MG_MAX_IMGS); if c > 0 { nimg = c } } } 247 } 248 // SERIES + auto-title: one metadata call -> "<Series> - Ch.<N>" (so the operator never has to name it). 249 if nimg > 0 { 250 let mapi: *u8 = sys_mmap(512) 251 if na_mangadex_meta_api(page_url, pn, mapi) > 0 { 252 let mb: *u8 = sys_mmap(MG_HTML_CAP) 253 let mn: i64 = nx_https_fetch_follow_chrome(mapi, store, mb, MG_HTML_CAP, 6, status) 254 if mn > 0 { if status[0] == 200 { na_mangadex_title(mb, mn, title_out, 500) } } 255 } 256 } 257 } } 258 // GENERIC (or adapter miss): fetch the page HTML + multi-strategy harvest (img/lazy/srcset/JS-JSON) + title 259 if nimg <= 0 { 260 let html: *u8 = sys_mmap(MG_HTML_CAP) 261 let hn: i64 = nx_https_fetch_follow_chrome(page_url, store, html, MG_HTML_CAP, 6, status) 262 if hn <= 0 { return 0 - 1 } 263 if na_html_title(html, hn, title_out, 500) <= 0 { var t3: i64 = 0; while title[t3]!=(0 as u8){ title_out[t3]=title[t3]; t3=t3+1 } title_out[t3]=0 as u8 } 264 nimg = nx_img_harvest(html, hn, page_url, pn, url_buf, MG_MAGIC_1048576, offs, lens, MG_MAX_IMGS) 265 } 266 if nimg <= 0 { return 0 - 2 } 267 __syscall(258, 0 - 100, dest_dir as i64, 0x1ed, 0, 0, 0) // mkdir dest (idempotent) 268 mg_write_dl(dest_dir, nimg, 0) // progress: total known, 0 done -> UI can show a real bar immediately 269 // 3. fork-fetch each image -> pageNNN.<ext>; keep only the ones that land as a real image 270 let exts: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64 271 let seen: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64 // content fingerprints of KEPT pages (dedup set) 272 var written: i64 = 0 273 var ndup: i64 = 0 274 var i: i64 = 0 275 while i < nimg { 276 let url: *u8 = ((url_buf as i64) + offs[i]) as *u8 277 // NUL-terminate a private copy of this URL (url_buf is packed) 278 let u: *u8 = sys_mmap(MG_MAGIC_4096) 279 var k: i64 = 0 280 while k < lens[i] { if k < MG_MAGIC_4095 { u[k] = url[k] } k = k + 1 } 281 u[lens[i]] = 0 as u8 282 let ext: *u8 = sys_mmap(8) 283 mg_ext_from_url(u, ext) 284 let pn: *u8 = sys_mmap(64) 285 mg_page_name(written, ext, pn) 286 // fork the fetch (child mmaps reclaimed on exit) 287 let pid: i64 = sys_fork() 288 if pid == 0 { let rc: i64 = mg_fetch_one(u, store, dest_dir, pn); sys_exit(rc) } 289 let st: *i64 = sys_mmap(8) as *i64 290 sys_wait4(pid, st, 0) 291 // confirm the page file exists (child wrote it) before counting it 292 let pp: *u8 = sys_mmap(MG_MAGIC_2048); var po: i64 = mg_apps(pp, 0, dest_dir); pp[po]=47 as u8; po=po+1; po = mg_apps(pp, po, pn); pp[po]=0 as u8 293 // DEDUP: fingerprint the landed page; if it matches an already-kept page, delete it + reuse the slot. 294 let fp: i64 = mg_file_fp(pp) 295 if fp != 0 { 296 var dup: i64 = 0 297 var s: i64 = 0 298 while s < written { if seen[s] == fp { dup = 1; s = written } else { s = s + 1 } } 299 if dup == 1 { __syscall(263, 0 - 100, pp as i64, 0, 0, 0, 0); ndup = ndup + 1 } // unlinkat AT_FDCWD: drop the byte-identical duplicate 300 else { seen[written] = fp; exts[written] = ext as i64; written = written + 1 } 301 } 302 mg_write_dl(dest_dir, nimg, written) // progress heartbeat -> /api/dlstatus -> live "N/total" in the UI 303 i = i + 1 304 } 305 if ndup > 0 { mg_puts("MANGA-GET: deduped " as *u8); mg_putn(ndup); mg_puts(" byte-identical duplicate image(s)\n" as *u8) } 306 if written <= 0 { 307 __syscall(263, 0 - 100, dest_dir as i64, 0x200, 0, 0, 0) // unlinkat AT_REMOVEDIR: remove the empty book dir (no sprawl on total failure) 308 return 0 - 2 309 } 310 mg_write_cbx(dest_dir, title_out, written, exts) 311 return written 312} 313 314func main(argc: i64, argv: *i64) -> i64 { 315 if argc < 4 { mg_puts("usage: nx_manga_get <page_url> <slug> <ukey> [title]\n" as *u8); sys_exit(2); return 2 } 316 let page_url: *u8 = argv[1] as *u8 317 let slug: *u8 = argv[2] as *u8 318 let ukey: *u8 = argv[3] as *u8 319 var title: *u8 = slug 320 if argc > 4 { title = argv[4] as *u8 } 321 322 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, MG_MAGIC_4194304) 323 if r <= 0 { mg_puts("MANGA-GET: certdata load failed\n" as *u8); sys_exit(1); return 1 } 324 let store: *TrustStore = r as *TrustStore 325 326 let dest: *u8 = sys_mmap(MG_MAGIC_2048) 327 rl_personal_slug_dir(ukey, slug, dest) // reader/u_<ukey>_<slug> 328 329 let title_eff: *u8 = sys_mmap(512) 330 let np: i64 = mg_gallery_to_pages(page_url, dest, title, store, title_eff) 331 if np < 0 { mg_puts("MANGA-GET-FAIL rc=" as *u8); mg_putn(np); mg_puts("\n" as *u8); sys_exit(1); return 1 } 332 rl_add_personal(ukey, slug, title_eff, mg_slen(title_eff)) 333 mg_puts("MANGA-GET OK slug=" as *u8); mg_puts(slug); mg_puts(" title=" as *u8); mg_puts(title_eff); mg_puts(" pages=" as *u8); mg_putn(np); mg_puts(" -> personal area\n" as *u8) 334 sys_exit(0); return 0 335}