DEV Community

Cover image for Embedding Files in an Executable - SDL/Windows - Part 5
Gurigraphics
Gurigraphics

Posted on • Updated on

Embedding Files in an Executable - SDL/Windows - Part 5

In this example we will test this system using the SDL library.

Let's change the patch.

If using only 2 arguments it will get the size of the file itself

patch main.exe data.txt
Enter fullscreen mode Exit fullscreen mode

If we use 3 arguments we can return to the original file size

patch main.exe data.txt 246784
Enter fullscreen mode Exit fullscreen mode

1) Let's update the patch:

#include <fstream>
#include <iostream>
#include <string> 
#include <vector>
#include <sstream>

std::vector<char> file_read_bin(const std::string& fileName) {
  std::string filePath = fileName;
  std::ifstream file(filePath, std::ios::binary);
  if (file.fail()) {
    std::cerr << "Open file error " << filePath << std::endl;
   } 
 // Read bytes 
  std::vector<char> bytFile((std::istreambuf_iterator<char>(file)),
                             std::istreambuf_iterator<char>());
  file.close(); 
  return bytFile;
}

void file_write_bin(const std::string& filePath, std::vector<char> bytFile) { 
  // Write bytes
  std::ofstream outFile(filePath, std::ios::binary);
  if (outFile.fail()) {
    std::cerr << "Open file error " << filePath << " to write" << std::endl;  
  } 
  outFile.write(bytFile.data(), bytFile.size());
  outFile.close();

  std::cout << "File sucessfull updated" << std::endl;
} 

int main(int argc, char *argv[]){   

  int correctSize = 0;

  if (argc < 2) {
      std::cout << "Require 2 or 3 args: program.exe data.txt 163857\n";
      return 1;
  }

  const char* program_file = argv[1];
  const char* data_file = argv[2];

  std::vector<char> bytFile = file_read_bin( program_file );
  std::vector<char> bytText = file_read_bin( data_file );

  int size = bytFile.size(); 

  if(argc == 3) {
    correctSize = size;    
  }else if(argc == 4) {
    correctSize = std::stoi(argv[3]);
  }

  int diff = size - correctSize;

  // Logs
  std::cout << "size: " << size << "\n";
  std::cout << "correctSize: " << correctSize << "\n";

  // Remove old code
  if(size > correctSize ){
    bytFile.erase(bytFile.end() - diff, bytFile.end() );
  }

  // Add content
  bytFile.insert(bytFile.end(), bytText.begin(), bytText.end());

  // Add content size
  bytFile.insert(bytFile.end(), {
    static_cast<char>((bytText.size() >> 24) & 0xFF),
    static_cast<char>((bytText.size() >> 16) & 0xFF),
    static_cast<char>((bytText.size() >> 8) & 0xFF),
    static_cast<char>((bytText.size() >> 0) & 0xFF)
  });

  // Add file name
  std::string data_filename_str(data_file);
  data_filename_str+="\0";
  bytFile.insert(bytFile.end(), data_filename_str.c_str(), data_filename_str.c_str() + data_filename_str.size() );

  // Add name size
  bytFile.insert( bytFile.end(), data_filename_str.size() );  

  // Add symbol exist new file
  std::string symbol = "^";
  bytFile.insert(bytFile.end(), symbol.c_str(), symbol.c_str() + symbol.size() );

  // Rewrite 
  file_write_bin(program_file, bytFile);

  return 0;
}
Enter fullscreen mode Exit fullscreen mode

2) Compile

g++ patch.cpp -o patch
Enter fullscreen mode Exit fullscreen mode

3) Create patchFiles lib

Now separate that system. Put in a file "patchFiles.h"

We want it to be insanely simple to use.

patch! patch! end!

With few lines:

#include "./patchFiles.h"

// Get files
std::map<std::string, FileInfo> files = getFiles(); 

//Use
std::cout << files["data.txt"].contentString;  
Enter fullscreen mode Exit fullscreen mode

For this we will use a map:

struct FileInfo {
    int fileNameSize;
    std::string fileName;
    int contentSize;
    int contentStart;
    int gap;
    std::string contentString;
    std::vector<char> contentVector;    
};

std::map<std::string, FileInfo> files; 
Enter fullscreen mode Exit fullscreen mode

patchFiles.h

#include <map>
#include <string>
#include <vector>
#include <filesystem>

#include <fstream>
#include <iostream>
#include <windows.h>
#include <sstream>
#include <string> 
#include <locale>

struct FileInfo {
    int fileNameSize;
    std::string fileName;
    int contentSize;
    int contentStart;
    int gap;
    std::string contentString;
    std::vector<char> contentVector;    
};

int assetsCount = 0;
std::map<std::string, FileInfo> files; 

