Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Problems with making my own InputStream  (Read 8498 times)

0 Members and 1 Guest are viewing this topic.

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
Problems with making my own InputStream
« on: November 17, 2013, 04:11:11 am »
Apparently, searching the forum and the internet for help regarding derivations of sf::InputStream was fruitless, so hello! I've just registered, to ask some help for making my own stream class. :)

I tried following the tutorial here: http://www.sfml-dev.org/tutorials/2.0/system-stream.php

My class is basically a copy and paste of it, and I want to simply be able to read and decrypt my own encrypted files. As of now, though, it does nothing in special, I have left the encrypting methods empty for testing. Problem is: I haven't been able to load anything with it. Every time I try to call a loadFromStream from a sf::Font or sf::Texture, be it for a .jpg or a .png on the latter, the application crashes. What could be it? Is there already a working sf::InputStream derivation out there?

Here are my codes, you can even try to to compile them by yourself. No custom libraries and all. I wonder if it's a problem of my machine?

#pragma once

// "EncryptedStream.h"
// ---

#include <string>
#include <cstdio>
#include <stdexcept>

#include <SFML/System/InputStream.hpp>

namespace ds {

        class FileCouldntOpen : public std::runtime_error {
                public:
                        FileCouldntOpen(const std::string& filePath);

                        const std::string& filePath() const;

                private:
                        std::string m_filePath;
        };

        // ---

        void encryptFile (const std::string& inputPath, const std::string& outputPath);

        class EncryptedStream : public sf::InputStream {
                public:
                        EncryptedStream();
                        EncryptedStream(const EncryptedStream&) =delete;
                        EncryptedStream(const std::string& filePath);

                        ~EncryptedStream();

                        void open (const std::string& filePath);
                        void close();

                        sf::Int64 read (void*, sf::Int64);
                        sf::Int64 seek (sf::Int64 position);
                        sf::Int64 tell();
                        sf::Int64 getSize();

                private:
                        void decrypt (char*, int);

                        std::FILE* m_file;
        };

}
 

#include <do/EncryptedStream.h>

// "EncryptedStream.cpp"
// ---

namespace ds {

        FileCouldntOpen::FileCouldntOpen(const std::string& filePath)
                : m_filePath(filePath),
                  std::runtime_error("Couldn't open " + filePath)
        {       }

        const std::string& FileCouldntOpen::filePath() const {
                return m_filePath;
        }

        // ---

        EncryptedStream::EncryptedStream()
                : m_file(nullptr)
        {       }

        EncryptedStream::EncryptedStream
                (const std::string& filePath)
        {
                open(filePath);
        }

        void EncryptedStream::open (const std::string& filePath) {
                if (m_file)
                        std::fclose(m_file);

                m_file = std::fopen(filePath.c_str(), "rb");

                if (!m_file)
                        throw FileCouldntOpen(filePath);
        }

        void EncryptedStream::close() {
                if (m_file)
                        std::fclose(m_file);
        }

        void encrypt (const std::string& inputPath, const std::string& outputPath) {
                // ...
        }

        void EncryptedStream::decrypt (char* data, int size) {
                // ...
        }

        // ---

        sf::Int64 EncryptedStream::read (void* data, sf::Int64 size) {
                if (m_file) {
                        sf::Int64 result = std::fread(data, 1, static_cast<std::size_t>(size), m_file);
                        decrypt(static_cast<char*>(data), size);

                        return result;
                }
                else {
                        return -1;
                }
        }

        sf::Int64 EncryptedStream::seek (sf::Int64 position) {
                if (m_file) {
                        std::fseek(m_file, static_cast<std::size_t>(position), SEEK_SET);
                        return tell();
                }
                else {
                        return -1;
                }
        }

        sf::Int64 EncryptedStream::tell() {
                if (m_file)
                        return std::ftell(m_file);
                else
                        return -1;
        }

        sf::Int64 EncryptedStream::getSize() {
                if (m_file) {
                        sf::Int64 position = tell();
                        std::fseek(m_file, 0, SEEK_END);
                        sf::Int64 size = tell();
                        seek(position);
                        return size;
                }
                else {
                        return -1;
                }
        }

        // ---

        EncryptedStream::~EncryptedStream() {
                if (m_file)
                        std::fclose(m_file);
        }

}

 

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
Re: Problems with making my own InputStream
« Reply #1 on: November 17, 2013, 04:33:39 am »
Pardon my incovenience. After 4 hours of debugging, apparently the only mistake was here:

        EncryptedStream::EncryptedStream()
                : m_file(nullptr)
        {       }

        EncryptedStream::EncryptedStream
                (const std::string& filePath)
        {
                open(filePath);
        }
 

