use bufio; use dejson; use encoding::json; use errors; use fs; use io; use os; use path; use strings; use trace; def MISSINGNO = "minecraft:missingno"; def BUILTIN_MISSING = "minecraft:builtin/missing"; type Pack = struct { fs: *fs::fs, kind: str, }; fn resource_splitpath(path: str) (str, str, str, str, str) = { const (base, path) = strings::cut(path, "/"); const (ns, path) = strings::cut(path, "/"); const (kind, path) = strings::cut(path, "/"); const bnameindex = match (strings::rindex(path, '/')) { case let index: size => yield index + 1; case void => yield 0z; }; const extindex = match (strings::rindex( strings::sub(path, bnameindex, strings::end), '.')) { case let index: size => yield bnameindex + index; case void => yield len(path); }; const name = strings::sub(path, 0, extindex); const ext = strings::sub(path, extindex, strings::end); return (base, ns, kind, name, ext); }; fn resource_open( pack: *Pack, kind: str, ident: str, ext: str, tr: *trace::tracer, ) (io::handle | trace::failed) = { const (ns, name) = ident_split(ident); const fname = strings::concat(name, ext); defer free(fname); let path = path::init()!; if (path::set(&path, pack.kind, ns, kind, fname) is path::too_long) { return trace::error(tr, "Path too long"); }; const path = path::string(&path); match (fs::open(pack.fs, path, fs::flag::RDONLY)) { case let f: io::handle => return f; case let err: fs::error => return trace::error(tr, "open: {}", fs::strerror(err)); }; }; fn resource_load_json( pack: *Pack, kind: str, ident: str, ext: str, tr: *trace::tracer, ) (json::value | trace::failed) = { const f = resource_open(pack, kind, ident, ext, tr)?; let rbuf: [os::BUFSZ]u8 = [0...]; let buf = bufio::init(f, rbuf, []); const json = match (json::load(&buf)) { case let json: json::value => yield json; case let err: json::error => io::close(f): void; return trace::error(tr, "Invalid JSON: {}", json::strerror(err)); }; match (io::close(f)) { case void => void; case let err: io::error => json::finish(json); return trace::error(tr, "close: {}", io::strerror(err)); }; return json; }; fn resource_search( pack: *Pack, kind: str, exts: str... ) [](str, str) = { const tr = trace::ctx(&trace::root, "resource search"); let out: [](str, str) = []; let path = path::init()!; const it = match (fs::iter(pack.fs, pack.kind)) { case let x: *fs::iterator => yield x; case let err: fs::error => trace::error(&tr, "{}: fs::iter: {}", pack.kind, fs::strerror(err)): void; return out; }; defer fs::finish(it); for (true) match (fs::next(it)) { case let ent: fs::dirent => if (!fs::isdir(ent.ftype)) continue; if (strings::hasprefix(ent.name, ".")) continue; if (path::set(&path, pack.kind, ent.name, kind) is path::too_long) { trace::error(&tr, "{}/{}/{}: Path too long", pack.kind, ent.name, kind): void; continue; }; resource_search_ns(pack, &out, path::string(&path), &tr, exts...); case done => break; }; return out; }; fn resource_search_ns( pack: *Pack, out: *[](str, str), parent: str, tr: *trace::tracer, exts: str... ) void = { let path = path::init()!; const it = match (fs::iter(pack.fs, parent)) { case let x: *fs::iterator => yield x; case errors::noentry => return; case let err: fs::error => trace::error(tr, "{}: fs::iter: {}", parent, fs::strerror(err)): void; return; }; defer fs::finish(it); for (true) match (fs::next(it)) { case let ent: fs::dirent => if (strings::hasprefix(ent.name, ".")) continue; if (path::set(&path, parent, ent.name) is path::too_long) { trace::error(tr, "{}/{}: Path too long", parent, ent.name): void; continue; }; const path = path::string(&path); if (fs::isdir(ent.ftype)) { resource_search_ns(pack, out, path, tr, exts...); } else if (fs::isfile(ent.ftype)) { const (_, ns, _, name, ext) = resource_splitpath(path); for (let i = 0z; i < len(exts); i += 1) { if (ext == exts[i]) { const ident = ident_make(ns, name); // yes, this borrow from input is // intentional. needs to be documented // later. append(out, (ident, exts[i])); break; }; }; }; case done => break; }; }; fn wassert_fields(de: *dejson::deser, names: str...) (void | dejson::error) = { dejson::object(de)?; match (dejson::assert_fields(de, names...)) { case let err: dejson::error => defer free(err); trace::warn(&trace::root, "{}", err); case void => void; }; };