Skip to content

What's New in 1.6.0

Agent 57951 edited this page Mar 15, 2026 · 1 revision

What's New in QWK.NET 1.6.0

Released 15 March 2026 · NuGet

This release extends the API in three areas: access to non-standard archive files, a complete kludge extraction model, and a richer DOOR.ID capability surface. The diagnostics tool surfaces all three in its output formats.


Non-Standard Archive Files

QWK.NET recognises as standard MESSAGES.DAT, CONTROL.DAT, DOOR.ID, TOREADER.EXT, TODOOR.EXT, WELCOME, NEWS, and GOODBYE; BBS software has historically bundled additional vendor-specific files alongside these. Two new members in QwkPacket expose them.

QwkPacket.UnknownFiles

IReadOnlyList<string> UnknownFiles { get; }

Populated during Open(). Contains the name of every archive entry not in the standard set (comparison is case-insensitive). Empty when all files are known.

using QwkPacket packet = QwkPacket.Open("DEMO1.QWK");

foreach (string fileName in packet.UnknownFiles)
{
  Console.WriteLine($"Non-standard file: {fileName}");
}

QwkPacket.OpenFile(string name)

Stream? OpenFile(string name)

Opens a raw, caller-owned byte stream for any archive entry by name (case-insensitive). Returns null when the file is not present. Works for any archive entry, not only those listed in UnknownFiles.

Argument Behaviour
name is null throws ArgumentNullException
File not found returns null
File found returns readable Stream; caller must dispose
// Read a non-standard file
using Stream? data = packet.OpenFile("BBSINFO.TXT");
if (data != null)
{
  using var reader = new StreamReader(data, Encoding.ASCII);
  Console.WriteLine(reader.ReadToEnd());
}

// Also works for standard files
using Stream? ctrl = packet.OpenFile("CONTROL.DAT");

Kludge Extraction — Complete Three-Convention Model

QWK packets carry machine-readable kludge lines at the top of message bodies, before the human-readable content. Prior to 1.6.0 the library recognised two conventions; 1.6.0 adds the third.

Three Recognised Conventions

Convention Trigger Key stored
QWKE extended headers Key before : is To, From, or Subject (case-insensitive) To, From, or Subject
@-kludge Line begins @identifier: Bare identifier (e.g. MSGID)
Ctrl-A kludge (new) First character is U+0001 (SOH) or U+263A (CP437 glyph for byte 0x01) Token before the first space or colon

Scanning stops at the first blank line or at any line that does not match one of these three forms.

Unified Key Lookup

The prefix character is stripped from the stored key in all three cases. A caller checking kludge.Key == "MSGID" finds the entry regardless of whether it arrived as a Ctrl-A kludge, an @-kludge, or a QWKE header. The original line text is preserved in kludge.RawLine for byte-level fidelity.

foreach (Message message in packet.Messages)
{
  foreach (var kludge in message.Kludges)
  {
    // Key never contains a leading @ or SOH character
    Console.WriteLine($"{kludge.Key} = {kludge.Value}");
  }
}

Breaking Change — @-kludge Key No Longer Includes @

Previously the @ sigil was part of the stored key (e.g. kludge.Key == "@MSGID"). It is now stripped (e.g. kludge.Key == "MSGID").

Migration: remove the leading @ from any string literal used in a kludge key comparison.

// Before 1.6.0
if (kludge.Key == "@MSGID") { ... }

// 1.6.0 and later
if (kludge.Key == "MSGID") { ... }

DOOR.ID — Extended Capability Support

12 New DoorCapability Members

The following CONTROLTYPE values were previously mapped to Unknown. They are now fully enumerated:

Member DOOR.ID CONTROLTYPE value Meaning
ResetAll RESETALL Reset all last-read pointers
Yours YOURS Retrieve messages addressed to current user
Mail MAIL Retrieve personal mail
DeleteMail DELMAIL Delete personal mail
Attach ATTACH File attachments
Own OWN Mark messages as owned
FileRequest FREQ FidoNet-style file requests
Index NDX NDX index files produced
TimeZone TZ Time-zone information in headers
Via VIA VIA routing path in headers
MessageId MSGID MSGID kludge lines
Control CONTROL Extended CONTROL kludge lines

The full DoorCapability enum now covers 20 values (Unknown maps any value not in the table above).

DoorId.ControlTypes

IReadOnlyList<string> ControlTypes { get; }

Raw CONTROLTYPE line values in document order, preserving original casing. Useful when a capability maps to Unknown and you need the original string for round-trip fidelity or logging.

if (packet.DoorId != null)
{
  // Typed capabilities
  foreach (DoorCapability cap in packet.DoorId.Capabilities)
  {
    Console.WriteLine(cap); // e.g. Add, Drop, ResetAll
  }

  // Raw values (includes non-standard ones)
  foreach (string raw in packet.DoorId.ControlTypes)
  {
    Console.WriteLine(raw); // e.g. "ADD", "CUSTOMEXT"
  }
}

Diagnostics Tool Updates

All three output formats reflect the new data.

Capabilities

Text — a Capabilities: line appears in the BBS INFORMATION section when at least one CONTROLTYPE is present:

  Door ID:       MailDoor 1.50
  Door System:   PCBoard 15.1
  Capabilities:  Add, Drop, Reset, ResetAll, Yours, Mail

Markdown — a **Door Capabilities:** bullet after the Door ID line.

JSON — a doorCapabilities string array inside the top-level object (omitted when empty):

"doorCapabilities": ["Add", "Drop", "Reset", "ResetAll", "Yours", "Mail"]

Additional Files

Text — an ADDITIONAL FILES: section after OPTIONAL FILES: (omitted when no non-standard files are present):

ADDITIONAL FILES:
  - BBSINFO.TXT
  - CUSTOM.DAT

Markdown — an ## Additional Files section after ## Optional Files.

JSON — an unknownFiles string array (always present when parse succeeds; empty array when no non-standard files exist):

"unknownFiles": ["BBSINFO.TXT", "CUSTOM.DAT"]

Upgrading from 1.5.0

  1. @-kludge key comparisons — remove the leading @ from any string literal compared against kludge.Key. This is the only breaking change.
  2. No other API signatures changed. New members (UnknownFiles, OpenFile, ControlTypes) and new enum values are purely additive.

Clone this wiki locally