Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dancing Dolls Problem : on GCC linux

As a preface, I really do not want the exact solution to my problem, just guidance. I don't want you to give me the code. This isn't homework, it's just an exercise I'm trying to solve.

I just want you to tell me how to access VDU and directly change character on same screen.

The screen is divided into 25 rows and 80 columns. The characters that are displayed on the screen are stored in a special memory called VDU memory (not to be confused with ordinary memory). Each character displayed on the screen occupies two bytes in VDU memory.

The first of these bytes contains the ASCII value of the character being displayed, whereas, the second byte contains the colour in which the character is displayed. For example, the ASCII value of the character present on zeroth row and zeroth column on the screen is stored at location number 0xB8000000.

Therefore the colour of this character would be present at location number 0xB8000000 + 1. Similarly ASCII value of character in row 0, col 1 will be at location 0xB8000000 + 2, and its colour at 0xB8000000 + 3.

My task:

With this knowledge write a program which when executed would keep converting every capital letter on the screen to small case letter and every small case letter to capital letter. The procedure should stop the moment the user hits a key from the keyboard. This is an activity of a rampant Virus called Dancing Dolls. (For monochrome adapter, use 0xB0000000 instead of 0xB8000000).

Really I don't have any idea to build this code. I'm stuck at even getting started.

like image 231
Udit Gupta Avatar asked Aug 20 '11 19:08

Udit Gupta


2 Answers

You are referring to what was once called the video refresh buffer. It's important to state that Dancing Dolls was a virus for DOS.

Basically, you are expecting video memory to be at address 0xB8000000 on your program's memory. However, modern Operating Systems (like Linux/Windows/Mac OS X) provide a virtual memory mechanism that prevents applications from manipulating devices directly. So the 0xB8000000 address your application sees is not the physical address 0xb8000000 that corresponds to the video refresh buffer. The last post of this thread also has some interesting info on this subject.

Nevertheless, the technique you are interested in is still valid for 16-bit DOS and is covered on the book Assembly Language Step-by-step: Programming with DOS and Linux. This book has a great section on chapter 6 that explains how this work exactly. The section is named Inspecting the Video Refresh Buffer with DEBUG, and has an interesting example that shows how to use debug.exe to access the video memory and modify it. I tested it successfully on cmd.exe of my Win 7 box.

But if you want to manipulate the screen of a Linux terminal, check ncurses:

It is a library of functions that manage an application's display on character-cell terminals

like image 116
karlphillip Avatar answered Sep 29 '22 11:09

karlphillip


I think, most important is to be aware that your question is about an exercise that (apparently) at one time was designed to be easy and interesting.

However, that was long ago, and meanwhile both hardware and software has progressed. It is no longer easy to access physical memory. And, at least for the beginner, it is no longer interesting to do that.

Thus, the main goodness points of the exercise have been chopped off by time’s inexorable entropic action.

However, for one who absolutely want to do this exercise, that once-so-easily-accessible hardware can be simulated. Doing such a simulation transparently, so that the student code is the same as if it was for real, is rather difficult and system-dependent. However, if you (the student) just agree to explicitly call some “update” routine in order to simulate the hardware screen update, then it’s more near managable – and thus, old exercises can be reused! :-)

One can then imagine that main does some initialization of things, and calls a routine doTheDancingDolls with arguments that give access to simulated video memory, and the aforementioned “update” functionality:

void doTheDancingDolls(
    curses::Console&            console,
    b8000::DisplayMemorySim&    memorySim
    )
{
    do
    {
        // Whatever - this is where you Do Things to the simulated video memory.

        // Then:
        memorySim.update( console );
    } while( !console.keyboard().keypressIsAvailable() );

    console.keyboard().getKey();    // Consume the keypress.
}

… where the memorySim object somehow provides the necessary pointer to the simulated video memory – a pointer that corresponds to the B8000000 address in the exercise.

That’s the main idea, how to get a starting point for solving this exercise that's designed for now ANTIQUATED equipment.

Fleshing it out a bit more, here’s one possible implementation of the video memory simulation (note that this is not code that addresses the things that you were meant to do in the exercise, it just addresses the things that you were never meant to do, but possibly would have to today):