The excerpt of code above would rather had been:

        EncryptedStream::EncryptedStream()
                : m_file(nullptr)
        {       }

        EncryptedStream::EncryptedStream(const std::string& filePath)
                : m_file(nullptr) // The lack of this was hindered by my silly typing of the parameters
        {
                open(filePath);
        }
 

The application crashed because of this line, in my open() method:

        void EncryptedStream::open (const std::string& filePath) {
                if (m_file) // Without default-setting m_file to nullptr, I'd often try to close a nonexistent address.
                        std::fclose(m_file);

                m_file = std::fopen(filePath.c_str(), "rb");

                if (!m_file)
                        throw FileCouldntOpen(filePath);
        }
 

All working, tout de suit! Who wants a free InputStream class for simple encrypted files?

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Problems with making my own InputStream
« Reply #2 on: November 17, 2013, 04:41:49 am »
I don't, :P but I found the error by eye in like 5 minutes and then confirmed with debugger in 1 and was about to post. :-X This is really basic. There's also a wiki if you want to share code or examples.
Don't get discouraged, I had a stupid error(buffer overrun) too just now. :P
« Last Edit: November 17, 2013, 04:47:42 am by FRex »
Back to C++ gamedev with SFML in May 2023

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Problems with making my own InputStream
« Reply #3 on: November 17, 2013, 11:30:07 am »
You should also consider the rule of 3 (or 5 in C++11): If you delete the copy constructor, delete also the copy assignment operator. And you could reuse the close() method in the destructor. And is there a reason why you the C standard library instead of C++ file streams? They make your life easier, and would also have prevented this bug.

But if you need 4 hours to find this crash, you definitely use the wrong tools. With a reasonable debugger, this would have been a task of a few minutes at most. Either it stops exactly at the wrong line, or you step through it until you see that something's wrong...
« Last Edit: November 17, 2013, 11:31:43 am by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

wintertime

  • Sr. Member
  • ****
  • Posts: 255
    • View Profile
Re: Problems with making my own InputStream
« Reply #4 on: November 17, 2013, 12:13:32 pm »
I personally find it acceptable to use fopen, fread, fclose, ... for something simple as this and hide it inside a single wrapper class. Thats because the C++ streams come with all kind of clutter, for example, codecvt stuff is not needed for a binary file.
The problem I see with the above implementation is that it is useless for any encryption that makes sense. Xor garbling with a single nonchanging 8 bit key and also without factoring in previous data does not, although its the favorite of many people new to encryption for its simpleness. An even halfway secure encryption would not allow you to take random pieces from the middle of the file and give back something other than garbage.
I think the way to go would be not writing a sf::InputStream, loading the whole file into memory, decrypting and using loadFromMemory. It may read in more than needed, but the single sequential read will help the filecache, so you need to profile. Though if you memory map the file instead, it is imho clearly better to map it in whole, as you have it possibly faster when the OS automatically reads only needed pieces and it uses less swap space when its directly backed by the source file.
« Last Edit: November 17, 2013, 12:27:36 pm by wintertime »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
AW: Problems with making my own InputStream
« Reply #5 on: November 17, 2013, 12:53:40 pm »
I'm not an expert either, but simply googling reveals that the common encryption algorithm are implemented both for stream and block memory, so there shouldn't be an issue. ;)

Also there's no real need to use C functions, but it's also not completely wrong.
« Last Edit: November 17, 2013, 12:55:31 pm by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

wintertime

  • Sr. Member
  • ****
  • Posts: 255
    • View Profile
Re: Problems with making my own InputStream
« Reply #6 on: November 17, 2013, 01:07:31 pm »
I would not really call me an expert on this, too. ;)
But the stream vs block cipher is something only halfway related. You can make a block cipher that encrypts single blocks, but if you have many that could make breaking the code easier for an attacker, thats why you would generally factor in the previous block and the current block and only then encrypt it with the block encryption, and that makes it impossible to decrypt a block from the middle without recursively having decrypted the previous blocks.

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
Re: Problems with making my own InputStream
« Reply #7 on: November 17, 2013, 07:18:32 pm »
At first I used <fstream>, but because of bugs unrelated to the final matter, I decided to just copy away Laurent's example - which uses C functions. The initial bugs were one of the factors that really stretched the time - the application wouldn't crash, but resources weren't loading properly -, but I also don't have an habit of using debuggers and rather I resort to spreading couts through the code, so that's it as well.  But thanks for the insight! If anything else happens, I'll come back here to pester you guys again. ;D

