code wiki / _hdl_build / nx_manga_sites.nx
nx_manga_sites.nx source
↩ module page · 400 lines · 21013 B
1// nx_manga_sites.nx -- R2b PER-SITE ADAPTERS (operator 2026-07-03: "make sure nhentai and mangadex and the others
2// each work as endpoints that we had working before along with all the new ones"). nhentai + MangaDex ship their
3// PAGE LIST in a JSON API (not the chapter HTML), so the generic harvester can't reach them -- each needs a real
4// adapter: detect the site from the URL, build its API URL, and parse the API JSON into the exact ORDERED page-URL
5// list. Everything else (batoto/manhwaread/new sites) falls back to nx_img_harvest (the generic multi-strategy).
6// Patterns GROUNDED from the working Python mm (verified, not assumed):
7// nhentai: /g/<id>/ -> GET https://nhentai.net/api/gallery/<id>
8// JSON media_id + images.pages[].t (j/p/g/w) -> https://i1.nhentai.net/galleries/<media_id>/<i>.<ext>
9// mangadex: /chapter/<uuid> -> GET https://api.mangadex.org/at-home/server/<uuid>
10// JSON baseUrl + chapter.hash + chapter.data[] -> <baseUrl>/data/<hash>/<filename>
11// PURE parsers (na_*_build) are gated deterministically on FIXTURE JSON (legal posture: build the general
12// capability, gate on fixtures, the operator points it where they like). Referer: the shared fetch already sends a
13// full browser UA which both CDNs accept; a referer-capable fetch is the fallback if a CDN 403s (R2d). license_tier: ORIGINAL
14import "nx_syscalls.nx"
15
16func na_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
17func na_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 }
18func na_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
19// case-insensitive substring index of needle in hay[0..hn), or -1
20func na_find_ci(hay: *u8, hn: i64, needle: *u8) -> i64 {
21 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 }
22 if nl == 0 { return 0 - 1 }
23 var i: i64 = 0
24 while i + nl <= hn {
25 var m: i64 = 1; var j: i64 = 0
26 while j < nl { if na_lc(hay[i+j] as i64) != na_lc(needle[j] as i64) { m = 0; j = nl } else { j = j + 1 } }
27 if m == 1 { return i }
28 i = i + 1
29 }
30 return 0 - 1
31}
32// exact substring (case-sensitive) from `from`
33func na_find_from(hay: *u8, hn: i64, needle: *u8, from: i64) -> i64 {
34 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 }
35 if nl == 0 { return 0 - 1 }
36 if from < 0 { return 0 - 1 }
37 var i: i64 = from
38 while i + nl <= hn {
39 var m: i64 = 1; var j: i64 = 0
40 while j < nl { if hay[i+j] != needle[j] { m = 0; j = nl } else { j = j + 1 } }
41 if m == 1 { return i }
42 i = i + 1
43 }
44 return 0 - 1
45}
46
47// site id: 1=nhentai, 2=mangadex, 0=generic
48func na_detect(url: *u8, n: i64) -> i64 {
49 if na_find_ci(url, n, "nhentai.net" as *u8) >= 0 { if na_find_ci(url, n, "/g/" as *u8) >= 0 { return 1 } }
50 if na_find_ci(url, n, "mangadex.org" as *u8) >= 0 { if na_find_ci(url, n, "/chapter/" as *u8) >= 0 { return 2 } }
51 return 0
52}
53
54// build the nhentai gallery API url from a /g/<id>/ page url. returns len, 0 if no id.
55func na_nhentai_api(url: *u8, n: i64, out: *u8) -> i64 {
56 let at: i64 = na_find_ci(url, n, "/g/" as *u8)
57 if at < 0 { return 0 }
58 var i: i64 = at + 3
59 var o: i64 = na_apps(out, 0, "https://nhentai.net/api/gallery/" as *u8)
60 var any: i64 = 0
61 while i < n { let c: i64 = url[i] as i64; if c >= 48 { if c <= 57 { out[o] = url[i]; o = o + 1; any = 1; i = i + 1 } else { i = n } } else { i = n } }
62 out[o] = 0 as u8
63 if any == 0 { return 0 }
64 return o
65}
66// build the mangadex at-home API url from a /chapter/<uuid> page url. returns len, 0 if no id.
67func na_mangadex_api(url: *u8, n: i64, out: *u8) -> i64 {
68 let at: i64 = na_find_ci(url, n, "/chapter/" as *u8)
69 if at < 0 { return 0 }
70 var i: i64 = at + 9
71 var o: i64 = na_apps(out, 0, "https://api.mangadex.org/at-home/server/" as *u8)
72 var any: i64 = 0
73 while i < n { let c: i64 = url[i] as i64
74 var ok: i64 = 0
75 if c >= 48 { if c <= 57 { ok = 1 } }
76 if c >= 97 { if c <= 122 { ok = 1 } }
77 if c >= 65 { if c <= 90 { ok = 1 } }
78 if c == 45 { ok = 1 }
79 if ok == 1 { out[o] = url[i]; o = o + 1; any = 1; i = i + 1 } else { i = n }
80 }
81 out[o] = 0 as u8
82 if any == 0 { return 0 }
83 return o
84}
85
86// build the mangadex chapter-metadata API url (series title + chapter number) from a /chapter/<uuid> page url.
87func na_mangadex_meta_api(url: *u8, n: i64, out: *u8) -> i64 {
88 let at: i64 = na_find_ci(url, n, "/chapter/" as *u8)
89 if at < 0 { return 0 }
90 var i: i64 = at + 9
91 var o: i64 = na_apps(out, 0, "https://api.mangadex.org/chapter/" as *u8)
92 var any: i64 = 0
93 while i < n { let c: i64 = url[i] as i64
94 var ok: i64 = 0
95 if c >= 48 { if c <= 57 { ok = 1 } }
96 if c >= 97 { if c <= 122 { ok = 1 } }
97 if c >= 65 { if c <= 90 { ok = 1 } }
98 if c == 45 { ok = 1 }
99 if ok == 1 { out[o] = url[i]; o = o + 1; any = 1; i = i + 1 } else { i = n }
100 }
101 o = na_apps(out, o, "?includes[]=manga" as *u8)
102 out[o] = 0 as u8
103 if any == 0 { return 0 }
104 return o
105}
106
107// parse "<Series> - Ch.<N>" from a /chapter?includes[]=manga response. The MANGA relationship's title is the
108// object form `title":{"en":"..."` (the chapter's own title is a plain string), so that marker finds the series.
109// returns len (0 if no series found -> caller keeps its default title). Also the SERIES-RECOGNITION key.
110func na_mangadex_title(json: *u8, jn: i64, out: *u8, cap: i64) -> i64 {
111 // the MANGA title is the OBJECT form `title":{"<lang>":"..."` (chapter title is a plain string). First lang wins.
112 let m: i64 = na_find_from(json, jn, "title\":{" as *u8, 0)
113 if m < 0 { return 0 }
114 var a: i64 = m + 8 // just past `title":{`
115 var r1: i64 = 1 // skip the lang key up to its ':' separator
116 while r1 == 1 { if a >= jn { r1 = 0 } else { if (json[a] as i64) == 58 { a = a + 1; r1 = 0 } else { a = a + 1 } } }
117 var r2: i64 = 1 // advance to the opening '"' of the value
118 while r2 == 1 { if a >= jn { r2 = 0 } else { if (json[a] as i64) == 34 { a = a + 1; r2 = 0 } else { a = a + 1 } } }
119 var o: i64 = 0
120 while a < jn { let c: i64 = json[a] as i64; if c == 34 { a = jn } else { if c == 92 { a = a + 1 } else { if o < cap - 24 { out[o] = json[a] as u8; o = o + 1 } a = a + 1 } } }
121 let ch: i64 = na_find_from(json, jn, "\"chapter\":\"" as *u8, 0)
122 if ch >= 0 {
123 var b: i64 = ch + 11 // just past `"chapter":"`
124 o = na_apps(out, o, " - Ch." as *u8)
125 while b < jn { let c2: i64 = json[b] as i64; if c2 == 34 { b = jn } else { if o < cap - 2 { out[o] = json[b] as u8; o = o + 1 } b = b + 1 } }
126 }
127 out[o] = 0 as u8
128 return o
129}
130
131// offset of the char just after the closing quote of `"key"` in buf, or -1
132func na_key_end(buf: *u8, n: i64, key: *u8) -> i64 {
133 let kq: *u8 = sys_mmap(128)
134 var ko: i64 = 0; kq[ko]=34 as u8; ko=ko+1
135 var ki: i64 = 0; while key[ki] != (0 as u8) { kq[ko]=key[ki]; ko=ko+1; ki=ki+1 }
136 kq[ko]=34 as u8; ko=ko+1; kq[ko]=0 as u8 // kq content = "key" (quote+key+quote); its length = ko
137 let at: i64 = na_find_from(buf, n, kq, 0)
138 if at < 0 { return 0 - 1 }
139 return at + ko // just past the closing quote
140}
141// 1 if c is JSON whitespace
142func na_ws(c: i64) -> i64 { if c==32 { return 1 } if c==9 { return 1 } if c==10 { return 1 } if c==13 { return 1 } return 0 }
143// index of the first byte == ch in buf[from..limit), or -1
144func na_char_from(buf: *u8, limit: i64, ch: i64, from: i64) -> i64 {
145 var i: i64 = from
146 while i < limit { if (buf[i] as i64) == ch { return i } i = i + 1 }
147 return 0 - 1
148}
149// find "key": "value" -> copy value (JSON \/-> /) into out. returns len, -1 absent.
150func na_json_str(buf: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 {
151 let ke: i64 = na_key_end(buf, n, key)
152 if ke < 0 { return 0 - 1 }
153 // clean scan: skip ws, require ':', skip ws, require '"', then read the value
154 var i: i64 = ke
155 var st: i64 = 0 // 0=want colon, 1=want quote, 2=reading
156 var o: i64 = 0
157 while i < n {
158 let c: i64 = buf[i] as i64
159 if st == 0 {
160 if c == 58 { st = 1 } else { if na_ws(c)==0 { return 0 - 1 } }
161 i = i + 1
162 } else { if st == 1 {
163 if c == 34 { st = 2 } else { if na_ws(c)==0 { return 0 - 1 } }
164 i = i + 1
165 } else {
166 if c == 34 { out[o] = 0 as u8; return o }
167 if c == 92 { if i + 1 < n { if buf[i+1]==(47 as u8) { if o<cap-1 { out[o]=47 as u8; o=o+1 } i=i+2 } else { if o<cap-1 { out[o]=buf[i] as u8; o=o+1 } i=i+1 } } else { i = i + 1 } }
168 else { if o<cap-1 { out[o]=buf[i]; o=o+1 } i=i+1 }
169 } }
170 }
171 return 0 - 1
172}
173func na_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 }
174func na_ext_for(t: i64) -> *u8 {
175 if t == 112 { return "png" as *u8 } // p
176 if t == 103 { return "gif" as *u8 } // g
177 if t == 119 { return "webp" as *u8 } // w
178 return "jpg" as *u8 // j (default)
179}
180
181// ---- TITLE (general capability: "grab the title so I can just focus on reading") ----
182// extract a human title from a page: prefer og:title, else <title> (strip a trailing " » site" / " :: site").
183// unescapes & ' " < >. returns length, 0 if none.
184func na_html_title(html: *u8, hlen: i64, out: *u8, cap: i64) -> i64 {
185 var s: i64 = 0 - 1; var e: i64 = 0 - 1
186 // og:title content="..."
187 let og: i64 = na_find_ci(html, hlen, "og:title" as *u8)
188 if og >= 0 {
189 let ct: i64 = na_find_from(html, hlen, "content=" as *u8, og)
190 if ct >= 0 { let q: i64 = na_char_from(html, hlen, 34, ct); if q >= 0 { s = q + 1; e = na_char_from(html, hlen, 34, s) } }
191 }
192 if s < 0 {
193 let tt: i64 = na_find_ci(html, hlen, "<title>" as *u8)
194 if tt >= 0 { s = tt + 7; e = na_find_from(html, hlen, "<" as *u8, s) }
195 }
196 if s < 0 { return 0 }
197 if e < 0 { e = s }
198 // copy s..e with entity decode
199 var o: i64 = 0
200 var i: i64 = s
201 while i < e {
202 let c: i64 = html[i] as i64
203 if c == 38 { // '&' -> maybe an entity
204 let cl: i64 = na_ent_len(html, e, i) // input bytes consumed (1 if not an entity)
205 let dec: i64 = na_ent_byte(html, e, i) // decoded byte, or -1 if not an entity
206 if dec >= 0 { if o < cap-1 { out[o]=dec as u8; o=o+1 } i = i + cl }
207 else { if o < cap-1 { out[o]=html[i]; o=o+1 } i = i + 1 }
208 } else { if o < cap-1 { out[o]=html[i]; o=o+1 } i = i + 1 }
209 }
210 out[o] = 0 as u8
211 // strip a trailing " » site" separator (0xc2 0xbb = '»' in UTF-8)
212 var j: i64 = 0
213 while j + 2 < o { if out[j]==(32 as u8) { if out[j+1]==(0xc2 as u8) { if out[j+2]==(0xbb as u8) { out[j]=0 as u8; return j } } } j = j + 1 }
214 return o
215}
216func na_ent_is(html: *u8, e: i64, i: i64, ent: *u8) -> i64 { var k: i64=0; while ent[k]!=(0 as u8) { if i+k>=e { return 0 } if html[i+k]!=ent[k] { return 0 } k=k+1 } return 1 }
217// decoded byte for a known entity at i, or -1 if none
218func na_ent_byte(html: *u8, e: i64, i: i64) -> i64 {
219 if na_ent_is(html, e, i, "&" as *u8)==1 { return 38 }
220 if na_ent_is(html, e, i, "'" as *u8)==1 { return 39 }
221 if na_ent_is(html, e, i, """ as *u8)==1 { return 34 }
222 if na_ent_is(html, e, i, "<" as *u8)==1 { return 60 }
223 if na_ent_is(html, e, i, ">" as *u8)==1 { return 62 }
224 return 0 - 1
225}
226// input bytes consumed by a known entity at i (1 if not an entity)
227func na_ent_len(html: *u8, e: i64, i: i64) -> i64 {
228 if na_ent_is(html, e, i, "&" as *u8)==1 { return 5 }
229 if na_ent_is(html, e, i, "'" as *u8)==1 { return 5 }
230 if na_ent_is(html, e, i, """ as *u8)==1 { return 6 }
231 if na_ent_is(html, e, i, "<" as *u8)==1 { return 4 }
232 if na_ent_is(html, e, i, ">" as *u8)==1 { return 4 }
233 return 1
234}
235
236// ---- nhentai PAGE-BASED extraction (the v1 API is deprecated -> 403; parse the gallery PAGE instead) ----
237// The page embeds thumbnails t{d}.nhentai.net/galleries/<media_id>/<n>t.<ext> for the current gallery (before the
238// related-galleries section). Full-res = i1.nhentai.net/galleries/<media_id>/<n>.<ext>. Returns count, -1 fail.
239func na_isdig(c: i64) -> i64 { if c>=48 { if c<=57 { return 1 } } return 0 }
240func na_islet(c: i64) -> i64 { let l: i64 = na_lc(c); if l>=97 { if l<=122 { return 1 } } return 0 }
241// index of first NON-digit at/after `from` (bounded by limit) -- clean, no sentinel
242func na_end_digits(buf: *u8, limit: i64, from: i64) -> i64 { var i: i64 = from; while i < limit { if na_isdig(buf[i] as i64)==1 { i = i + 1 } else { return i } } return i }
243func na_end_letters(buf: *u8, limit: i64, from: i64) -> i64 { var i: i64 = from; while i < limit { if na_islet(buf[i] as i64)==1 { i = i + 1 } else { return i } } return i }
244func na_nhentai_from_page(html: *u8, hlen: i64, url_buf: *u8, cap: i64, offs: *i64, lens: *i64, max: i64) -> i64 {
245 // 1. current gallery media_id = the first /galleries/<digits>/<digits>t.<ext>
246 let mid: *u8 = sys_mmap(64); var midlen: i64 = 0
247 var i: i64 = 0; var found: i64 = 0
248 while found == 0 {
249 let at: i64 = na_find_from(html, hlen, "/galleries/" as *u8, i)
250 if at < 0 { found = 0 - 1 } else {
251 let ms: i64 = at + 11
252 let p: i64 = na_end_digits(html, hlen, ms) // end of media_id digits
253 if p > ms { if p < hlen { if html[p]==(47 as u8) { // '/'
254 let qs: i64 = p + 1
255 let q: i64 = na_end_digits(html, hlen, qs) // end of page-number digits
256 if q > qs { if q < hlen { if html[q]==(116 as u8) { // 't' -> page thumbnail
257 var m: i64 = 0; while m < p - ms { mid[m]=html[ms+m]; m=m+1 } mid[p-ms]=0 as u8; midlen = p - ms; found = 1
258 } } }
259 } } }
260 i = at + 1
261 }
262 }
263 if found != 1 { return 0 - 1 }
264 // 2. collect all /galleries/<mid>/<n>t.<ext> for this mid, deduped by full URL, in order.
265 let key: *u8 = sys_mmap(128); var ko: i64 = na_apps(key, 0, "/galleries/" as *u8)
266 var mm: i64 = 0; while mm < midlen { key[ko]=mid[mm]; ko=ko+1; mm=mm+1 } key[ko]=47 as u8; ko=ko+1; key[ko]=0 as u8
267 var count: i64 = 0; var buf_pos: i64 = 0
268 var scan: i64 = 0; var go: i64 = 1
269 while go == 1 {
270 let at2: i64 = na_find_from(html, hlen, key, scan)
271 if at2 < 0 { go = 0 } else {
272 let ns: i64 = at2 + ko // just after "/galleries/<mid>/"
273 let p2: i64 = na_end_digits(html, hlen, ns)
274 var is_page: i64 = 0
275 if p2 > ns { if p2 < hlen { if html[p2]==(116 as u8) { if p2+1 < hlen { if html[p2+1]==(46 as u8) { is_page = 1 } } } } } // <digits> 't' '.'
276 if is_page == 1 {
277 let es: i64 = p2 + 2
278 let ee: i64 = na_end_letters(html, hlen, es)
279 if count < max {
280 // build https://i1.nhentai.net/galleries/<mid>/<n>.<ext>
281 let start: i64 = buf_pos
282 buf_pos = na_apps(url_buf, buf_pos, "https://i1.nhentai.net/galleries/" as *u8)
283 var mi: i64 = 0; while mi < midlen { url_buf[buf_pos]=mid[mi]; buf_pos=buf_pos+1; mi=mi+1 }
284 url_buf[buf_pos]=47 as u8; buf_pos=buf_pos+1
285 var di: i64 = ns; while di < p2 { url_buf[buf_pos]=html[di]; buf_pos=buf_pos+1; di=di+1 } // page number
286 url_buf[buf_pos]=46 as u8; buf_pos=buf_pos+1
287 var xi: i64 = es; while xi < ee { url_buf[buf_pos]=(na_lc(html[xi] as i64)) as u8; buf_pos=buf_pos+1; xi=xi+1 }
288 // dedup by full URL (thumbnails may repeat)
289 let cn: i64 = buf_pos - start
290 if na_seen(url_buf, offs, lens, count, start, cn) == 1 { buf_pos = start } else { offs[count]=start; lens[count]=cn; count=count+1 }
291 }
292 }
293 scan = at2 + ko
294 }
295 }
296 if count <= 0 { return 0 - 1 }
297 return count
298}
299// seen? compare url_buf[start..start+cn) against each collected offs/lens
300func na_seen(url_buf: *u8, offs: *i64, lens: *i64, count: i64, start: i64, cn: i64) -> i64 {
301 var i: i64 = 0
302 while i < count {
303 if lens[i] == cn {
304 var eq: i64 = 1; var k: i64 = 0
305 while k < cn { if url_buf[offs[i]+k] != url_buf[start+k] { eq = 0; k = cn } else { k = k + 1 } }
306 if eq == 1 { return 1 }
307 }
308 i = i + 1
309 }
310 return 0
311}
312
313// nhentai: parse media_id + count of pages[].t -> build i1.nhentai.net page URLs. returns count, -1 on parse fail.
314func na_nhentai_build(json: *u8, jn: i64, url_buf: *u8, cap: i64, offs: *i64, lens: *i64, max: i64) -> i64 {
315 let media: *u8 = sys_mmap(64)
316 let ml: i64 = na_json_str(json, jn, "media_id" as *u8, media, 64)
317 if ml <= 0 { return 0 - 1 }
318 // find the pages array: "pages":[
319 let pat: i64 = na_find_from(json, jn, "\"pages\"" as *u8, 0)
320 if pat < 0 { return 0 - 1 }
321 let lb: i64 = na_find_from(json, jn, "[" as *u8, pat)
322 if lb < 0 { return 0 - 1 }
323 let rb: i64 = na_find_from(json, jn, "]" as *u8, lb)
324 if rb < 0 { return 0 - 1 }
325 var count: i64 = 0
326 var buf_pos: i64 = 0
327 var i: i64 = lb
328 var page: i64 = 1
329 // each page object contains exactly one "t":"X"
330 var go: i64 = 1
331 while go == 1 {
332 let ti: i64 = na_find_from(json, rb, "\"t\"" as *u8, i)
333 if ti < 0 { go = 0 } else {
334 let q1: i64 = na_char_from(json, rb, 58, ti) // ':' after "t"
335 if q1 < 0 { go = 0 } else {
336 let vq: i64 = na_char_from(json, rb, 34, q1 + 1) // opening value quote
337 if vq < 0 { go = 0 } else {
338 let tchar: i64 = json[vq + 1] as i64
339 let ext: *u8 = na_ext_for(na_lc(tchar))
340 if count < max {
341 let start: i64 = buf_pos
342 buf_pos = na_apps(url_buf, buf_pos, "https://i1.nhentai.net/galleries/" as *u8)
343 var mi: i64 = 0; while mi < ml { url_buf[buf_pos]=media[mi]; buf_pos=buf_pos+1; mi=mi+1 }
344 url_buf[buf_pos]=47 as u8; buf_pos=buf_pos+1
345 buf_pos = na_num(url_buf, buf_pos, page)
346 url_buf[buf_pos]=46 as u8; buf_pos=buf_pos+1
347 buf_pos = na_apps(url_buf, buf_pos, ext)
348 offs[count]=start; lens[count]=buf_pos-start; count=count+1
349 }
350 page = page + 1
351 i = vq + 2
352 }
353 }
354 }
355 }
356 if count <= 0 { return 0 - 1 }
357 return count
358}
359
360// mangadex: parse baseUrl + chapter.hash + data[] -> build <baseUrl>/data/<hash>/<file>. returns count, -1 fail.
361func na_mangadex_build(json: *u8, jn: i64, url_buf: *u8, cap: i64, offs: *i64, lens: *i64, max: i64) -> i64 {
362 let base: *u8 = sys_mmap(512)
363 let bl: i64 = na_json_str(json, jn, "baseUrl" as *u8, base, 512)
364 if bl <= 0 { return 0 - 1 }
365 let hash: *u8 = sys_mmap(128)
366 let hl: i64 = na_json_str(json, jn, "hash" as *u8, hash, 128)
367 if hl <= 0 { return 0 - 1 }
368 // "data":[ (the full-quality array; dataSaver is separate)
369 let dat: i64 = na_find_from(json, jn, "\"data\"" as *u8, 0)
370 if dat < 0 { return 0 - 1 }
371 let lb: i64 = na_find_from(json, jn, "[" as *u8, dat)
372 if lb < 0 { return 0 - 1 }
373 let rb: i64 = na_find_from(json, jn, "]" as *u8, lb)
374 if rb < 0 { return 0 - 1 }
375 var count: i64 = 0
376 var buf_pos: i64 = 0
377 var i: i64 = lb + 1
378 var go: i64 = 1
379 while go == 1 {
380 let sq: i64 = na_char_from(json, rb, 34, i) // opening quote of the next filename string
381 if sq < 0 { go = 0 } else {
382 let e: i64 = na_char_from(json, rb, 34, sq + 1) // closing quote
383 if e < 0 { go = 0 } else {
384 let fl: i64 = e - (sq + 1)
385 if fl > 0 { if count < max {
386 let start: i64 = buf_pos
387 var q: i64 = 0; while q < bl { url_buf[buf_pos]=base[q]; buf_pos=buf_pos+1; q=q+1 }
388 buf_pos = na_apps(url_buf, buf_pos, "/data/" as *u8)
389 q = 0; while q < hl { url_buf[buf_pos]=hash[q]; buf_pos=buf_pos+1; q=q+1 }
390 url_buf[buf_pos]=47 as u8; buf_pos=buf_pos+1
391 q = 0; while q < fl { url_buf[buf_pos]=json[sq+1+q]; buf_pos=buf_pos+1; q=q+1 }
392 offs[count]=start; lens[count]=buf_pos-start; count=count+1
393 } }
394 i = e + 1
395 }
396 }
397 }
398 if count <= 0 { return 0 - 1 }
399 return count
400}