File [b8000.h]
#ifndef B8000_H
#define B8000_H
// A simulation of the early PC's 25*80 color text mode video memory.
// The structure of the memory is INTENTIONALLY not modelled in the
// exposed interface. A student can learn from imposing such structure.
//
// Copyright (c) 2011 Alf P. Steinbach.


//------------------------------------------ Dependencies:

#include "curses_plus_plus.h"       // curses::Console
#include <vector>                   // std::vector


//------------------------------------------ Interface:

namespace b8000 {
    typedef unsigned char   Byte;

    class DisplayMemorySim
    {
    private:
        std::vector< Byte >     buf_;

    public:
        enum { nColumns = 80, nLines = 25 };

        DisplayMemorySim(): buf_( 2*nLines*nColumns ) {}

        void* bufferPointer()               { return &buf_[0]; }
        void const* bufferPointer() const   { return &buf_[0]; }

        void update( curses::Console& console ) const
        {
            assert( console.nLines() >= nLines );
            assert( console.nColumns() >= nColumns );
            assert( console.colors().nColors() >= 16 );

            for( int y = 0;  y < nLines;  ++y )
            {
                for( int x = 0;  x < nColumns;  ++x )
                {
                    int const   iCell       = 2*(y*nColumns + x);
                    Byte const  charCode    = buf_[iCell];
                    Byte const  colors      = buf_[iCell + 1];
                    Byte const  fg          = colors & 0xF;
                    Byte const  bg          = colors >> 4;

                    console.colors().setFgBg( fg, bg );
                    console.putAt( x, y, charCode );
                }
            }
            console.updateScreen();
        }
    };
}    // namespace b8000

#endif

And the curses::Console class might be a simple wrapper over the curses library, e.g. like …

File [curses_plus_plus.h]
#ifndef CURSES_PLUS_PLUS_H
#define CURSES_PLUS_PLUS_H
// Sort of minimal C++ interface to the "curses" library.
// Copyright (c) 2011 Alf P. Steinbach.


//------------------------------------------ Dependencies:

#include "curses.h"
#if defined( _MSC_VER ) && defined( __PDCURSES__ )
#   pragma comment( lib, "pdcurses.lib" )
#endif

#ifdef  _MSC_VER
#   pragma warning( disable: 4355 ) // 'this' used in member initializer
#endif 

#include <assert.h>         // assert
#include <stddef.h>         // NULL


//------------------------------------------ Interface:

namespace curses {
    class Console;

    namespace detail {
        template< class Number > inline Number square( Number x ) { return x*x; }

        template< class Derived >
        struct SingleInstance
        {
            static int& instanceCount()
            {
                static int n    = 0;
                return n;
            }

            SingleInstance( SingleInstance const& );            // No such.
            SingleInstance& operator=( SingleInstance const& ); // No such.

            SingleInstance()
            {
                ++instanceCount();
                assert(( "can only have one instance at a time", (instanceCount() == 1) ));
            }

            ~SingleInstance() { --instanceCount(); }
        };
    }  // namespace detail

    namespace color {
        #ifdef  _WIN32      // Any Windows, 32-bit or 64-bit.
            int const   lightness   = 0x08;         //  Windows only.      8

            // The portable colors, expressed for Windows systems.
            int const   black           = COLOR_BLACK;                  // 0
            int const   blue            = COLOR_BLUE;                   // 1
            int const   green           = COLOR_GREEN;                  // 2
            int const   cyan            = COLOR_CYAN;                   // 3
            int const   red             = COLOR_RED;                    // 4
            int const   magenta         = COLOR_MAGENTA;                // 5
            int const   yellow          = COLOR_YELLOW | lightness;     // 6 + 8
            int const   white           = COLOR_WHITE | lightness;      // 7 + 8

            // Windows-specific colors.
            int const   gray            = COLOR_BLACK | lightness;
            int const   lightBlue       = COLOR_BLUE | lightness;
            int const   lightGreen      = COLOR_GREEN | lightness;
            int const   lightCyan       = COLOR_CYAN | lightness;
            int const   lightRed        = COLOR_RED | lightness;
            int const   lightMagenta    = COLOR_MAGENTA | lightness;
            int const   brown           = COLOR_YELLOW & ~lightness;    // A bit greenish.
            int const   lightGray       = COLOR_WHITE & ~lightness;
        #else
            // The portable colors, expressed for non-Windows systems.
            int const   black           = COLOR_BLACK;
            int const   blue            = COLOR_BLUE;
            int const   green           = COLOR_GREEN;
            int const   cyan            = COLOR_CYAN;
            int const   red             = COLOR_RED;
            int const   magenta         = COLOR_MAGENTA;
            int const   yellow          = COLOR_YELLOW;
            int const   white           = COLOR_WHITE;
        #endif
    }    // namespace color

