Tutorial: Add a Binary Filter to Okteta

Welcome to the first in hopefully a row of little tutorials showing how you can yourself add the features you need to Okteta, the KDE 4 hex editor.
Although, after writing this one, which took way too much time, I will see how I can make this more economic for me.
For now, you simply have to make use of this tutorial, so my time was well spent! 🙂

Setup of the development environment


Unless you already have a development environment, including the sources of kdeutils, e.g. after following the instructions from TechBase, the following little setup should work for you, too:

You need to have installed a proper compiler, cmake, a subversion client and the development packages of qt4 and kdelibs4. You might not need the kdelibs from trunk, version 4.3, perhaps even 4.2 should do it, I do not remember to have used any newer kdelibs API in Okteta so far. (Please report if you experience problems.)

Now get the sources of Okteta:

svn co svn://anonsvn.kde.org/home/kde/trunk/KDE/kdeutils -N
svn up kdeutils/okteta

Build the program in a separate directory (replace “yourinstallpath” with a proper path as useful for personal development, e.g. the output of kde4-config --prefix):

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=yourinstallpath ../kdeutils
cd okteta
make

The build will take a while as all those files you just saw when doing the checkout want to get compiled…

Do a make install to see if the installation works, too.
Then testrun Okteta with yourinstallpath/okteta.
(Or just okteta if you are sure your shell does not outsmart you and instead starts okteta from a path it has cached from a previous start when this version of Okteta was not yet installed)

Hoping that all went smooth we can now turn to…

Adding a Binary Filter

In this tutorial you write a filter which will turn all bytes either to 0x00 or 0xFF, depending on whether their old value is below or above a given level. It will look like this:
Level Filter

To do so you follow the instructions found in okteta/kasten/controllers/view/libbytearrayfilter/README.developers:

Step 1: Creating source files

Commands to get this done:

cd ../../kdeutils/okteta/kasten/controllers/view/libbytearrayfilter/filter/
cp template/template_bytearrayfilter.h levelbytearrayfilter.h
cp template/template_bytearrayfilter.cpp levelbytearrayfilter.cpp

Step 2: Adapting the code from the template

“levelbytearrayfilter.h” should end up like this:

#ifndef LEVELBYTEARRAYFILTER_H
#define LEVELBYTEARRAYFILTER_H

// lib
#include "nobytearrayfilterparameterset.h"
#include [abstractbytearrayfilter.h]

class LevelByteArrayFilter : public AbstractByteArrayFilter
{
  public:
    LevelByteArrayFilter();

    virtual ~LevelByteArrayFilter();

  public: // AbstractByteArrayFilter API
    virtual bool filter( Okteta::Byte* result, Okteta::AbstractByteArrayModel* model, const Okteta::AddressRange& range ) const;
    virtual AbstractByteArrayFilterParameterSet* parameterSet();

  protected:
    NoByteArrayFilterParameterSet mParameterSet;
};

#endif

And the file “levelbytearrayfilter.cpp” should end like this:

#include "levelbytearrayfilter.h"

// Okteta core
#include [abstractbytearraymodel.h]
// KDE
#include [KLocale]

LevelByteArrayFilter::LevelByteArrayFilter()
  : AbstractByteArrayFilter(
        i18nc("name of the filter; sets the byte to 0 or 255, depending on a level",
              "LEVEL data") )
{}

AbstractByteArrayFilterParameterSet* LevelByteArrayFilter::parameterSet() { return &mParameterSet; }

bool LevelByteArrayFilter::filter( Okteta::Byte* result,
                                       Okteta::AbstractByteArrayModel* model, const Okteta::AddressRange& range ) const
{
    const unsigned int level = 127;

    int r = 0;
    Okteta::Address m = range.start();
    int nextBlockEnd = FilteredByteCountSignalLimit;
    while( m byte( m++ );
        const Okteta::Byte resultByte = ( byte > level ) ? 255 : 0;
        result[r++] = resultByte;

        if( r >= nextBlockEnd )
        {
            nextBlockEnd += FilteredByteCountSignalLimit;
            emit filteredBytes( r );
        }
    }

    return true;
}

LevelByteArrayFilter::~LevelByteArrayFilter() {}

Step 3: Adding the filter to the library

The file “okteta/kasten/controllers/view/libbytearrayfilter/bytearrayfilterfactory.cpp” should now as both have an include for the file “filter/levelbytearrayfilter.h”

#include "filter/levelbytearrayfilter.h"