Nevertheless, I was aiming for very simply encrypting here, like, XOR cyphers. I just don't want your commoner user to be able to mess around with the game's files.

wintertime

  • Sr. Member
  • ****
  • Posts: 255
    • View Profile
Re: Problems with making my own InputStream
« Reply #8 on: November 18, 2013, 12:53:10 am »
Normally people would just look for a premade encryption library, because they are made by experts. Xor with a single byte is only 256 different keys and someone can easily write a script to check all keys in a few seconds.
Though brute forcing is not even necessary, as you still have the problem of storing the key. The program needs it somehow and if its included people will find it. You know, debuggers are not only a nice tool for debugging, but can show the data in memory that your own program decrypted itself for its own use. Or they just change a variable to cheat the game into thinking they played through till where its shown and displaying it.
You may as well just pump it through zlib and give it a wrong file extension. Thats about as unsafe as the xor, but makes it use less space and people will not recognize text containing spoilers immediately in Notepad.

Btw., may I ask if you already have your game ready with code, sound, art pieces and all? If not that may be a better use of your time. ;)

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
Re: Problems with making my own InputStream
« Reply #9 on: November 18, 2013, 02:04:47 am »
Normally people would just look for a premade encryption library, because they are made by experts. Xor with a single byte is only 256 different keys and someone can easily write a script to check all keys in a few seconds.
Kind of useless to argue about better encryption when that's not the aim. He doesn't want to make sure the NSA can't read his game assets, but he wants to prevent that anyone can just go and copy the assets. By Xor-ing everything they'd at least have to do something to get there. :)

Not to forget:
  • Regardless of how you encrypt your stuff, as long as your application can open it, there's a way to reverse engineer the application and open the assets as well, making it extreme complex is only waste of time, especially given the probably not that valuable assets.
  • Encryption does have an overhead, whether you pre-load everything or load it on the fly and complex encryption can steal valuable seconds.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

wintertime

  • Sr. Member
  • ****
  • Posts: 255
    • View Profile
Re: Problems with making my own InputStream
« Reply #10 on: November 18, 2013, 01:28:00 pm »
Actually I tried to tell him its getting broken anyway even with a good library, so its wasted effort to implement a slight xor-obscurification when he gets that for free from zlib with an included size saving (maybe just replace the header bytes through code).

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
Re: Problems with making my own InputStream
« Reply #11 on: November 18, 2013, 02:05:14 pm »
Actually I tried to tell him its getting broken anyway even with a good library, so its wasted effort to implement a slight xor-obscurification when he gets that for free from zlib with an included size saving (maybe just replace the header bytes through code).
You essentially could to both, it's just that by now zip is something most users are familiar with and it's as easy as to right click on the file and chose "unpack archive", while an ciphered file requires the key to be either reverse engineered or brute forced and then deciphered. For someone like you and someone that really wants to get there it's no problem to do so, but it will still throw off a larger group of people.

Not sure how much work it is to make his implementation fit, but if it isn't that much then it really doesn't matter if he now throws in a cipher or not. ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Celtic Minstrel

  • Jr. Member
  • **
  • Posts: 80
    • View Profile
Re: Problems with making my own InputStream
« Reply #12 on: November 18, 2013, 03:56:52 pm »
You essentially could to both, it's just that by now zip is something most users are familiar with and it's as easy as to right click on the file and chose "unpack archive",
But this only works if the file extension is ".zip". Change your zip file's extension to something random, and most people won't even realize it's a zip file.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
Re: Problems with making my own InputStream
« Reply #13 on: November 18, 2013, 04:11:47 pm »
Of course not everyone knows everything etc. 7zip users can just right click it. My point is, that it's still harder to crack a simple cipher than to simply unpack and maybe optionally rename a file. For instance if I could simply unpack the file I might just use the data, but if I have to write an application to crack the cipher and all, I'll probably just go: meh
And telling someone "just rename and unpack it" is quite a bit easier than to explain what steps need to be done to get the data.

Anyways I feel I've been going in a loop here, so I'll just let it go, it's not like it would matter. ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Celtic Minstrel

  • Jr. Member
  • **
  • Posts: 80
    • View Profile
Re: Problems with making my own InputStream
« Reply #14 on: November 18, 2013, 04:39:11 pm »
Of course not everyone knows everything etc. 7zip users can just right click it.
So 7zip users can right-click (for example) a .uqm file and unpack it? (This is a real file extension, used by The Ur-Quan Masters aka Star Control 2.)