    class Colors
        : private detail::SingleInstance< Colors >
    {
    private:
        Colors( Colors const& );                // No such.
        Colors& operator=( Colors const& );     // No such.

        int     n_;
        int     nPairs_;
        int     rawIndexOfPair0_;

        int rawIndexFor( int fg, int bg ) const
        {
            return bg*n_ + fg;
        }

    public:
        int nColors() const         { return n_; }
        int nColorPairs() const     { return nPairs_; }

        int indexFor( int fg, int bg ) const
        {
            int const   rawIndex    = rawIndexFor( fg, bg );

            return 0?0
                : (rawIndex == rawIndexOfPair0_)?   0
                : (rawIndex == 0)?                  rawIndexOfPair0_
                :   rawIndex;
        }

        void setColorPair( int i )
        {
            ::color_set( short( i ), NULL );         //attrset( COLOR_PAIR( i ) );
        }

        void setFgBg( int fg, int bg )
        {
            setColorPair( indexFor( fg, bg ) );
        }

        Colors( Console& )
        {
            ::start_color();    // Initialize color support.

            // Although these look like global constants, they're global variables
            // that are initialized by the call to Curses' `start_color` (above).
            n_ = ::COLORS;  nPairs_ = ::COLOR_PAIRS;
            assert( detail::square( n_ ) <= nPairs_ );   // Our requirement.

            // Obtain curses' default colors, those are at color pair index 0.
            {
                short   fg0  = -1;
                short   bg0  = -1;

                ::pair_content( 0, &fg0, &bg0 );
                assert( fg0 != -1 );
                assert( bg0 != -1 );
                rawIndexOfPair0_ = rawIndexFor( fg0, bg0 );
            }

            // Initialize color pair table to support finding index for given colors.
            // The color pair at index 0 can't be specified (according to docs),
            // so trickery is required. Here like swapping index 0 to elsewhere.
            for( int fg = 0;  fg < n_;  ++fg )
            {
                for( int bg = 0;  bg < n_;  ++bg )
                {
                    int const i = indexFor( fg, bg );

                    if( i == 0 ) { continue; }      // It's initialized already.
                    ::init_pair( short( i ), short( fg ), short( bg ) );
                }
            }
        }
    };

    class Keyboard
        : private detail::SingleInstance< Keyboard >
    {
    private:
        WINDOW*     pCursesWin_;
        bool        isBlocking_;
        int         bufferedKeypress_;
        bool        hasBufferedKeypress_;

        void setBlocking( bool desiredBlocking )
        {
            if( isBlocking_ != desiredBlocking )
            {
                ::nodelay( pCursesWin_, !desiredBlocking );
                isBlocking_ = desiredBlocking;
            }
        }

    public:
        int getKey()
        {
            if( hasBufferedKeypress_ )
            {
                hasBufferedKeypress_ = false;
                return bufferedKeypress_;
            }

            setBlocking( true );
            return ::getch();
        }

        bool keypressIsAvailable()
        {
            if( hasBufferedKeypress_ )  { return true; }

            setBlocking( false );
            int const key = ::getch();
            if( key == ERR )            { return false; }

            hasBufferedKeypress_ = true;
            bufferedKeypress_ = key;
            return true;
        }

        Keyboard( WINDOW& pCursesWin )
            : pCursesWin_( &pCursesWin )
            , isBlocking_( true )
            , hasBufferedKeypress_( false )
        {
            ::keypad( pCursesWin_, true );  // Assemble multi-character sequences into key symbols.
            ::nodelay( pCursesWin_, false );
            assert( isBlocking_ == true );
        }

        ~Keyboard()
        {
            setBlocking( true );
        }
    };