as well create a filter “LevelByteArrayFilter”

    result
        << new AndByteArrayFilter()
        << new OrByteArrayFilter()
        << new XOrByteArrayFilter()
        << new InvertByteArrayFilter()
        << new ReverseByteArrayFilter()
        << new RotateByteArrayFilter()
        << new ShiftByteArrayFilter()
        // new:
        << new LevelByteArrayFilter();

In the file “okteta/kasten/controllers/CMakeLists.txt” the list “LIBFILTER_SRCS” should now also include the file “levelbytearrayfilter.cpp”:

  view/libbytearrayfilter/filter/reversebytearrayfilter.cpp
  view/libbytearrayfilter/filter/rotatebytearrayfilter.cpp
  view/libbytearrayfilter/filter/shiftbytearrayfilter.cpp
# new:
  view/libbytearrayfilter/filter/levelbytearrayfilter.cpp

Step 4: Compile, install and run

Commands to get this done:

cd ../../../../../../../build/
make
make install

If the build and install went well, start Okteta with yourinstallpath/okteta (cmp. remarks at end of section “Setup of the development environment” above).

Load some file, select a range, open the Binary Filter tool and select “LEVEL data” as filter. It should look like this:
Level Filter before being applied

Now press the “Filter” button. It should look like this:
Level Filter after being applied

Now let’s turn to…

Making the Level an editable Parameter

Step 1: Creating source files

Commands to get this done:

cd ../kdeutils/okteta/kasten/controllers/view/libbytearrayfilter/filter/
cp template/template_bytearrayfilterparameterset.h levelbytearrayfilterparameterset.h
cp template/template_bytearrayfilterparameterset.cpp levelbytearrayfilterparameterset.cpp
cp template/template_bytearrayfilterparametersetedit.h levelbytearrayfilterparametersetedit.h
cp template/template_bytearrayfilterparametersetedit.cpp levelbytearrayfilterparametersetedit.cpp

Step 2: Adapting the code from the template

“levelbytearrayfilterparameterset.h” should end up like this:

#ifndef LEVELBYTEARRAYFILTERWITHPARAMETERSET_H
#define LEVELBYTEARRAYFILTERWITHPARAMETERSET_H

// lib
#include [abstractbytearrayfilterparameterset.h]


class LevelByteArrayFilterParameterSet : public AbstractByteArrayFilterParameterSet
{
  public:
    LevelByteArrayFilterParameterSet();

    virtual ~LevelByteArrayFilterParameterSet();

  public: // AbstractByteArrayFilterParameterSet API
    virtual const char* id() const;

  public: // getters
    unsigned char level() const;

  public: // setters
    void setLevel( unsigned int level );

  protected: // parameters
    unsigned char mLevel;
};

#endif

“levelbytearrayfilterparameterset.cpp” should end up like this:

#include "levelbytearrayfilterparameterset.h"

static const unsigned int DefaultLevel = 127;

LevelByteArrayFilterParameterSet::LevelByteArrayFilterParameterSet()
  : mLevel( DefaultLevel )
{}

const char* LevelByteArrayFilterParameterSet::id() const { return "Level"; }

unsigned char LevelByteArrayFilterParameterSet::level() const { return mLevel; }

void LevelByteArrayFilterParameterSet::setLevel( unsigned int level ) { mLevel = level; }

LevelByteArrayFilterParameterSet::~LevelByteArrayFilterParameterSet() {}

“levelbytearrayfilterparametersetedit.h” should end up like this:

#ifndef LEVELBYTEARRAYFILTERPARAMETERSETEDIT_H
#define LEVELBYTEARRAYFILTERPARAMETERSETEDIT_H

// lib
#include [abstractbytearrayfilterparametersetedit.h]

class KIntNumInput;

class LevelByteArrayFilterParameterSetEdit : public AbstractByteArrayFilterParameterSetEdit
{
  Q_OBJECT

  public:
    static const char* const Id;

  public:
    LevelByteArrayFilterParameterSetEdit( QWidget* parent = 0 );

    virtual ~LevelByteArrayFilterParameterSetEdit();

  public: // AbstractByteArrayFilterParameterSetEdit API
    virtual void setValues( const AbstractByteArrayFilterParameterSet* parameterSet );
    virtual void getParameterSet( AbstractByteArrayFilterParameterSet* parameterSet ) const;
    virtual bool isValid() const;

  protected Q_SLOTS:
    void onLevelChanged( int value );

  protected:
    KIntNumInput* mLevelEdit;

    bool mIsValid :1;
};

#endif

“levelbytearrayfilterparametersetedit.cpp” should end up like this:

#include "levelbytearrayfilterparametersetedit.h"

// parameterset
#include "levelbytearrayfilterparameterset.h"
// KDE
#include [KLocale]
#include [KIntNumInput]
// Qt
#include [QtGui/QFormLayout]

