Kjetil's Information Center: A Blog About My Projects

lazyboNES Emulator Telnet Hacks

Just for fun I made some special modifications to the lazyboNES emulator to make it more usable over a Telnet connection from DOS in text mode! The changes are incorporated in the new version 0.7 that can be downloaded here or from the Git repositories. The "SPECIAL_TERMINAL" macro must be defined during compilation and a modified Telnet client is required to make use of the features.

The first feature is that the emulator now accepts individual key press and key release events, which makes the game more playable. Normally a terminal will only send key presses, so in the regular mode lazyboNES will release the key on a timer.

The second feature is sound support, which is transmitted as custom ANSI escape codes to be interpreted by the Telnet client. Only the frequency from the second APU square wave generator is sent, which is the best choice to produces the distinct Super Mario Bros music.

A third feature, also present in the regular mode, is automatic cropping of the display if the height is less than 30 rows on the terminal. This makes SMB playable also on more standard 80x24 or 80x25 text resolutions.

I have tested with a modified version of the mTCP Telnet client for DOS. Hijacking the keyboard interrupt makes it possible to send key release events and the sound is sent to the PC speaker.

Changes based on "mTCP-src_2023-03-31.zip":

diff -ur mtcp-src_2023-03-31_orig/apps/telnet/keys.cpp mtcp-src_2023-03-31/apps/telnet/keys.cpp
--- mtcp-src_2023-03-31_orig/apps/telnet/keys.cpp       2023-03-31 09:00:00.000000000 +0200
+++ mtcp-src_2023-03-31/apps/telnet/keys.cpp    2024-08-26 18:21:27.207217804 +0200
@@ -31,6 +31,8 @@
 
 
 #include <stdio.h>
+#include <dos.h>
+#include <conio.h>
 
 #include "inlines.h"
 #include "keys.h"
@@ -49,6 +51,9 @@
 
 #endif
 
+static void (__interrupt __far *prev_int_9)();
+static uint8_t releaseKey = 0;
+static bool releaseKeyEnabled = false;
 
 Key_t getKey( void ) {
 
@@ -159,4 +164,84 @@
 
   return rc;
 }
+
+void __interrupt __far keyboardIsr ( void )
+{
+  unsigned code = inp(0x60);
+  switch (code) {
+  case 0x91:
+    releaseKey = 'W';
+    break;
+  case 0x9E:
+    releaseKey = 'A';
+    break;
+  case 0x9F:
+    releaseKey = 'S';
+    break;
+  case 0xA0:
+    releaseKey = 'D';
+    break;
+  case 0xA3:
+    releaseKey = 'H';
+    break;
+  case 0xA4:
+    releaseKey = 'J';
+    break;
+  case 0xA5:
+    releaseKey = 'K';
+    break;
+  case 0xA6:
+    releaseKey = 'L';
+    break;
+  case 0xD2:
+    releaseKey = '=';
+    break;
+  case 0xD3:
+    releaseKey = ';';
+    break;
+  case 0x43: /* F9 */
+    releaseKeyEnabled = true;
+    releaseKey = 0; /* Clear possible pending key. */
+    break;
+  case 0x44: /* F10 */
+    releaseKeyEnabled = false;
+    break;
+  default:
+    releaseKey = 0;
+    break;
+  }
+  _chain_intr( prev_int_9 );
+}
+
+void keysInit ( void )
+{
+  prev_int_9 = _dos_getvect( 0x9 );
+  _dos_setvect( 0x9, keyboardIsr );
+}
+
+void keysExit ( void )
+{
+  _dos_setvect( 0x9, prev_int_9 );
+}
+
+bool releaseKeyReady ( void )
+{
+  if (releaseKeyEnabled && releaseKey > 0) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+Key_t getReleaseKey ( void )
+{
+  Key_t rc;
+
+  rc.action = K_NormalKey;
+  rc.local = 0;
+  rc.normalKey = releaseKey;
+  releaseKey = 0;
+
+  return rc;
+}
 
diff -ur mtcp-src_2023-03-31_orig/apps/telnet/keys.h mtcp-src_2023-03-31/apps/telnet/keys.h
--- mtcp-src_2023-03-31_orig/apps/telnet/keys.h 2023-03-31 09:00:00.000000000 +0200
+++ mtcp-src_2023-03-31/apps/telnet/keys.h      2024-08-26 18:21:27.207217804 +0200
@@ -84,6 +84,10 @@
 
 
 Key_t getKey( void );
+void keysInit( void );
+void keysExit( void );
+bool releaseKeyReady ( void );
+Key_t getReleaseKey ( void );
 
 
 
diff -ur mtcp-src_2023-03-31_orig/apps/telnet/telnet.cpp mtcp-src_2023-03-31/apps/telnet/telnet.cpp
--- mtcp-src_2023-03-31_orig/apps/telnet/telnet.cpp     2023-03-31 09:00:00.000000000 +0200
+++ mtcp-src_2023-03-31/apps/telnet/telnet.cpp  2024-08-26 18:21:27.220217805 +0200
@@ -405,6 +405,7 @@
     Strict_DOS_Mode = 1;
   }
   
+  keysInit();
 
 
 
@@ -605,9 +606,17 @@
 
     // If a keystroke is waiting process it
 
+    bool process = false;
+    Key_t key;
     if ( biosIsKeyReady( ) ) {
+      key = getKey( );
+      process = true;
+    } else if ( releaseKeyReady( ) ) {
+      key = getReleaseKey( );
+      process = true;
+    }
 
-      Key_t key = getKey( );
+    if ( process ) {
 
       if ( key.action != K_NoKey ) {
 
@@ -715,6 +724,7 @@
 
   TcpSocketMgr::freeSocket( mySocket );
 
+  keysExit();
   shutdown( 0 );
 
   return 0;
@@ -3096,6 +3106,17 @@
 
     }
 
+    // Advanced Beep
+
+    case 'x': {
+      if ( parms[0] == CSI_DEFAULT_ARG ) {
+        nosound( );
+      } else {
+        sound( parms[0] );
+      }
+      break;
+    }
+
     // case 'h': ANSI set options - not implemented
     // case 'l': ANSI reset options - not implemented
 
          


For a demonstration check this video.

Topic: Open Source, by Kjetil @ 30/08-2024, Article Link