Embedding Files in an Executable - SDL/Windows - Part 5
Gurigraphics
Posted on March 20, 2023
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
If we use 3 arguments we can return to the original file size
patch main.exe data.txt 246784
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;
}
2) Compile
g++ patch.cpp -o patch
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;
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;
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;
}
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
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
5) Show images
I took two free images from Pexels and adjusted the size: 1280x720
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;
}
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
7) Now Patch
Now let's add the assets in this recipe:
patch main.exe data.txt
patch main.exe bg.png
8) Run
Run the program:
main
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
Let's make the executable return to this size using a third argument:
patch main.exe data.txt 246784
patch main.exe bg.png
8) Run
Run the program:
main
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
Posted on March 20, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.