const char* const LevelByteArrayFilterParameterSetEdit::Id = "Level";

LevelByteArrayFilterParameterSetEdit::LevelByteArrayFilterParameterSetEdit( QWidget* parent )
  : AbstractByteArrayFilterParameterSetEdit( parent )
{
    QFormLayout* baseLayout = new QFormLayout( this );
    // margin is provided by the container for this widget
    baseLayout->setMargin( 0 );

    mLevelEdit = new KIntNumInput( this );
    // For demonstration purpose we start at -1, not 0, to show handling of an invalid state
    // Otherwise the range should start at 0 and there is no need to connect to the valueChanged signal
    mLevelEdit->setRange( -1, 256 );
    // start with the invalid number
    mLevelEdit->setValue( -1 );
    connect( mLevelEdit, SIGNAL(valueChanged( int )), SLOT(onLevelChanged( int )) );

    const QString levelLabelText =
         i18nc( "@label:spinbox decimal value up to which bytes are set to 0",
                "Level:" );

    baseLayout->addRow( levelLabelText, mLevelEdit );

    // note start state
    mIsValid = isValid();
}


bool LevelByteArrayFilterParameterSetEdit::isValid() const { return mLevelEdit->value() != -1; }


void LevelByteArrayFilterParameterSetEdit::setValues( const AbstractByteArrayFilterParameterSet* parameterSet )
{
    const LevelByteArrayFilterParameterSet* template_ParameterSet =
        static_cast( parameterSet );

    mLevelEdit->setValue( template_ParameterSet->level() );
}

void LevelByteArrayFilterParameterSetEdit::getParameterSet( AbstractByteArrayFilterParameterSet* parameterSet ) const
{
    LevelByteArrayFilterParameterSet* template_ParameterSet =
        static_cast( parameterSet );

    template_ParameterSet->setLevel( mLevelEdit->value() );
}


void LevelByteArrayFilterParameterSetEdit::onLevelChanged( int value )
{
    const bool isValid = ( value != -1 );

    if( mIsValid == isValid )
        return;

    mIsValid = isValid;
    emit validityChanged( isValid );
}

LevelByteArrayFilterParameterSetEdit::~LevelByteArrayFilterParameterSetEdit()
{}

Also make the “LevelByteArrayFilter” use the “LevelByteArrayFilterParameterSet”, by adapting the include notion and the used parameter set class in the file “levelbytearrayfilter.h” to now be:

#include "levelbytearrayfilterparameterset.h"
    LevelByteArrayFilterParameterSet mParameterSet;

In the file “levelbytearrayfilter.cpp” change the variable “level” to be not the hardcoded 127, but to take its value from the parameter set:

    const unsigned int level = mParameterSet.level();

Step 3: Adding the parameterset to the library

The file “okteta/kasten/controllers/view/libbytearrayfilter/bytearrayfilterparameterseteditfactory.cpp” should now as both have an include for the file “filter/levelbytearrayfilterparametersetedit.h”

#include "filter/levelbytearrayfilterparametersetedit.h"

as well create a widget “LevelByteArrayFilterParameterSetEdit” for the “LevelByteArrayFilterParameterSetEdit::Id”:

[...]
    else if( qstrcmp(id,RotateByteArrayFilterParameterSetEdit::Id) == 0 )
        result = new RotateByteArrayFilterParameterSetEdit();
// new:
    else if( qstrcmp(id,LevelByteArrayFilterParameterSetEdit::Id) == 0 )
        result = new LevelByteArrayFilterParameterSetEdit();
[...]

In the file “okteta/kasten/controllers/CMakeLists.txt” the list “LIBFILTER_SRCS” should now also include the files “levelbytearrayfilterparameterset.cpp” and “levelbytearrayfilterparametersetedit.cpp”:

[...]
  view/libbytearrayfilter/filter/rotatebytearrayfilterparameterset.cpp
# new:
  view/libbytearrayfilter/filter/levelbytearrayfilterparameterset.cpp
[...]
  view/libbytearrayfilter/filter/rotatebytearrayfilterparametersetedit.cpp
# new:
  view/libbytearrayfilter/filter/levelbytearrayfilterparametersetedit.cpp
[...]

Step 4: Compile, install and run

Commands to get this done:

cd ../../../../../../../build/
make
make install

Running Okteta now should get you this:
Level Filter with Parameter after being applied

It did? Hurra 🙂

Now, please go and write your own special filters! Then share your work and send them to me for inclusion in the next Okteta release 🙂

Next tutorial will be: Add a Checksum/Hashsum algorithm

2 thoughts on “Tutorial: Add a Binary Filter to Okteta

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.