Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# datacoe

datacoe is a small, simple and generic C++ data management library for game development,
datacoe is a small, simple and generic C++ data management library template for game development,
It provides functionalities for data persistence, serialization, and encryption.

[![Windows](https://github.com/nircoe/datacoe/actions/workflows/ci-windows.yml/badge.svg?branch=main&event=push)](https://github.com/nircoe/datacoe/actions/workflows/ci-windows.yml)
Expand Down Expand Up @@ -286,7 +286,6 @@ The project includes a comprehensive test suite built with Google Test. Tests co

- Basic data operations
- Error handling and recovery
- Thread safety and concurrency
- Performance benchmarks
- Memory usage

Expand Down Expand Up @@ -411,6 +410,13 @@ Game-specific implementations will have their own tags (e.g., `worm-v1.0.0`) to

## Version History

### v0.1.1 (Optional Encryption)
- Added ability to disable encryption when not needed
- Implemented automatic encryption detection for backwards compatibility
- Improved file handling with clear encryption status identification
- Enhanced performance for unencrypted files
- Added tests to measure and compare encryption overhead in terms of both time and file size

### v0.1.0 (Initial Development)
- Basic data management functionality
- JSON serialization
Expand Down Expand Up @@ -448,9 +454,9 @@ If you'd like to contribute, please:
- ✅ AES encryption/decryption using CryptoPP
- ✅ Comprehensive test suite with Google Test
- ✅ Automated dependency management
- ✅ Optional encryption (ability to disable encryption if not needed)

### Planned Improvements
- ⏳ Optional encryption (ability to disable encryption if not needed)
- ⏳ Secure encryption key management (replacing fixed keys with secure storage and derivation)
- ⏳ Graceful recovery from corrupted files with backup system
- ⏳ Thread-safe operations for concurrent data access
Expand Down
8 changes: 7 additions & 1 deletion include/data_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace datacoe
{
std::string m_filename;
GameData m_gamedata;
bool m_encrypt = true; // Whether to use encryption
bool m_fileEncrypted = false; // Whether the file is currently encrypted

public:
// Users should add or modify constructors and destructor as needed
Expand All @@ -22,7 +24,7 @@ namespace datacoe
bool loadGame();

// Users should modify the initialization to match their own game
void init(const std::string filename);
void init(const std::string filename, bool encrypt = true);

// Users should modify this method to match their own game
void newGame();
Expand All @@ -32,5 +34,9 @@ namespace datacoe
void setHighScore(int highscore);

const GameData &getGameData() const;

// Encryption related methods
bool isEncrypted() const;
void setEncryption(bool encrypt);
};
} // namespace datacoe
5 changes: 3 additions & 2 deletions include/data_reader_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ namespace datacoe
static std::string decrypt(const std::string &encodedData);

public:
static bool writeData(const GameData &gamedata, const std::string &filename);
static std::optional<GameData> readData(const std::string &filename);
static bool isFileEncrypted(const std::string &filename);
static bool writeData(const GameData &gamedata, const std::string &filename, bool encryption = true);
static std::optional<GameData> readData(const std::string &filename, bool decryption = true);
};
} // namespace datacoe
24 changes: 21 additions & 3 deletions src/data_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,29 @@ namespace datacoe
if (m_gamedata.getNickName().empty())
return true; // no need to save (guest mode)

return DataReaderWriter::writeData(m_gamedata, m_filename);
bool result = DataReaderWriter::writeData(m_gamedata, m_filename, m_encrypt);
if (result)
m_fileEncrypted = m_encrypt;

return result;
}

bool DataManager::loadGame()
{
std::optional<GameData> loadedGameData = DataReaderWriter::readData(m_filename);
m_fileEncrypted = DataReaderWriter::isFileEncrypted(m_filename);

std::optional<GameData> loadedGameData = DataReaderWriter::readData(m_filename, m_encrypt);
bool readDataSucceed = loadedGameData.has_value();
if (readDataSucceed)
m_gamedata = loadedGameData.value();
return readDataSucceed;
}

