Jan's Blog

executable hell

a lot of applications these days are distributed in multiple executable files

especially game mods

launching these programs directly is fine and good but for things like mods for steam games that require a third party executable thats where the problems come in, especially on Linux.

My local install of Modern Warfare 2 with IW4x installed has 3 executables

iw4mp.exe, iw4sp.exe, iw4x.exe

It gets away with this on steam by having 2 different titles for singleplayer and multiplayer

but launching iw4x with the Proton environment is where it gets annoying

I can replace the mp executable but that means I need to deal with moving it when I want to play regular MP

launching it directly without steam can be done in this case but other games may depend on steam running, which wine itself cannot tell if it is

wrapping around binaries

I copied together some boilerplate code from my other projects polecat and OFLauncher to create a simple Qt application.

the logic is quite simple

 1QDirIterator it(".", {"*" EXE}, QDir::Files);
 2while (it.hasNext())
 3{
 4    it.next(); // we want the filename so lets ignore the output
 5    QString filename = it.fileName();
 6
 7    if (!isExe(filename.toStdString().c_str()) || filename.compare(prgm) == 0) continue;
 8
 9    addButton(filename);
10
11}

we iterate over all files in the current directory and check if they are executable files.

On linux we can simply check the executable bit but on Windows there isn’t such a straight forward way so we just check if it ends with .EXE

 1#define EXE ".exe"
 2
 3int isExe(const char* path)
 4{
 5#ifdef _WIN32
 6    size_t pathlen = strlen(path);
 7
 8    return strncmp(path + (pathlen - strlen(EXE)), EXE, strlen(EXE)+1) == 0;
 9#else
10    struct stat sb = getStat(path);
11
12    return (sb.st_mode & S_IXUSR) != 0;
13#endif
14}

to be cheap we don’t store the executable names or paths ourselves

every executable gets a button added with its name

1void MainWindow::addButton(const QString name)
2{
3    QPushButton* button = new QPushButton(this->centralwidget);
4    button->setText(name);
5
6    connect(button, &QPushButton::clicked, this, [=]{replaceProcess(name);});
7
8    this->verticalLayout->addWidget(button);
9}

that button gets a lambda function hooked up to its clicked event giving us access to the name without having to store it ourselves

 1void MainWindow::replaceProcess(QString name)
 2{
 3    char* exec = new char[strlen(this->argv[0]) - strlen(this->prgm) + name.size()];
 4    strcpy(exec, this->argv[0]);
 5    strcpy(exec+(this->prgm-this->argv[0]), name.toStdString().c_str());
 6    this->argv[0] = exec;
 7
 8    if (execvp(exec, this->argv) == -1)
 9        printf("execvp() %s\n", strerror(errno));
10}

we keep all but argv[0] the same so we can retain commandline arguments.

argv[0] needs to be modified since many programs expect it to contain its own executable name

the resulting executable is rather simplistic in design but it does its job as advertised

source code

#C++ #Qt #Steam