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
QDirIterator it(".", {"*" EXE}, QDir::Files);
while (it.hasNext())
{
it.next(); // we want the filename so lets ignore the output
QString filename = it.fileName();
if (!isExe(filename.toStdString().c_str()) || filename.compare(prgm) == 0) continue;
addButton(filename);
}
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
#define EXE ".exe"
int isExe(const char* path)
{
#ifdef _WIN32
size_t pathlen = strlen(path);
return strncmp(path + (pathlen - strlen(EXE)), EXE, strlen(EXE)+1) == 0;
#else
struct stat sb = getStat(path);
return (sb.st_mode & S_IXUSR) != 0;
#endif
}
to be cheap we don’t store the executable names or paths ourselves
every executable gets a button added with its name
void MainWindow::addButton(const QString name)
{
QPushButton* button = new QPushButton(this->centralwidget);
button->setText(name);
connect(button, &QPushButton::clicked, this, [=]{replaceProcess(name);});
this->verticalLayout->addWidget(button);
}
that button gets a lambda function hooked up to its clicked event giving us access to the name without having to store it ourselves
void MainWindow::replaceProcess(QString name)
{
char* exec = new char[strlen(this->argv[0]) - strlen(this->prgm) + name.size()];
strcpy(exec, this->argv[0]);
strcpy(exec+(this->prgm-this->argv[0]), name.toStdString().c_str());
this->argv[0] = exec;
if (execvp(exec, this->argv) == -1)
printf("execvp() %s\n", strerror(errno));
}
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