    class Console
        : private detail::SingleInstance< Console >
    {
    private:
        ::WINDOW*   pCursesWin_;
        Colors      colors_;
        Keyboard    keyboard_;

        Console( Console const& );                    // No such.
        Console& operator=( Console const& );         // No such.

        static ::WINDOW* beginWin()
        {
            return ::initscr();
        }

    public:
        // At least with pdcurses in Windows, these numbers are for the
        // console window, i.e. not for its underlying text buffer.
        int nColumns() const    { return ::getmaxx( pCursesWin_ ); }
        int nLines() const      { return ::getmaxy( pCursesWin_ ); }

        Colors& colors() { return colors_; }
        Colors const& colors() const { return colors_; }

        Keyboard& keyboard() { return keyboard_; }
        Keyboard const& keyboard() const { return keyboard_; }

        void putAt( int x, int y, char const s[] )
        {
            ::mvaddstr( y, x, s );
        }

        void putAt( int x, int y, char c )
        {
            ::mvaddch( y, x, c );
        }

        void updateScreen() { ::refresh(); }

        Console()
            : pCursesWin_( beginWin() )
            , colors_( *this )
            , keyboard_( *pCursesWin_ )
        {
            ::cbreak();         // Immediate input (no line buffering).
            ::noecho();         // No input echo.
        }

        ~Console()
        {
            ::endwin();
        }       
    };
}    // namespace curses

#endif

To drive it all, in the main program you’d check whether the terminal window (Windows “ console window”) is sufficiently large, etc.:

File [main.cpp]
#include "string_util.h"            // strUtil::S
#include "b8000.h"                  // b8000::DisplayMemorySim
#include "curses_plus_plus.h"       // curses::Console

#include <algorithm>                // std::max
#include <assert.h>                 // assert
#include <iostream>                 // std::cerr, std::endl
#include <stdexcept>                // std::runtime_error, std::exception
#include <stdlib.h>                 // EXIT_SUCCESS, EXIT_FAILURE



void doTheDancingDolls(
    curses::Console&            console,
    b8000::DisplayMemorySim&    memorySim
    )
{
    do
    {
        // Whatever - this is where you Do Things to the simulated video memory.
        // The following three lines are just to see that something's going on here.
        using stringUtil::S;
        static int x = 0;
        console.putAt( 30, 5, S() << "I have now counted to " << ++x << "..." );

        // Then:
        //memorySim.update( console );
    } while( !console.keyboard().keypressIsAvailable() );

    console.keyboard().getKey();    // Consume the keypress.
}



bool throwX( std::string const& s ) { throw std::runtime_error( s ); }

void cppMain()
{
    using std::max;
    using stringUtil::S;

    curses::Console         console;
    enum
    {
        w = b8000::DisplayMemorySim::nColumns,
        h = b8000::DisplayMemorySim::nLines
    };

    (console.colors().nColors() >= 16)
        || throwX( "The console window doesn't support 16 colors." );
    (console.nColumns() >= w)
        || throwX( S() << "The console window has less than " << w << " columns." );
    (console.nLines() >= h)
        || throwX( S()  << "The console window has less than " << h << " lines." );

    namespace color = curses::color;
    console.colors().setFgBg( color::lightRed, color::yellow );
    console.putAt( 30, 0,        "  The Dancing Dolls!  " );
    console.putAt( 30, 1, S() << "  In " << h << "x" << w << " color mode." );
    console.putAt( 30, 3, S() << "  Press ANY key to start...  " );
    console.keyboard().getKey();
    console.putAt( 30, 3, S() << "  Press ANY key to stop...   " );

    b8000::DisplayMemorySim     displayMemorySim;
    doTheDancingDolls( console, displayMemorySim );
}

int main()
{
    using namespace std;

    try
    {
        cppMain();
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        cout << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}

This post got a bit long, so I’m not adding in the code for the S() thing (it's not much code but still).

Anyway, this should provide a starting point for experimentation.

Disclaimer: I just cobbled this together. It’s not very extensively tested. I may, for example, have misunderstood things about the curses library’ keyboard handling –. And I’m hoping that it will work also in *nix-land, but I don’t know.

Cheers & hth.,

like image 28
Cheers and hth. - Alf Avatar answered Sep 29 '22 12:09

Cheers and hth. - Alf