void DataManager::init(const std::string filename)
void DataManager::init(const std::string filename, bool encrypt)
{
m_filename = filename;
m_encrypt = encrypt;

if (!loadGame())
{
// can't load, needs to ask the user for a nickname and create new GameData
Expand All @@ -48,4 +56,14 @@ namespace datacoe
{
return m_gamedata;
}

bool DataManager::isEncrypted() const
{
return m_fileEncrypted;
}

void DataManager::setEncryption(bool encrypt)
{
m_encrypt = encrypt;
}
} // namespace datacoe
111 changes: 87 additions & 24 deletions src/data_reader_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <filesystem>
#include <thread>
#include <chrono>
#include <vector>
#include "cryptopp/aes.h"
#include "cryptopp/modes.h"
#include "cryptopp/filters.h"
Expand All @@ -14,11 +15,34 @@

namespace datacoe
{
const std::string ENCRYPTION_PREFIX = "DATACOE_ENCRYPTED";

// Fixed Encryption Key (Warning: This is Insecure, I'm using it for learning purposes only!)
const CryptoPP::byte fixedKey[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};

bool DataReaderWriter::isFileEncrypted(const std::string &filename)
{
if (!std::filesystem::exists(filename))
return false;

std::ifstream file(filename, std::ios::binary);
if (!file.is_open())
return false;

// Read just enough bytes to check for our prefix
std::vector<char> header(ENCRYPTION_PREFIX.size());
file.read(header.data(), ENCRYPTION_PREFIX.size());

// Check if we read enough bytes
if (file.gcount() < static_cast<std::streamsize>(ENCRYPTION_PREFIX.size()))
return false;

// Compare with our prefix
return std::string(header.data(), static_cast<size_t>(file.gcount())) == ENCRYPTION_PREFIX;
}

std::string DataReaderWriter::encrypt(const std::string &data)
{
try
Expand All @@ -45,7 +69,7 @@ namespace datacoe
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(encoded)));

return encoded;
return ENCRYPTION_PREFIX + encoded;
}
catch (const CryptoPP::Exception &e)
{
Expand All @@ -65,9 +89,20 @@ namespace datacoe
{
try
{
std::string dataToDecrypt = encodedData;
if (dataToDecrypt.substr(0, ENCRYPTION_PREFIX.size()) == ENCRYPTION_PREFIX)
{
dataToDecrypt = dataToDecrypt.substr(ENCRYPTION_PREFIX.size());
}
else
{
std::cerr << "DataReaderWriter::decrypt() Warning: Missing encryption prefix" << std::endl;
// Continue anyway in case it's an older file without the prefix
}

// Decode Base64
std::string decoded;
CryptoPP::StringSource ss1(encodedData, true,
CryptoPP::StringSource ss1(dataToDecrypt, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(decoded)));

Expand Down Expand Up @@ -106,7 +141,7 @@ namespace datacoe
}
}

bool DataReaderWriter::writeData(const GameData &gamedata, const std::string &filename)
bool DataReaderWriter::writeData(const GameData &gamedata, const std::string &filename, bool encryption)
{
try
{
Expand All @@ -115,23 +150,32 @@ namespace datacoe
std::cout << "Debug: GameData to JSON: " << std::endl
<< jsonData << std::endl;

// Encrypt the JSON data
std::string encryptedData = encrypt(jsonData);
if (encryptedData.empty())
std::string writeableData;

if(encryption)
{
std::cerr << "DataReaderWriter::writeData() Error: Encryption failed" << std::endl;
return false;
// Encrypt the JSON data
std::string encryptedData = encrypt(jsonData);
if (encryptedData.empty())
{
std::cerr << "DataReaderWriter::writeData() Error: Encryption failed" << std::endl;
return false;
}
writeableData = encryptedData;
}
else // no encryption
writeableData = jsonData;

// Write the encrypted data to file
std::ofstream file(filename, std::ios::binary);
// Write the data to file
auto openmode = encryption ? std::ios::binary : std::ios::out;
std::ofstream file(filename, openmode);
if (!file.is_open())
{
std::cerr << "DataReaderWriter::writeData() Error: Could not open file for writing: " << filename << std::endl;
return false;
}

file.write(encryptedData.c_str(), encryptedData.size());
file.write(writeableData.c_str(), writeableData.size());
if (!file.good())
{
std::cerr << "DataReaderWriter::writeData() Error: File write failed" << std::endl;
Expand All @@ -150,7 +194,7 @@ namespace datacoe
}
}

std::optional<GameData> DataReaderWriter::readData(const std::string &filename)
std::optional<GameData> DataReaderWriter::readData(const std::string &filename, bool decryption)
{
try
{
Expand All @@ -160,30 +204,49 @@ namespace datacoe
return std::nullopt;
}

// Read the encrypted data from file
std::ifstream file(filename, std::ios::binary);
bool fileIsEncrypted = isFileEncrypted(filename);
if(fileIsEncrypted != decryption)
{
std::cerr << "DataReaderWriter::readData() Warning: "
<< (fileIsEncrypted ? "File is encrypted but decryption=false"
: "File is not encrypted but decryption=true")
<< " - Adjusting decryption flag to match file state" << std::endl;
decryption = fileIsEncrypted;
}

// Read the data from file
auto openmode = decryption ? std::ios::binary : std::ios::in;
std::ifstream file(filename, openmode);
if (!file.is_open())
{
std::cerr << "DataReaderWriter::readData() Error: Could not open file for reading: " << filename << std::endl;
return std::nullopt;
}

std::string encodedData((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
std::string data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();

// Decrypt the data
std::string decryptedData = decrypt(encodedData);
if (decryptedData.empty())
std::string parseableData;

if(decryption)
{
std::cerr << "DataReaderWriter::readData() Error: Decryption failed" << std::endl;
return std::nullopt;
}
// Decrypt the data
std::string decryptedData = decrypt(data);
if (decryptedData.empty())
{
std::cerr << "DataReaderWriter::readData() Error: Decryption failed" << std::endl;
return std::nullopt;
}

std::cout << "Debug: Decrypted JSON: " << std::endl
<< decryptedData << std::endl;
std::cout << "Debug: Decrypted JSON: " << std::endl
<< decryptedData << std::endl;
parseableData = decryptedData;
}
else // no decryption
parseableData = data;

// Parse the JSON data
json j = json::parse(decryptedData);
json j = json::parse(parseableData);
return GameData::fromJson(j);
}
catch (const json::exception &e)
Expand Down
1 change: 0 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ set(TEST_FILES
integration_tests.cpp
performance_tests.cpp
memory_tests.cpp
thread_safety_tests.cpp
error_handling_tests.cpp
)

Expand Down
Loading
Loading