show banner and ask for calling again later

This commit is contained in:
Sebastian Woetzel 2023-08-15 21:27:48 +02:00
parent cdaa7ecbaf
commit 17e8e006fd
Signed by: wose
GPG key ID: 6124402A3FAA8631
15 changed files with 1177 additions and 10 deletions

11
.gitignore vendored
View file

@ -1,16 +1,7 @@
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
/target

396
Cargo.lock generated Normal file
View file

@ -0,0 +1,396 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "bitflags"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hashlink"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f"
dependencies = [
"hashbrown",
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys",
]
[[package]]
name = "koko-bbs"
version = "0.1.0"
dependencies = [
"env_logger",
"lazy_static",
"log",
"mio",
"rusqlite",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libsqlite3-sys"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mio"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "regex"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "rusqlite"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustix"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "koko-bbs"
version = "0.1.0"
edition = "2021"
[dependencies]
env_logger = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.19"
mio = { version = "0.8.8", features = ["net", "os-poll"] }
rusqlite = "0.29.0"

304
src/ansithings.org Normal file
View file

@ -0,0 +1,304 @@
#+title: Ansithings
The ANSI-BBS Specification
This "specification" is designed to standardize the meaning of ANSI-BBS in the communications world. It is split into three parts. General definitions are contained in the core document, specifications for the handling of server to client communications and saved ANSI-BBS files are contained in this document, and client to server specicifications are contained in the Client Communications document.
This is not a comprehensive ANSI specification, but a compilation of current common practices.
ANSI-BBS terminal chariteristics:
Output area
Almost all ANSI-BBS terminals are 80 columns wide and usually default to 24 rows high. Is is widely considered acceptable to assume 80 columns, but not considered acceptable to assume 24 rows. Other common hights include 23, 25, 42, and 49 rows.
Font issues
Most english BBS software assumes that IBM CodePage 437 is in use at all times, and extensive use is made of the extended ASCII characters in the 128-255 range. In many cases, if the user asserts that he supports ANSI, that user is assumed to be using this codepage. This is flawed but exceedingly common. In many countries not in North America, the most common codepage will not be IBM CodePage 437, which will cause visual glitches in a significant number of BBS programs.
Also common is the usage of some of the so-called "low ASCII" characters which IBM systems will use when control characters (those from zero to 31) are displayed. Only five are generally interpreted as control characters. Using terminals intended for non-BBS use will often cause glitches and outright malfunctions when used with BBS software. Many of these terminals will not display anything for these characters. Another group will display different glyphs for these. Most will cause exceedingly bad effects when certain control characters are sent to the terminal. The worst control sequences, their effects, and work-arounds are as follows:
SO (0x0e): Shift Out
Effects: Terminals which properly support the Shift Out control character will cause the meanings of all following characters to be changed. This will result in "gibberish" being displayed after this character is sent to the terminal.
Work-around: After sending an SO character, send the "Shift In" character (0x0f) followed by a backspace, a space, then another backspace. The SI character "undoes" what the SO character does. The backspace space backspace sequence erases the SI character on terminals which display it as the "white sun with rays" character.
ENQ (0x05): Enquiry
Effects: Terminals which properly support the Enquiry will send an arbitrary identification string back to the sender. This will be interpreted as characters which were entered by the user. It is quite common to encounter a loop by which one or more of the reply characters trigger another ENQ to be sent.
Work-around: Send a CSI6n after the ENQ then discard all input until the response is recieved.
End of line behaviour (wrapping)
This behaviour is a major weakness in the current ANSI-BBS world. Because there is no officially correct behaviour at the end of a line, many different methods have been implemented by different terminal programs. The end result is that you cannot rely on what happens after a character is placed at the end of a line. In general, people simply avoid using the last column. This isn't necessary however since you can work-around this by using an absolute character positioning after writing to the last column. It is encouraged that programs which create ANSI art handle writes to the last column in this manner. The best method to use is as follows:
At the beginning of a line which contains a character in column 80, add a line feed (0x0a) followed by the ESC [ A sequence to move the cursor back to the previous line. This forces a scroll if the current line is the last one on the screen.
Add an ESC [ s sequence to save the cursor position.
After the character is written to column 80, add an ESC [ u sequence to restore the cursor position
Add a carriage return (0x0d) then line feed (0x0a). The CR/LF combination is used to allow proper line ending detection on Microsoft operating systems.
Attributes
This document will often speak of attributes. An attribute is a complete combination of four different factors which effect visual presentation of a character. These factors are:
Blink Characters blink while displayed. On some terminals, this selects a Bold/Bright background colour. This most often happens with Windows programs that use the Win32 console for output and are running in windowed mode. With these terminals, the text will correctly blink while in full-screen mode.
Bold/Bright Characters are displayed with a more intense foreground colour
Foreground colour The colour the characters themselves are displayed in
Background colour The colour of unset pixels behind a character
Control characters:
While control characters have well defined meanings intended for terminals, BBS tradition has used these to display IBMs "low ascii" glyphs. Because if this historical usage, we must therefore support this if we intend to be compatible with a larger number of door games. In the standards, these are referred to as the C0 control function set.
This is a list of control characters and either their effect or the glyph which they display
NUL (0x00):
No action. The NULL character is silently discarded. Traditionally, the NUL character has been used as a pad character by UNIX systems. Because of this historical usage, this must be retained. DoorWay mode also makes extensive use of the NULL character.
SOL (0x01):
This will display the "while smiling face" (☺) character which is unicode 9786 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
STX (0x02):
This will display the "black smiling face" (☻) character which is unicode 9787 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
ETX (0x03):
This will display the "black heart suit" (♥) character which is unicode 9829 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
EOT (0x04):
This will display the "black diamond suit" (♦) character which is unicode 9830 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
ENQ (0x05):
This will display the "black club suit" (♣) character which is unicode 9827 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
ACK (0x06):
This will display the "black spade suit" (♠) character which is unicode 9824 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
BEL (0x07):
Beep - Causes the terminal to make a beep sound. The duration and frequency of the beep is up to the implementor and a system "beep" should be used when possible. However, as a rough guide, 440Hz, 100ms duration has been used by some terminals.
BS (0x08):
Backspace. Moves cursor position to the previous column unless the current column is the first, in which case no operation is performed. This may or may not be implemented as a "destructive" backspace which will remove the character from that position too and possibly change the current background colour to the current one. Because of this ambuguity, it is strongly suggested that you always use the backspace space backspace sequence. The specification calls for a NON destructive backspace.
TAB (0x09): (Subject to discussion)
Moves to the next tab stop. This may or may not erase characters from the current location to the new one. The specification indicates that the characters should NOT be erased.
LF (0x0a):
Move cursor position to same column of the next row. If current row is the last row, scrolls the screen up and fills the new row with the current attribute.
VT (0x0b):
This will display the "male sign" (♂) character which is unicode 9794 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
FF (0x0c):
Clears screen and homes cursor. Same as CSI2J.
CR (0x0d):
Move cursor position to column 1 of the current line
SO (0x0e):
This will display the "beamed eighth notes" (♫) character which is unicode 9835 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character. Not supported by mTelnet
SI (0x0f):
This will display the "white sun with rays" (☼) character which is unicode 9788 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character. Not supported by mTelnet
DLE (0x10):
This will display the "black right-pointing pointer" (▶) character which is unicode 9654 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
DC1 (0x11):
This will display the "black left-pointing pointer" (◀) character which is unicode 9664 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
DC2 (0x12):
This will display the "up down arrow" (↕) character which is unicode 8597 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
DC3 (0x13):
This will display the "double exclamation mark" (‼) character which is unicode 8252 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
DC4 (0x14):
This will display the "pilcrow sign" (¶) character which is unicode 182 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
NAK (0x15):
This will display the "section sign" (§) character which is unicode 167 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
SYN (0x16):
This will display the "black rectangle" (▬) character which is unicode 9644 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
ETB (0x17):
This will display the "up down arrow with base" (↨) character which is unicode 8616 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
CAN (0x18):
This will display the "upwards arrow" (↑) character which is unicode 8593 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
EM (0x19):
This will display the "downwards arrow" (↓) character which is unicode 8595 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
SUB (0x1a):
This will display the "rightwards arrow" (→) character which is unicode 8594 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
ESC (0x1b): (Subject to discussion)
This chacter is reserved for use to introduce control codes and control characters.
FS (0x1c): (Subject to discussion)
This will display the "turned not sign" (⌙) character which is unicode 8985 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
DISCUSSION:
It has been suggested that the far more common "right angle" (∟) which is unicode 8735 be used here. Apparently, IBM now uses this character in their maps.
GS (0x1d):
This will display the "left right arrow" (↔) character which is unicode 8596 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
RS (0x1e):
This will display the "black up-pointing triangle" (▲) character which is unicode 9650 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
US (0x1f):
This will display the "black down-pointing triangle" (▼) character which is unicode 9660 and occupies this slot on IBMs running codepage 437. This will be treated as a normal character.
Two-byte control functions
Two-byte control function format
Known as the "7-bit C1 control function set" for '@' to '_' and the "Independant control functions" for '`' to '~' in standards, these are two-byte combinations which do not take any parameters. The first byte is always an ESC (0x1b) character. The second byte is from the range '@' (0x40) to '~' (0x7e) inclusive.
Unhandled functions
If a legal function which the terminal does not support is encountered, that function should be silently ignored.
Illegal functions
An illegal function is defined as any ESC character followed by any character which is not in the range of '@' (0x40) to '~' (0x5f) inclusive.
When an illegal function is encountered, the terminal should attempt to make this known to the user in some manner. Commonly all or part of the sequence itself is displayed
Defined Control Functions (in alphabetical order by the second byte)
ESC[
Control sequence Introducer (CSI)
This indicates the start of a control sequence. See the section on control sequences below for more details.
Control Sequences:
Control Sequence Format
Control sequences make up the bulk of the ANSI-BBS spec. And contain four distinct parts which are, in order:
Control Sequence Introducer (CSI)
The two-character CSI consists of the ESC character (0x1b) followed by the '[' character (0x5b)
Parameter Bytes (PBs)
Parameter bytes consist of zero of more of the characters from '0' (0x30) to '?' (0x3f)
Intermediate Bytes (IBs)
Intermediate bytes consist of zero or more of the charaters from ' ' (0x20) to '/' (0x2f)
Final Byte (FB)
The final byte consists of one of the characters from '@' (0x40) to '~' (0x7e)
All control sequences begin with a CSI, are followed by a series of zero or more PBs which are followed by zero or more IBs and terminated with a FB. The IBs and the FB together make a string which defines the Control Function CF.
Parameter Byte Formats
If a parameter byte string consists of only characters from '0' to ';' inclusive ("0123456789:;") then it is interpreted as a list of semi-colon (';') separated numeric values. The meaning of a colon in a value must be defined by the control function. If it is not so defined, anything between a colon and the end of that sub-parameter is ignored (ie: up to the next semi-colon, IB, or FB).
If a parameter byte string begins with one of the chrarcters from '<' to '?' inclusive ("<=>?") then it is part of an extension which will be defined in another document.
If a parameter byte string begins with one of the characters from '0' to ';' inclusive ("01234567890:;") but contains one of the characters from '<' to '?' ("<=>?"), it is deemed to be an illegal sequence and should be handled accordingly.
Unhandled sequences
If a legal sequence which the terminal does not support is encountered, that sequence should be silently ignored. If one item in a parameter list is not handled, only that single unhandled parameter should be ignored. All other parameters from the list should be handled.
Illegal sequences
An illegal sequence is defined as:
A sequence which begins with a CSI but has any IB character before any PB character.
A sequence which begins with a CSI and contains a character which is not a legal PB, IB, or FB
A sequence which begins with a CSI, is followed by a PB from the set of charaters from '0' to ';' inclusive, and contains a PB from the set of characters from '<' to '?' inclusive.
When an illegal sequence is encountered, the terminal should attempt to make this known to the user in some manner. Commonly all or part of the sequence itself is displayed
Defined Control Sequences (in alphabetical order by FB)
The following is a list of commonly defined control sequences for ANSI-BBS terminals. Parameters will be indicated by one of:
p# where # is replaced with a number. This is used to indicate an order-specific parameter. That is to say, p2 *must* be the second parameter. Defaults will be defined for each so-indicated parameter. If this parameter is omitted or empty, the default will be used.
pX indicates that multiple numeric parameters are accepted in any order.
? where '?' is one of the characters from '<' to '?' inclusive. This inducates that this charater MUST be used in this position.
IBs and FBs will be shown Like this.
CSIp1@
Insert Character(s)
Defaults: p1 = 1
Moves text from the current position to the right edge p1 characters to the right, with rightmost charaters going off-screen and the resulting hole being filled with the current attribute.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
Not supported by mTelnet
CSIp1A
Cursor Up
Defaults: p1 = 1
Moves the cursor position up p1 lines from the current position. Attempting to move past the screen boundaries stops the cursor at the screen boundary.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1B
Cursor Down
Defaults: p1 = 1
Moves the cursor position down p1 lines from the current position. Attempting to move past the screen boundaries stops the cursor at the screen boundary.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1C
Cursor Right
Defaults: p1 = 1
Moves the cursor position right p1 columns from the current position. Attempting to move past the screen boundaries stops the cursor at the screen boundary.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1D
Cursor Left
Defaults: p1 = 1
Moves the cursor position left p1 columns from the current position. Attempting to move past the screen boundaries stops the cursor at the screen boundary.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1;p2H
CSIp1;p2f
Cusror Position
Defaults: p1 = 1 p2 = 1
Moves the cursor to the p2th column of the p1th line.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1J (Subject to discussion)
Erase in Page
Defaults: p1 = 0
Erases from the current screen according to the value of p1
Erase from the current position to the end of the screen.
Erase from the current position to the start of the screen.
Erase entire screen. As a violation of ECMA-048, also moves the cursor to position 1/1 as a number of BBS programs assume this behaviour.
Erased characters are set to the current attribute.
DISCUSSION:
Should ESC 2 J violate the spec?
Source: BANSI.TXT
CSIp1K
Erase in Line
Defaults: p1 = 0
Erases from the current line according to the value of p1
Erase from the current position to the end of the line.
Erase from the current position to the start of the line.
Erase entire line.
Erased characters are set to the current attribute.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
mTelnet interprets any sequence ending in 'K' as though p1 is zero
CSIp1L
Insert Line(s)
Defaults: p1 = 1
Inserts p1 lines at the current line position. The current line and those after it are scrolled down and the new empty lines are filled with the current attribute.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1M
Delete Line(s)
Defaults: p1 = 1
Deletes the current line and the p1 - 1 lines after it scrolling the first non-deleted line up to the current line and filling the newly empty lines at the end of the screen with the current attribute. Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1P
Delete Character
Defaults: p1 = 1
Deletes the character at the current position by shifting all characters from the current column + p1 left to the current column. Opened blanks at the end of the line are filled with the current attribute.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
Not supported by mTelnet
CSIp1S
Scroll Up
Defaults: p1 = 1
Scrolls all text on the screen up p1 lines. New lines emptied at the bottom are filled with the current attribute.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
Not supported by mTelnet
CSIp1T
Scroll Down
Defaults: p1 = 1
Scrolls all text on the screen down p1 lines. New lines emptied at the top are filled with the current attribute.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
Not supported by mTelnet
CSIp1X
Erase Character
Defaults: p1 = 1
Erases p1 characters starting at the corrent position. Will not go past the end of the line.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1Z
Cursor Backward Tabulation
Defaults: p1 = 1
Move the cursor to the p1th preceeding tab stop. Will not go past the start of the line.
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
Not supported by mTelnet
CSIpXm
Select Graphic Rendition
Defaults: pX = 0
Sets or clears one or more text attributes. Unlimited parameters are supported and are applied in received order. The following are supoprted:
Modifies
pX Description Blink Bold FG BG
0 Default attribute, white on black X X X X
1 Bright Intensity X
2 Dim Intensity X
5 Blink (slow) X
6 Blink (fast) X
7 Negative Image - Reverses FG and BG X X
8 Concealed characters. Sets the FG to the BG X
22 Normal intensity X
25 Steady (not blinking) X
30 Black foreground X
31 Red foreground X
32 Green foreground X
33 Yellow foreground X
34 Blue foreground X
35 Magenta foreground X
36 Cyan foreground X
37 White foreground X
40 Black background X
41 Red background X
42 Green background X
43 Yellow background X
44 Blue background X
45 Magenta background X
46 Cyan background X
47 White background X
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIp1n
Device Status Report
Defaults: p1 = 0
A request for a status report. ANSI-BBS terminals should handle at least the following values for p1
Request active cursor position
terminal must reply with CSIp1;p2R where p1 is the current line number counting from one, and p2 is the current column.
NOTE: This sequences should not be present in saved ANSI-BBS files
Source: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
CSIs
Save Current Cursor Position
No parameters
Saves the current cursor position for later restoring with CSIu.
Source: DOS ANSI.SYS Documentation
CSIu
Restore Cursor Position
No parameters
Move the cursor to the last saved position. If no position has been saved, the cursor is not moved.
Source: DOS ANSI.SYS Documentation
Please feel free to Comment on this page.

137
src/connection.rs Normal file
View file

@ -0,0 +1,137 @@
use log::{debug, info};
use mio::net::TcpStream;
use mio::event::Event;
use mio::{Interest, Registry};
use std::io::{self, Read, Write};
use crate::state::State;
use crate::system::System;
use crate::user::User;
use crate::utils::{would_block, interrupted};
pub struct Connection<'a> {
pub system: &'a System,
pub ts: TcpStream,
pub user: Option<User>,
pub state: State,
}
impl Connection<'_> {
pub fn handle_event(&mut self, reg: &Registry, event: &Event) -> io::Result<bool> {
if event.is_writable() {
self.handle_write_event(reg, event)?;
}
if event.is_readable() {
self.handle_read_event(reg, event)?;
}
Ok(false)
}
fn set_state(&mut self, s: State) {
debug!("state change {:?} -> {:?}", self.state, s);
self.state = s;
}
fn write(&mut self, buf: &[u8]) -> io::Result<bool> {
loop {
match self.ts.write(&buf) {
// This shouldn't happen since the TCP send buffer is big enough
Ok(n) if n < buf.len() => return Err(io::ErrorKind::WriteZero.into()),
Ok(_) => return Ok(true),
Err(ref err) if would_block(err) => return Ok(false),
Err(ref err) if interrupted(err) => continue,
Err(err) => return Err(err),
}
}
}
fn handle_write_event(&mut self, reg: &Registry, event: &Event) -> io::Result<()> {
// TODO just write everything that changed in the framebuffer
// we update the fb after user input, there is no change until the user
// did something (for now), so we don't need to match on the current state here
// a special case might be after the user logged out, but this could also be
// represented as a new state
// ... actually... this works only after the user has seen the banner since
// it scrolls the screen, so we still need to match the state but only check
// for the Connected and maybe the Logout state, everything else just prints
// updates of the fb
match self.state {
// query term info (size etc.)
State::Connected => {
if self.write(&self.system.theme.banner)? {
self.set_state(State::Banner);
reg.reregister(&mut self.ts, event.token(), Interest::READABLE)?;
}
},
// send banner and wait for keypress
_ => todo!("handle write for all the other states")
};
Ok(())
}
fn read(&self, buf: &mut [u8]) -> io::Result<()> {
Ok(())
}
fn handle_read_event(&mut self, reg: &Registry, event: &Event) -> io::Result<bool> {
let mut received_data = vec![0; 4096];
let mut connection_closed = false;
let mut bytes_read = 0;
loop {
match self.ts.read(&mut received_data[bytes_read..]) {
Ok(0) => {
connection_closed = true;
break;
}
Ok(n) => {
bytes_read += n;
// TODO: we should probably fail when our recv buffer is too
// small and not just increase it indefinitely
if bytes_read == received_data.len() {
received_data.resize(received_data.len() + 1024, 0);
}
}
Err(ref err) if would_block(err) => break,
Err(ref err) if interrupted(err) => continue,
Err(err) => return Err(err),
}
}
if bytes_read != 0 {
let received_data = &received_data[..bytes_read];
debug!("data: {:?}", received_data);
match self.state {
// parse term info
State::Connected => {
},
// one keypress to show the login screen, ignore the rest
State::Banner => {},
_ => {},
};
// if let Ok(str_buf) = std::str::from_utf8(received_data) {
// debug!("Received data: {}", str_buf.trim_end());
// } else {
// debug!("Received (none UTF-8) data: {:?}", received_data);
// }
}
if connection_closed {
info!("Connection closed");
return Ok(true);
}
Ok(false)
}
}

47
src/framebuffer.rs Normal file
View file

@ -0,0 +1,47 @@
enum Colour {
None,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
enum Modifier {
None,
Bright
}
struct Cell {
char: u8,
colour: Option<Colour>,
modifier: Modifier,
changed: bool,
}
impl Default for Cell {
fn default() -> Cell {
Cell {
char: b' ',
colour: None,
modifier: Modifier::None,
changed: true,
}
}
}
impl Cell {
pub fn new(char: u8) -> Self {
Self {
char,
.. Default::default()
}
}
}
pub struct framebuffer {
}

101
src/main.rs Normal file
View file

@ -0,0 +1,101 @@
use log::info;
use mio::net::TcpListener;
use mio::{Events, Interest, Poll, Token};
use std::collections::HashMap;
use std::io;
mod connection;
mod system;
mod state;
mod theme;
mod user;
mod utils;
use self::connection::Connection;
use self::system::System;
use self::state::State;
use self::utils::interrupted;
const SERVER: Token = Token(0);
const LISTEN: &str = "127.0.0.1:9000";
fn main() -> io::Result<()> {
env_logger::init();
let system = System::new("dachgeschoss")?;
let mut poll = Poll::new()?;
let mut events = Events::with_capacity(128);
let addr = LISTEN.parse().unwrap();
let mut server = TcpListener::bind(addr)?;
poll.registry()
.register(&mut server, SERVER, Interest::READABLE)?;
let mut connections = HashMap::new();
let mut unique_token = Token(SERVER.0 + 1);
loop {
if let Err(err) = poll.poll(&mut events, None) {
if interrupted(&err) {
continue;
}
return Err(err);
}
for event in events.iter() {
match event.token() {
SERVER => loop {
let (mut connection, address) = match server.accept() {
Ok((connection, address)) => (connection, address),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
break;
}
Err(e) => {
return Err(e);
}
};
info!("Connection from: {}", address);
let token = next(&mut unique_token);
poll.registry().register(
&mut connection,
token,
Interest::READABLE.add(Interest::WRITABLE),
)?;
connections.insert(
token,
Connection {
system: &system,
ts: connection,
state: State::Connected,
user: None,
},
);
},
token => {
let done = if let Some(connection) = connections.get_mut(&token) {
connection.handle_event(poll.registry(), event)?
} else {
false
};
if done {
if let Some(mut connection) = connections.remove(&token) {
poll.registry().deregister(&mut connection.ts)?;
}
}
}
}
}
}
}
fn next(current: &mut Token) -> Token {
let next = current.0;
current.0 += 1;
Token(next)
}

24
src/state.rs Normal file
View file

@ -0,0 +1,24 @@
#[derive(Debug)]
pub struct LoginState {
name: Option<String>,
password: Option<String>,
}
impl LoginState {
pub fn new() -> Self {
Self {
name: None,
password: None,
}
}
}
#[derive(Debug)]
pub enum State {
Connected,
Banner,
Login(LoginState),
Landing,
Messages,
OneLiners,
}

15
src/system.rs Normal file
View file

@ -0,0 +1,15 @@
use std::io;
use crate::theme::Theme;
pub struct System {
pub theme: Theme,
}
impl System {
pub fn new(t: &str) -> io::Result<Self> {
Ok(Self {
theme: Theme::new(t)?
})
}
}

31
src/theme.rs Normal file
View file

@ -0,0 +1,31 @@
use std::fs;
use std::io;
const GREETINGS: &[u8] = b"Greetings!\r\n\r\nUse a BBS terminal like syncterm \
to connect by\r\ntelnet port 23\r\ndialin number TBD\r\nssh with user bbs and \
password doesntmatter\r\n\r\n";
//const PROMPT_ANY_KEY: &[u8] = b"\r\n<Apply adequate pressure to your favorite key...>";
const PROMPT_ANY_KEY: &[u8] = b"\r\nStill hacking on this thing, come back later...";
pub struct Theme {
pub banner: Vec<u8>,
pub header: Vec<u8>,
pub bye: Vec<u8>,
}
impl Theme {
pub fn new(name: &str) -> io::Result<Self> {
let mut banner = Vec::new();
banner.extend_from_slice(&GREETINGS);
banner.extend(fs::read(format!("themes/{}/camp-logo.ans", name))?);
banner.extend_from_slice(&PROMPT_ANY_KEY);
let bye = fs::read(format!("themes/{}/htp.ans", name))?;
let header = fs::read(format!("themes/{}/header.ans", name))?;
Ok(Self {
banner,
header,
bye,
})
}
}

3
src/user.rs Normal file
View file

@ -0,0 +1,3 @@
pub struct User {
name: String,
}

14
src/utils.rs Normal file
View file

@ -0,0 +1,14 @@
use std::io;
pub fn would_block(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::WouldBlock
}
pub fn interrupted(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::Interrupted
}
pub fn trim_crlf(s: &mut String) {
let len = s.trim_end_matches(&['\r', '\n'][..]).len();
s.truncate(len);
}

View file

@ -0,0 +1,44 @@
original by Veith Y<>er CC-BY-SA 4.0 ワワロロワ
 ワワワ ワワロロロワ ゙ロロロロロロワ
 ワロロロロロワ ロロロロロロロロン ゙ロロロロロロロ ワロロロロワ
 ロロロロロロロロワロロロロロロロロン ゙ロロロロロロロン ロロロロロロロワ
ーワワ ワワワーロロローロロンワワワーロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロン
ーロロン アロロ゚ロロロ ロロンロロロンロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚゚゚゚
 ゚゚゚゚゚゚゚ ゚゚゚゚゚゚゚ ロロロロロロロロン ゙ロロロロロロロン
 ワワワワワワワワワワワワワワワ ロロロロロロロロロ ロロロロロロロンワワワ ワー ワワ ワワ ワワー
 ワワ ワワ アワワ ワワ ロロン ロロワロロロロロロロロロロロロロロロロロン ゙ロロロロロロロイー ロロロロロロロロロロロン アロロ ロロ イロ゚ ロロイ
 ー ー ーー ア ゚゚ ゚ロロロロロロロロロロロロロロロロロロロ ゙ロロロロロロロン ロロロロロロロワーワワー ワワ ワア ワロロ ロロー
 ロロロー ロロ アロー ロロ ロロワロロロロロロロロロロロロロロロロロ゚ ワ ゚ロロロロロ゚ ゚ロロロロ゚ ゚ロロロー ロロ イイーアロ゚ ロロロ
 ワ ーロロロロロロロロロロロロロ゚ ー゚゚゚ ゚゚゚ ワワーワワアイロロ ロロロ
 ロロロローーロロ ロロ ーロア ロロロロロロロロロロロロロン ロ_ _ワ_ ー _ー___ __ーロロロロロロア ゚イ ゚゚ ゚゚ ゚ロア
 ワワワワワワ ワワ ワ_ ーロロロロロロロロロロロロロン ー ロ ア ー ゚゚゚゚ー アー ロロアロロアアロロ ロロイ
 ロロロロロローアロロ ロロ_ ロロロロロロロロロロロロロロ ア_ イ_ ワ_ _アワワワ__ _アロロロロロ゚ ーア ロロロ
 ワワワー ーワワワー ーロロロロロロロロロロロロロ゚ ゚゚゚ー アー ロ ロロ ロロロロロアーロロ ロロロ
 ゚゚゚ ゚゚ロア アロロロロロロロロロロロロ゚ ワワワワワワワロワワワワロロロロワワワワロロロワワワロ
 ロロロロロロロロロ゚ ワロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロ゚ ワロロロロロロロロロロロロロロロロロロロロロロ゚゚゚゚゚゚
 ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ
 ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ
 ワロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロワワワワワワワワワワワワ
 ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚
 ワロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚ロ
 ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚
 ゚゚゚゚゚゚゚゚゚゚゚゚゚ロロロロロロロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロロロロロロロロロロロロ゚
 ロロロロロロロロロロロロロロロロロ ワワワ ワワワワワワ
 ワロロロロロロロロロロロロロロロ゚ ワ゚ ゚ワ ワ゚
 ロロロロロロロロロロロロロロ゚ ゚ワ ワ゚ ゚ワ
 ロロロロロロロロロロロロ゚ ワ゚ ワワワワロワ
 ワロロロロロロロロロロ゚ ワ゚ワ ロ ゚ワ
 ロロロロロロロロロ゚ ワロワワワロワワワ ロ゚゚
 ロロロロロロロロ゚
ロロロロロロ゚
ロロロロ゚
ワロロロ゚
ロロ゚
ロ゚
ン

View file

@ -0,0 +1,6 @@
<31> \ <37> /`<60><><EFBFBD><EFBFBD> \ /<37> ' \ / \ / <31> \ ________
 \______/ <37>' \______/ <37> \<37>_____/ \______/ <31> \/ __ ___\
<31> / <37><6D> \ <31> / <31> \ /<37> \ / \ / ( ) / \
<31><6D>/ <36><6D><EFBFBD> <36><6D><31>\<36><6D>_<36>_<36>/<2F><><EFBFBD><37><35><6D><EFBFBD><37><35><6D><EFBFBD>_<EFBFBD><5F>_<EFBFBD> <35> <35><6D><EFBFBD> <35><6D><EFBFBD>_<EFBFBD><5F><EFBFBD>_/ \_<6D><5F><EFBFBD>__/ / __\ \<37><6D>\ <36> <36> <36>_<6D>/<36> <20><><EFBFBD>\<5C><><EFBFBD> <35><6D> .<35><6D><EFBFBD> <35> <35><6D><EFBFBD> <35> <31> <35><6D><EFBFBD> <35><6D><EFBFBD> \ / \ /\ <20>) /<37><6D> \<36><6D><EFBFBD>_<36><6D><EFBFBD> <36><6D> <36> <20> <35><6D><31>_<35><6D><EFBFBD>/<2F><><31><37><35><6D> <35>\<5C>_<35><6D><31>_<35><6D><31> <35><6D><31> \______/ \ /__\_ <20> /
<31><6D> \ / \ <37> / <31> \ <37> / \________/
<31><6D> \______/ \______/ \____<6D>_/

View file

@ -0,0 +1,43 @@

ワワワワワワロロロロロロロワワワワ
ワロロロロロロロロロロロロロロロロロロロロロロロワワ
゚゚ロロロロロロロロロロロロロロロロロロロロロロロロロロロロワ
ロロワワワ ゚゚ロロロロロロロロロロロロロロロロロロロロロロロロワ
ロロロロロロロロロワワワ ゚゚ロロロロロロロロロロロロロロロロロロロロロワ
ロロロロロロロロロロロロロロロロロワワ ゚゚゚゚゚ロロロロロロロロロロワ
ロロロロロロロロロロ゚ ゚ロロロロロロロ ゚゚ロロロロロロロ
ロロロロロロロロロロ ロロロロロロ ゚ロロロロロ
ロロロロロロロロロロ ロロロロ ゚ロロロロ
ロロロロロロロロロロロ ロロロロロ ゚゚゚
ロロロロロロロロロロロロ ロロロロロ ワロワワ
ロロロロロロロロロロロロ ロロロロロロ ワロロロロロロワ
ロロロロロロロロロロロロロロ ロロロロロロロロワ ワロロロロロロロロロ
ロロロロロロロロロロロロロロロワ ワロロロロロロロロロロロロワワ ワワワワロロロロロロロロロロロ
ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ
ロロロロロロロロ゚ ゚ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ ゚ロロロロロロロロ
ロロロロロロロロ ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ ロロロロロロロロ
゚ロロロロロロロワ ゚ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚ ワロロロロロロロ゚
ロロロロロロロロ ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ ロロロロロロロロ
゚ロロロロロロロワ ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ ロロロロロロロロ゚
ロロロロロロロロワ ゚ロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚ ロロロロロロロロロ
ロロロロロロロロロワ ゚ロロロロロロロロロロロロロロロロロロロロロロロロ゚ ワロロロロロロロロロ
゚ロロロロロロロロワ ゚ロロロロロロロロロロロロロロロロロロロ゚゚ ワロロロロロロロロロロ
゚ロロロロロロロロロロワ ゚゚゚ロロロロロロロロ゚゚゚ ワロロロロロロロロロロ゚
゚ロロロロロロロロロロロワワ ワワロロロロロロロロロロロ゚
゚ロロロロロロロロロロロロロロワワワワワワワワワロロロロロロロロロロロロロロロ゚
゚ロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロロ゚゚
゚゚ロロロロロロロロロロロロロロロロロロロロロロロロロロ゚゚
゚゚゚ロロロロロロロロロロロロロロロ゚゚゚
゚゚゚゚゚゚゚゚゚
ロロ ロロ ロロロロロロロロロ ロロロロロロロロ ロロ ワロロ゚ ロロロロロロロロ ロロ ロロ ロロロロロロロロ
ワワ ワワ ワワ ワワ ワワ ワワ ゚゚゚ ワワ ワワ ワワ ワワ
ロロ ロロロロロロ ロロ ロロロロロロ ロロ ロロ ロロロロワ ロロ ロロ ロロロロロロ ロロロロロロロロ
ロロ ロロ ロロ ロロ ロロワワワワワワ ロロ ゚ロロワ ロロ ロロ ロロ ロロワワワワワワ
゚゚ ゚゚ ゚゚ ゚゚ ゚゚゚゚゚゚゚゚ ゚゚ ゚゚ ゚゚ ゚゚ ゚゚ ゚゚゚゚゚゚゚゚
ロロロロロロロロロ ロロ ロロロロロロロロロ ロロロロロロロロ ロロロロロロロロ ロロロロロロロロ
ワワ ワワ ワワ ワワ ワワ ワワ ワワ ワワ ワワ
ロロ ロロロロロロ ロロ ロロ ロロロロロロ ロロ ロロ ロロロロロロロロ ロロ
ロロ ロロワワワワワワ ロロ ロロ ロロ ロロ ロロワワワワワワ ロロ
゚゚ ゚゚゚゚゚゚゚゚ ゚゚ ゚゚ ゚゚ ゚゚ ゚゚゚゚゚゚゚゚ ゚゚