std::string getFileName(){
    char filename[MAX_PATH];
    GetModuleFileNameA(NULL, filename, MAX_PATH);
    std::string filepath(filename);
    std::string basename = filepath.substr(filepath.find_last_of("\\/") + 1);
    return filename;
}

std::vector<char> file_read_bin(const std::string& fileName) {
  std::string filePath = fileName;
  std::ifstream file(filePath, std::ios::binary);
  if (file.fail()) {
    std::cerr << "Erro ao abrir o arquivo " << filePath << std::endl;
   } 
 // Read bytes 
  std::vector<char> bytFile((std::istreambuf_iterator<char>(file)),
                             std::istreambuf_iterator<char>());
  file.close(); 
  return bytFile;
}

std::string getHexValue(const std::vector<char>& bytFile, int size, int byteCount) {
    std::stringstream ss;
    ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<unsigned int>(static_cast<unsigned char>(bytFile[size - byteCount]));
    return ss.str();
}

int hexToInt(std::string hexStr) {
    return std::stoi(hexStr, nullptr, 16);
}

int getFileNamesize(std::vector<char> bytFile, int size){

    std::string sizeHex = getHexValue(bytFile, size, 2); 
    return hexToInt( sizeHex );
}

std::string getFileName(std::vector<char> bytFile, int size, int fileNameSize){

    std::string fileName = "";
    int startfileName = fileNameSize + 2;
    for (int i = startfileName; i > 2; i--) { 
        std::string byteInStringFormat;
        byteInStringFormat.push_back(static_cast<char>(bytFile[size - i])); // convert byte to char
        fileName += byteInStringFormat;
    } 
    return fileName;
}

int getContentSize(std::vector<char> bytFile, int size, int fileNameSize){

    std::string byte_24 = getHexValue(bytFile, size, fileNameSize + 3);
    std::string byte_16 = getHexValue(bytFile, size, fileNameSize + 4);
    std::string byte_08 = getHexValue(bytFile, size, fileNameSize + 5);
    std::string byte_00 = getHexValue(bytFile, size, fileNameSize + 6);

    std::stringstream ss;   
    ss << std::hex << byte_00 << byte_08 << byte_16 << byte_24;
    int contentSize;
    ss >> contentSize;
    return contentSize;
}

std::string getContent(std::vector<char> bytFile, int size, int fileNameSize, int contentSize, int contentStart){

    std::string content = "";

    for (int i = contentStart; i > fileNameSize+3+3; i--) { 
        std::string byteInStringFormat;
        byteInStringFormat.push_back(static_cast<char>(bytFile[size - i])); // convert byte to char 
        content += byteInStringFormat;
    }

    return content;
}

int getContent(std::map<std::string, FileInfo>& files, const std::vector<char>& bytFile, int size) {

    if( bytFile[size - 1] != '^') { // Exist new file?
        std::cout << "end" << "\n";    
        return 0; 
    }

    std::cout << assetsCount << "-----------------" << "\n"; 

    // Get file name size in last byte 
    int fileNameSize = getFileNamesize(bytFile, size);
    std::cout << "content name size: " << fileNameSize << "\n";

    // Get file name in last byte 
    std::string fileName = getFileName(bytFile, size, fileNameSize);
    std::cout << "content name: " << fileName << "\n"; 

    // Get content size in 4 bytes
    int contentSize = getContentSize(bytFile, size, fileNameSize);
    std::cout << "content size: " << contentSize << "\n";   


    // Get contentStart from final of file 
    int contentStart = contentSize+fileNameSize+3+3;
    int gap = contentStart - contentSize;

    // Get content
    std::string contentString = "";   
    std::vector<char> contentVector;

    if(fileName.find(".txt") != fileName.npos){

      contentString = getContent(bytFile, size, fileNameSize, contentSize, contentStart);

    }else if(fileName.find(".png") != fileName.npos){

        for(int i = bytFile.size() - contentSize - gap; i < bytFile.size() + gap; i++) {
            contentVector.push_back(bytFile[i]);
        }
    }

    files[ fileName ] = {fileNameSize, fileName, contentSize, contentStart, gap, contentString, contentVector};
    assetsCount+=1;

    // End
    int final = size - (contentStart + 1);
    std::cout << "-----------------" << "\n"; 

    getContent(files, bytFile, final+1); // get new file

    return 0;
}

std::map<std::string, FileInfo> getFiles() {

    // Read file  
    std::string filename = getFileName();
    std::vector<char> bytFile = file_read_bin( filename ); 
    int size = bytFile.size(); 

    // Logs
    std::cout << "file size: " << size << std::endl;
    std::cout << "last byte: " << getHexValue(bytFile, size, 1) << std::endl;

    getContent(files, bytFile, size); 

    return files;
}
Enter fullscreen mode Exit fullscreen mode

4) Download SDL

Download and export inside the project folder:

SDL2_image-devel-2.6.3-mingw.zip
https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.3/SDL2_image-devel-2.6.3-mingw.zip

SDL2-devel-2.26.4-mingw.zip
https://github.com/libsdl-org/SDL/releases/download/release-2.26.4/SDL2-devel-2.26.4-mingw.zip

You can leave the folders with these same names.

SDL2_image-devel-2.6.3-mingw
SDL2-devel-2.26.4-mingw
Enter fullscreen mode Exit fullscreen mode

We're not even going to install it. Let's use it anyway.

Just grab these two files from bin folders and drop them in the project directory:

SDL2.dll
SDL2_image.dll
Enter fullscreen mode Exit fullscreen mode

5) Show images

I took two free images from Pexels and adjusted the size: 1280x720

Image app

Image app

Download these images. Rename one of the images to "bg.png"
https://dev-to-uploads.s3.amazonaws.com/uploads/articles/899pvtvcwx2zney81vr4.png
https://dev-to-uploads.s3.amazonaws.com/uploads/articles/agy0ayu8ul5xl78k8yzh.png

Also create a file: data.txt
Content: 123

Now just import our lib and use:

main.cpp


#include <SDL.h>
#include <SDL_image.h>

#include "./patchFiles.h"

int main(int argc, char *argv[]){    

    // Get assets
    std::map<std::string, FileInfo> files = getFiles(); 

    // Use
    std::vector<char> bytImage = files["bg.png"].contentVector;
    std::cout << files["data.txt"].contentString; 

    SDL_Init(SDL_INIT_VIDEO);

    SDL_Window* window = SDL_CreateWindow("App", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_RWops* rwops = SDL_RWFromMem(bytImage.data(), bytImage.size());
    SDL_Surface* surface = IMG_Load_RW(rwops, 1);  
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);

    SDL_FreeSurface(surface);

    bool quit = false;
    while (!quit) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_QUIT:
                    quit = true;
                    break;
                case SDL_KEYDOWN:
                    if (event.key.keysym.sym == SDLK_ESCAPE) {
                        quit = true;
                        break;
                    }
            }
        }

        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL, NULL);
        SDL_RenderPresent(renderer);
    }

    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;  
}

Enter fullscreen mode Exit fullscreen mode

6) Compile

Let's compile using all of that, no makefile or anything:

g++ -std=c++17 main.cpp -o main.exe -I.\ -I.\SDL2-devel-2.26.4-mingw\SDL2-2.26.4\x86_64-w64-mingw32\include\SDL2 -I.\SDL2_image-devel-2.6.3-mingw\SDL2_image-2.6.3\x86_64-w64-mingw32\include\SDL2 -L.\SDL2_image-devel-2.6.3-mingw\SDL2_image-2.6.3\x86_64-w64-mingw32\lib -L.\SDL2-devel-2.26.4-mingw\SDL2-2.26.4\x86_64-w64-mingw32\lib -w -Wl,-subsystem,console -lmingw32 -lSDL2main -lSDL2 -lSDL2_image
Enter fullscreen mode Exit fullscreen mode

7) Now Patch

Now let's add the assets in this recipe:

patch main.exe data.txt
patch main.exe bg.png
Enter fullscreen mode Exit fullscreen mode

8) Run
Run the program:

main
Enter fullscreen mode Exit fullscreen mode

Image app

9) Test
Now let's test if this all really works.
Let's change the assets without having to compile.

Create a folder and drop the image there or delete it.
Rename the other image to "bg.png"
Change the content of data.txt to "0987654321" or whatever.

There in the prompt you can read what was the first file size.

> patch main.exe data.txt
size: 246784
correctSize: 246784
Enter fullscreen mode Exit fullscreen mode

Let's make the executable return to this size using a third argument:

patch main.exe data.txt 246784
patch main.exe bg.png
Enter fullscreen mode Exit fullscreen mode

8) Run
Run the program:

main
Enter fullscreen mode Exit fullscreen mode

Image app

Negative aspects

This method can be useful in repetitive tests to save compilation time. For example, a game engine that needs to render games over and over again as quickly as possible. Since it is quite tedious to constantly wait for the compilation.

However, it may not be appropriate to distribute this modified file.

Antiviruses can report it as a suspicious file or false positives:

Modified - 1 security vendor and no sandboxes flagged this file as malicious
https://www.virustotal.com/gui/file/f0f6cdbe71d7323e535cb333aa3b6592b7fa3b2623d7013246f19aa2e271a60d

Normal - No security vendors and no sandboxes flagged this file as malicious
https://www.virustotal.com/gui/file/099db7271ae6ba2014e86a5666d0602807e4ede039e02e8c65762f472280014d

Embedding Files in an Executable - Icons & Details - Part 6

https://dev.to/gurigraphics/embedding-files-in-an-executable-icons-details-part-6-1j9l

Top comments (0)