krz/nfogen
A scene-style ASCII .nfo generator.
clone: git clone https://gitbay.org/krz/nfogen.git
cc082e51fff8fccc1d9d8f9285a8439edc806733
verified · cmc
author: Christian Cleberg <hello@cleberg.net> · 2026-08-08T06:01:10Z
README.nfo | 6 ++- examples/krz.profile.toml | 82 +++++++++++++++++++++++++++++++++++++ examples/nightfall-demo.cp437.nfo | 83 +++++++++++++++++++++++++++++++++++++ examples/nightfall-demo.nfo | 83 +++++++++++++++++++++++++++++++++++++ examples/nightfall-demo.toml | 31 ++++++++++++++ nfogen.py | 86 +++++++++++++++++++++++++++++++++++++-- nfogen.toml | 6 ++- 7 files changed, 372 insertions(+), 5 deletions(-) @@ -41,7 +41,11 @@ │ LOGO: --logo art.asc places art above the box │ │ BANNERS: --presents --footer scene boilerplate │ │ │ -│ OPTIONS: -o -c -g -s -w --style --layout --logo │ +│ PROFILE: -p group.toml supplies logo, footer, │ +│ members, couriers, boards, affiliates, │ +│ news, outposts. release cfg overrides it. │ +│ │ +│ OPTIONS: -o -c -p -g -s -w --style --layout --logo │ │ --encoding --presents --footer -i --no-input │ │ │ │ Generated by nfogen itself. │ new file mode 100644 @@ -0,0 +1,82 @@ +# A group profile: the reusable KRZ boilerplate. Point any release config at +# it with `profile = "examples/krz.profile.toml"` (or nfogen -p PATH) and it +# supplies the logo, roster, boards, footer and house style. The release +# config only carries per-release fields (title, cracker, notes, ...). + +group = "KRZ" +site = "krz.sh" +style = "double" +layout = "panel" +presents = true +footer = true +logo = "examples/krz-logo.asc" +logo_encoding = "utf8" + +footer_text = """ +SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE +if you enjoyed this release, buy it! +KRZ does not accept cash or hardware of any kind. +""" + +news = """ +Welcome Celestial Wizard back to the group! +We are in need of couriers for the release rush -- especially +those available in the daytime. Contact a senior member. +""" + +members = [ + "Avalon", "Black Spyrit", "Butcher", "The Chairman", "Darwin", + "Doctor No", "Eldar", "Grimstalk", "Hologram", "Intrepid", "Jack", + "Knightmare", "Marko Ramius", "ONYX", "Razor Blade", "Red Baron", + "Savage Steel", "StingRay", "The Unknown", "The Witch King", +] + +[couriers] +Director = ["The Unforgiven"] +Senior = ["Ur Lord", "Lord Tiamat", "Wind Walker", "Bubba"] +Elite = ["Shadow Phantom", "Baddog", "Load Master", "Joebee", "Azmodeus"] +Trial = ["Dead Pool", "Beach Bum", "Black Mage", "Ben Jammin"] + +[[boards]] +name = "Suburbia" +role = "World HQ" +sysop = "The Chairman" +phone = "ITS/PRIVATE" + +[[boards]] +name = "TerrorDome" +role = "Canadian HQ" +sysop = "StingRay" +phone = "ITS/PRIVATE" + +[[boards]] +name = "Kindergarden" +role = "European HQ" +sysop = "Doctor No" +phone = "+47-7-525156" + +[[boards]] +name = "Apocalypse" +role = "Courier HQ" +sysop = "P.O.W." +phone = "ITS/PRIVATE" + +[[affiliates]] +name = "Reign In Blood" +role = "Affiliate" +sysop = "Uriel" +phone = "718/698-1969" + +[[affiliates]] +name = "So-Krate's" +role = "Affiliate" +sysop = "Philosopher" +phone = "310/578-7226" + +[[affiliates]] +name = "The Space Zone" +role = "Affiliate" +sysop = "Death Ray" +phone = "301/416-0118" + +outposts = ["Infinite Darkness", "The Atomic Cafe", "Sanctuary"] new file mode 100644 @@ -0,0 +1,83 @@ + _ _______ ___ + | |/ / _ \ \ / / | + | ' /| |_) \ V /| | k r a z y w a r e z + | . \| _ < | | |_| + |_|\_\_| \_\|_| (_) ------- since 1985 ------- + +ͻ + N I G H T F A L L . P R O T O C O L . V 1 . 4 - K R Z + [ KRZ ] krz.sh + -={ KRZ proudly presents }=- + + Release Information Game Information + + DATE : 2026-08-08 TYPE : GAME + CRACKER : Lime PUBLISHER : Nocturne Interactive + SUPPLIER : The Witch King FORMAT : ISO + patch + PACKAGER : Renegade Chemist DISKS : 3 x CD + PROTECTION : SafeDisc + Serial SIZE : 1.87 GB + INSTALL : unzip, run SETUP, ap.. FILES : 96 x 20 MB + RATING : 9/10 URL : krz.sh/nightfall + + NOTES + + A stealth-action thriller set in a city that never sleeps. + Cracked clean -- no disc check, no online activation. + + Mount or burn the ISO, install, then run the KRZ patch over + the game directory. Serial is in the keygen. Tested working. + + GREETS + + razor-blade jack the-couriers everyone-in-the-scene + + GROUP NEWS + + Welcome Celestial Wizard back to the group! + We are in need of couriers for the release rush -- especially + those available in the daytime. Contact a senior member. + + MEMBERS + + Avalon Black Spyrit Butcher The Chairman Darwin Doctor No + Eldar Grimstalk Hologram Intrepid Jack Knightmare Marko + Ramius ONYX Razor Blade Red Baron Savage Steel StingRay The + Unknown The Witch King + + + COURIERS + + - Director - + The Unforgiven + + - Senior - + Ur Lord Lord Tiamat Wind Walker Bubba + + - Elite - + Shadow Phantom Baddog Load Master Joebee Azmodeus + + - Trial - + Dead Pool Beach Bum Black Mage Ben Jammin + + + BOARDS + + BOARD ROLE SYSOP CONTACT + Suburbia World HQ The Chairman ITS/PRIVATE + TerrorDome Canadian HQ StingRay ITS/PRIVATE + Kindergarden European HQ Doctor No +47-7-525156 + Apocalypse Courier HQ P.O.W. ITS/PRIVATE + + AFFILIATES + + BOARD ROLE SYSOP CONTACT + Reign In Blood Affiliate Uriel 718/698-1969 + So-Krate's Affiliate Philosopher 310/578-7226 + The Space Zone Affiliate Death Ray 301/416-0118 + + + SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE + if you enjoyed this release, buy it! + KRZ does not accept cash or hardware of any kind. + +ͼ new file mode 100644 @@ -0,0 +1,83 @@ + _ _______ ___ + | |/ / _ \ \ / / | + | ' /| |_) \ V /| | k r a z y w a r e z + | . \| _ < | | |_| + |_|\_\_| \_\|_| (_) ------- since 1985 ------- + +╔═════════════════════════════════════════════════════════════════════════════╗ +║ N I G H T F A L L . P R O T O C O L . V 1 . 4 - K R Z ║ +║ [ KRZ ] krz.sh ║ +║ -={ KRZ proudly presents }=- ║ +╠══════════════════════════════════════╦══════════════════════════════════════╣ +║ Release Information ║ Game Information ║ +╠══════════════════════════════════════╬══════════════════════════════════════╣ +║ DATE : 2026-08-08 ║ TYPE : GAME ║ +║ CRACKER : Lime ║ PUBLISHER : Nocturne Interactive ║ +║ SUPPLIER : The Witch King ║ FORMAT : ISO + patch ║ +║ PACKAGER : Renegade Chemist ║ DISKS : 3 x CD ║ +║ PROTECTION : SafeDisc + Serial ║ SIZE : 1.87 GB ║ +║ INSTALL : unzip, run SETUP, ap.. ║ FILES : 96 x 20 MB ║ +║ RATING : 9/10 ║ URL : krz.sh/nightfall ║ +╠══════════════════════════════════════╩══════════════════════════════════════╣ +║ NOTES ║ +║ ║ +║ A stealth-action thriller set in a city that never sleeps. ║ +║ Cracked clean -- no disc check, no online activation. ║ +║ ║ +║ Mount or burn the ISO, install, then run the KRZ patch over ║ +║ the game directory. Serial is in the keygen. Tested working. ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ GREETS ║ +║ ║ +║ razor-blade jack the-couriers everyone-in-the-scene ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ GROUP NEWS ║ +║ ║ +║ Welcome Celestial Wizard back to the group! ║ +║ We are in need of couriers for the release rush -- especially ║ +║ those available in the daytime. Contact a senior member. ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ MEMBERS ║ +║ ║ +║ Avalon Black Spyrit Butcher The Chairman Darwin Doctor No ║ +║ Eldar Grimstalk Hologram Intrepid Jack Knightmare Marko ║ +║ Ramius ONYX Razor Blade Red Baron Savage Steel StingRay The ║ +║ Unknown The Witch King ║ +║ ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ COURIERS ║ +║ ║ +║ - Director - ║ +║ The Unforgiven ║ +║ ║ +║ - Senior - ║ +║ Ur Lord Lord Tiamat Wind Walker Bubba ║ +║ ║ +║ - Elite - ║ +║ Shadow Phantom Baddog Load Master Joebee Azmodeus ║ +║ ║ +║ - Trial - ║ +║ Dead Pool Beach Bum Black Mage Ben Jammin ║ +║ ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ BOARDS ║ +║ ║ +║ BOARD ROLE SYSOP CONTACT ║ +║ Suburbia World HQ The Chairman ITS/PRIVATE ║ +║ TerrorDome Canadian HQ StingRay ITS/PRIVATE ║ +║ Kindergarden European HQ Doctor No +47-7-525156 ║ +║ Apocalypse Courier HQ P.O.W. ITS/PRIVATE ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ AFFILIATES ║ +║ ║ +║ BOARD ROLE SYSOP CONTACT ║ +║ Reign In Blood Affiliate Uriel 718/698-1969 ║ +║ So-Krate's Affiliate Philosopher 310/578-7226 ║ +║ The Space Zone Affiliate Death Ray 301/416-0118 ║ +╠═════════════════════════════════════════════════════════════════════════════╣ +║ ║ +║ SUPPORT THE COMPANIES THAT PRODUCE QUALITY SOFTWARE ║ +║ if you enjoyed this release, buy it! ║ +║ KRZ does not accept cash or hardware of any kind. ║ +║ ║ +╚═════════════════════════════════════════════════════════════════════════════╝ new file mode 100644 @@ -0,0 +1,31 @@ +# A release config. It names a group profile for all the shared boilerplate +# and only sets the fields that change from one release to the next. +profile = "examples/krz.profile.toml" + +title = "Nightfall.Protocol.v1.4-KRZ" +date = "2026-08-08" + +cracker = "Lime" +supplier = "The Witch King" +packager = "Renegade Chemist" +protection = "SafeDisc + Serial" +install = "unzip, run SETUP, apply keygen" +rating = "9/10" + +type = "GAME" +publisher = "Nocturne Interactive" +format = "ISO + patch" +disks = "3 x CD" +size = "1.87 GB" +files = "96 x 20 MB" +url = "krz.sh/nightfall" + +notes = """ +A stealth-action thriller set in a city that never sleeps. +Cracked clean -- no disc check, no online activation. + +Mount or burn the ISO, install, then run the KRZ patch over +the game directory. Serial is in the keygen. Tested working. +""" + +greets = ["razor-blade", "jack", "the-couriers", "everyone-in-the-scene"] @@ -140,10 +140,18 @@ def _split_list(value) -> list[str]: return [p for p in str(value).replace(",", " ").split() if p] +def _col_widths(available: int, fracs: list[float]) -> list[int]: + """Split `available` chars into columns by fraction; last column absorbs + the remainder so the widths always sum exactly.""" + widths = [max(4, int(available * f)) for f in fracs[:-1]] + widths.append(available - sum(widths)) + return widths + + def render(data: dict, group: str, site: str, width: int, style: str = DEFAULT_STYLE, presents: bool = False, footer: str | None = None, layout: str = DEFAULT_LAYOUT, - logo: str | None = None, + logo: str | None = None, roster: dict | None = None, panel_titles: tuple[str, str] = PANEL_TITLES) -> str: s = STYLES[style] h, v = s["h"], s["v"] @@ -243,6 +251,66 @@ def render(data: dict, group: str, site: str, width: int, for wrapped in textwrap.wrap(" ".join(greets), inner - 4): out.append(line(" " + wrapped)) + # group roster sections (from a profile): news, members, couriers, + # boards/affiliates tables, outposts + r = roster or {} + + def section(header: str) -> None: + out.append(sep_before()) + out.append(line(f" {header}")) + out.append(line()) + + def names_block(names: list[str]) -> None: + for wrapped in textwrap.wrap(" ".join(names), inner - 4): + out.append(center(wrapped)) + out.append(line()) + + def table(header: str, entries: list) -> None: + section(header) + if entries and isinstance(entries[0], dict): + ws = _col_widths(inner - 2, [0.34, 0.24, 0.22, 0.20]) + keys = ["name", "role", "sysop", "phone"] + + def row(vals: list[str]) -> str: + cells = "".join(_clip(str(x), w - 1).ljust(w) + for x, w in zip(vals, ws)) + return line(" " + cells) + + out.append(row(["BOARD", "ROLE", "SYSOP", "CONTACT"])) + for e in entries: + out.append(row([e.get(k, "") for k in keys])) + else: + names_block(_split_list(entries)) + + news = str(r.get("news") or "").strip() + if news: + section("GROUP NEWS") + for para in news.splitlines(): + for wrapped in (textwrap.wrap(para, inner - 4) or [""]): + out.append(line(" " + wrapped)) + + members = _split_list(r.get("members") or []) + if members: + section("MEMBERS") + names_block(members) + + couriers = r.get("couriers") or {} + if couriers: + section("COURIERS") + for tier, who in couriers.items(): + out.append(center(f"- {tier} -")) + names_block(_split_list(who)) + + if r.get("boards"): + table("BOARDS", r["boards"]) + if r.get("affiliates"): + table("AFFILIATES", r["affiliates"]) + + outposts = _split_list(r.get("outposts") or []) + if outposts: + section("OUTPOSTS") + names_block(outposts) + # footer disclaimer, centered if footer: out.append(sep_before()) @@ -268,6 +336,8 @@ def build_parser() -> argparse.ArgumentParser: ) p.add_argument("-c", "--config", metavar="PATH", help="config file (.toml or .json) with field defaults") + p.add_argument("-p", "--profile", metavar="PATH", + help="group profile (logo, roster, boards) merged under the config") p.add_argument("-o", "--output", metavar="PATH", help="write to PATH instead of stdout") p.add_argument("-g", "--group", help=f"release group (default {DEFAULT_GROUP})") @@ -300,7 +370,13 @@ def build_parser() -> argparse.ArgumentParser: def main(argv: list[str] | None = None) -> int: args = build_parser().parse_args(argv) - config = load_config(args.config) + release = load_config(args.config) + + # a group profile supplies base values (logo, roster, style, footer, ...); + # the release config overrides it, and CLI flags override both. + profile_path = args.profile or release.get("profile") + profile = load_config(profile_path) if profile_path else {} + config = {**profile, **release} # merge: config defaults, then any CLI flag that was supplied data = {k: config[k] for k, *_ in FIELDS if k in config and config[k] != ""} @@ -338,6 +414,10 @@ def main(argv: list[str] | None = None) -> int: except FileNotFoundError: sys.exit(f"nfogen: logo not found: {logo_path}") + roster = {k: config[k] for k in + ("news", "members", "couriers", "boards", "affiliates", "outposts") + if config.get(k)} + interactive = sys.stdin.isatty() and not args.no_input if args.interactive: prompt_missing(data, force_all=True) @@ -349,7 +429,7 @@ def main(argv: list[str] | None = None) -> int: text = render(data, group=group, site=site, width=width, style=style, presents=presents, footer=footer, layout=layout, - logo=logo, panel_titles=panel_titles) + logo=logo, roster=roster, panel_titles=panel_titles) if args.encoding == "cp437": raw = text.encode("cp437", errors="replace") @@ -40,7 +40,11 @@ ENCODING: utf8 or cp437 (--encoding cp437 = DOS) LOGO: --logo art.asc places art above the box BANNERS: --presents --footer scene boilerplate -OPTIONS: -o -c -g -s -w --style --layout --logo +PROFILE: -p group.toml supplies logo, footer, + members, couriers, boards, affiliates, + news, outposts. release cfg overrides it. + +OPTIONS: -o -c -p -g -s -w --style --layout --logo --encoding --presents --footer -i --no-input Generated by nfogen itself.