From 63c0977ed67baedeaefb8ce152bc7d4aa9397ba5 Mon Sep 17 00:00:00 2001
From: Syping <schiedelrafael@keppe.org>
Date: Thu, 26 Aug 2021 00:22:11 +0200
Subject: [PATCH] add error function, README and CI

---
 .ci/ubuntu_install.sh        |   3 +
 .github/workflows/ubuntu.yml |  24 ++++
 CMakeLists.txt               |  30 +++--
 README.md                    |  35 ++++++
 src/RagePhoto-Extract.cpp    | 103 ++++++++++++------
 src/RagePhoto.cpp            | 205 +++++++++++++++++++++++++----------
 src/RagePhoto.h              |  46 +++++++-
 7 files changed, 340 insertions(+), 106 deletions(-)
 create mode 100755 .ci/ubuntu_install.sh
 create mode 100644 .github/workflows/ubuntu.yml
 create mode 100644 README.md

diff --git a/.ci/ubuntu_install.sh b/.ci/ubuntu_install.sh
new file mode 100755
index 0000000..e596a01
--- /dev/null
+++ b/.ci/ubuntu_install.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+apt-get update -qq
+apt-get install cmake git gcc g++ make ninja-build -qq
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
new file mode 100644
index 0000000..9bb3935
--- /dev/null
+++ b/.github/workflows/ubuntu.yml
@@ -0,0 +1,24 @@
+name: Ubuntu
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+env:
+  BUILD_TYPE: Release
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with: 
+        submodules: recursive
+    - name: Install packages
+      run: sudo .ci/ubuntu_install.sh
+    - name: Configure CMake
+      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -GNinja
+    - name: Build
+      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab1fb75..7ba6196 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,14 +6,20 @@ set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 if (WIN32)
-    set(LIBRAGEPHOTO_NAME libragephoto)
-    set(LIBRAGEPHOTO_DEFINES USE_WINAPI)
+    list(APPEND LIBRAGEPHOTO_DEFINES
+        USE_WINAPI
+    )
+endif()
+
+message("-- Testing iconv")
+try_run(ICONV_RUN ICONV_COMPILE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/tests/IconvTest.cpp)
+if (ICONV_COMPILE)
+    list(APPEND LIBRAGEPHOTO_DEFINES
+        USE_ICONV
+    )
+    message("-- Testing iconv - yes")
 else()
-    set(LIBRAGEPHOTO_NAME ragephoto)
-    try_run(ICONV_RUN ICONV_COMPILE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/tests/IconvTest.cpp)
-    if (ICONV_COMPILE)
-        set(LIBRAGEPHOTO_DEFINES USE_ICONV)
-    endif()
+    message("-- Testing iconv - no")
 endif()
 
 option(WITH_BENCHMARK "Benchmark RagePhoto Parsing Engine" OFF)
@@ -23,17 +29,17 @@ if (WITH_BENCHMARK)
     )
 endif()
 
-project(${LIBRAGEPHOTO_NAME} LANGUAGES CXX)
-add_library(${LIBRAGEPHOTO_NAME} SHARED
+project(ragephoto LANGUAGES CXX)
+add_library(ragephoto SHARED
     src/libragephoto_global.h
     src/RagePhoto.cpp
     src/RagePhoto.h
 )
-target_compile_definitions(${LIBRAGEPHOTO_NAME} PRIVATE
+target_compile_definitions(ragephoto PRIVATE
     LIBRAGEPHOTO_LIBRARY
     ${LIBRAGEPHOTO_DEFINES}
 )
-install(TARGETS ${LIBRAGEPHOTO_NAME} DESTINATION lib)
+install(TARGETS ragephoto DESTINATION lib)
 install(FILES
     src/RagePhoto.h
     src/libragephoto_global.h
@@ -47,5 +53,5 @@ add_executable(ragephoto-extract
     src/RagePhoto-Extract.cpp
     src/RagePhoto.h
 )
-target_link_libraries(ragephoto-extract ${LIBRAGEPHOTO_NAME})
+target_link_libraries(ragephoto-extract ragephoto)
 install(TARGETS ragephoto-extract DESTINATION bin)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6abfa6e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+## libragephoto
+Open Source RAGE Photo Parser for GTA V
+
+- Read RAGE Photos error free and correct
+- Support for metadata stored in RAGE Photos
+- Export RAGE Photos to jpeg with ragephoto-extract
+- High Efficient and Simple C++ API
+
+#### Build libragephoto & ragephoto-extract
+
+```bash
+git clone https://gitlab.com/Syping/libragephoto && cd libragephoto
+mkdir -p build && cd build
+cmake ../
+make -j $(nproc --all)
+sudo make install
+```
+
+#### How to Use libragephoto
+
+```cpp
+RagePhoto ragePhoto;
+bool loaded = ragePhoto.load(data, size);
+const char* photoData = ragePhoto.photoData();
+uint32_t photoSize = ragePhoto.photoSize();
+std::string json = ragePhoto.json();
+std::string title = ragePhoto.title();
+RagePhoto::Error error = ragePhoto.error();
+```
+
+#### How to Use ragephoto-extract
+
+```bash
+ragephoto-extract PGTA5123456789 photo.jpg
+```
diff --git a/src/RagePhoto-Extract.cpp b/src/RagePhoto-Extract.cpp
index 79b9f00..260fec3 100644
--- a/src/RagePhoto-Extract.cpp
+++ b/src/RagePhoto-Extract.cpp
@@ -22,48 +22,81 @@
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
-        std::cout << "Usage: " << argv[0] << " [snapmatic] [jpegout]" << std::endl;
+        std::cout << "Usage: " << argv[0] << " [photo] [jpegout]" << std::endl;
         return 0;
     }
 
-    // Initialise RagePhoto
-    RagePhoto ragePhoto;
+    // Make it crash when RagePhoto have a bug which make the deinitialisation to fail
+    {
+        // Initialise RagePhoto
+        RagePhoto ragePhoto;
 
-    // Read file
-    FILE *file = fopen(argv[1], "rb");
-    if (!file)
-        return -1;
-    const int fseek_end_value = fseek(file, 0, SEEK_END);
-    if (fseek_end_value == -1) {
+        // Read file
+        FILE *file = fopen(argv[1], "rb");
+        if (!file) {
+            std::cout << "Failed to open import file" << std::endl;
+            return -1;
+        }
+        const int fseek_end_value = fseek(file, 0, SEEK_END);
+        if (fseek_end_value == -1) {
+            fclose(file);
+            std::cout << "Failed to read file" << std::endl;
+            return -1;
+        }
+        const size_t file_size = ftell(file);
+        if (file_size == -1) {
+            fclose(file);
+            std::cout << "Failed to read file" << std::endl;
+            return -1;
+        }
+        const int fseek_set_value = fseek(file, 0, SEEK_SET);
+        if (fseek_set_value == -1) {
+            fclose(file);
+            std::cout << "Failed to read file" << std::endl;
+            return -1;
+        }
+        char *data = static_cast<char*>(malloc(file_size));
+        const size_t file_rsize = fread(data, 1, file_size, file);
+        if (file_size != file_rsize) {
+            fclose(file);
+            std::cout << "Failed to read file" << std::endl;
+            return -1;
+        }
         fclose(file);
-        return -1;
-    }
-    size_t file_size = ftell(file);
-    if (file_size == -1) {
-        fclose(file);
-        return -1;
-    }
-    const int fseek_set_value = fseek(file, 0, SEEK_SET);
-    if (fseek_set_value == -1) {
-        fclose(file);
-        return -1;
-    }
-    char *data = static_cast<char*>(malloc(file_size));
-    const size_t file_rsize = fread(data, 1, file_size, file);
-    if (file_size != file_rsize) {
-        fclose(file);
-        return -1;
-    }
 
-    ragePhoto.load(data, file_size);
-    fclose(file);
+        // Load Photo
+        const bool loaded = ragePhoto.load(data, file_size);
 
-    // Write jpeg
-    file = fopen(argv[2], "wb");
-    if (!file)
-        return -1;
-    fwrite(ragePhoto.photoData(), sizeof(char), ragePhoto.photoSize(), file);
-    fclose(file);
+        // Deinitialise data after Photo loaded
+        free(data);
+
+        if (!loaded) {
+            const RagePhoto::Error error = ragePhoto.error();
+            if (error <= RagePhoto::Error::PhotoReadError) {
+                std::cout << "Failed to load photo" << std::endl;
+                return 1;
+            }
+        }
+
+        // Write jpeg
+        file = fopen(argv[2], "wb");
+        if (!file) {
+            std::cout << "Failed to open export file" << std::endl;
+            return -1;
+        }
+        const size_t written = fwrite(ragePhoto.photoData(), sizeof(char), ragePhoto.photoSize(), file);
+        fclose(file);
+
+        if (written != ragePhoto.photoSize()) {
+            std::cout << "Failed to write file" << std::endl;
+            return -1;
+        }
+
+        std::cout << "Photo successfully exported" << std::endl;
+
+        // Clear RagePhoto (provocate crash when pointer leak)
+        ragePhoto.clear();
+    }
 
     return 0;
 }
diff --git a/src/RagePhoto.cpp b/src/RagePhoto.cpp
index 3fba132..2624479 100644
--- a/src/RagePhoto.cpp
+++ b/src/RagePhoto.cpp
@@ -31,7 +31,9 @@
 
 RagePhoto::RagePhoto()
 {
+    p_photoLoaded = false;
     p_photoData = nullptr;
+    p_error = Error::Uninitialised;
 }
 
 RagePhoto::~RagePhoto()
@@ -39,29 +41,52 @@ RagePhoto::~RagePhoto()
     free(p_photoData);
 }
 
+void RagePhoto::clear()
+{
+    if (p_photoLoaded) {
+        free(p_photoData);
+        p_photoData = nullptr;
+        p_photoLoaded = false;
+    }
+    p_descriptionString.clear();
+    p_jsonString.clear();
+    p_photoString.clear();
+    p_titleString.clear();
+    p_error = Error::Uninitialised;
+}
+
 bool RagePhoto::load(const char *data, size_t length)
 {
 #ifdef RAGEPHOTO_BENCHMARK
     auto benchmark_parse_start = std::chrono::high_resolution_clock::now();
 #endif
 
+    // Avoid data conflicts
+    clear();
+
     size_t pos = 0;
     char uInt32Buffer[4];
-    size_t size = bRead(data, uInt32Buffer, &pos, 4, length);
-    if (size != 4)
+    size_t size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+    if (size != 4) {
+        p_error = Error::NoFormatIdentifier; // 1
         return false;
+    }
 
     uint32_t format = charToUInt32LE(uInt32Buffer);
     if (format == static_cast<uint32_t>(PhotoFormat::GTA5)) {
         char photoHeader[256];
-        size = bRead(data, photoHeader, &pos, 256, length);
-        if (size != 256)
+        size = readBuffer(data, photoHeader, &pos, 256, length);
+        if (size != 256) {
+            p_error = Error::IncompleteHeader; // 3
             return false;
+        }
 
 #ifdef USE_ICONV
         iconv_t iconv_in = iconv_open("UTF-8", "UTF-16LE");
-        if (iconv_in == (iconv_t)-1)
+        if (iconv_in == (iconv_t)-1) {
+            p_error = Error::UnicodeInitError; // 4
             return false;
+        }
         char photoString[256];
         size_t src_s = sizeof(photoHeader);
         size_t dst_s = sizeof(photoString);
@@ -69,138 +94,193 @@ bool RagePhoto::load(const char *data, size_t length)
         char *dst = photoString;
         size_t ret = iconv(iconv_in, &src, &src_s, &dst, &dst_s);
         iconv_close(iconv_in);
-        if (ret == static_cast<size_t>(-1))
+        if (ret == static_cast<size_t>(-1)) {
+            p_error = Error::UnicodeHeaderError; // 5
             return false;
+        }
         p_photoString = std::string(photoString);
 #endif
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteChecksum; // 6
             return false;
+        }
         p_headerSum = charToUInt32LE(uInt32Buffer);
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteEOF; // 7
             return false;
+        }
         p_endOfFile = charToUInt32LE(uInt32Buffer);
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteJsonOffset; // 8
             return false;
+        }
         p_jsonOffset = charToUInt32LE(uInt32Buffer);
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteTitleOffset; // 9
             return false;
+        }
         p_titlOffset = charToUInt32LE(uInt32Buffer);
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteDescOffset; // 10
             return false;
+        }
         p_descOffset = charToUInt32LE(uInt32Buffer);
 
         char markerBuffer[4];
-        size = bRead(data, markerBuffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, markerBuffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteJpegMarker; // 11
             return false;
-        if (strncmp(markerBuffer, "JPEG", 4) != 0)
+        }
+        if (strncmp(markerBuffer, "JPEG", 4) != 0) {
+            p_error = Error::IncorrectJpegMarker; // 12
             return false;
+        }
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompletePhotoBuffer; // 13
             return false;
+        }
         p_photoBuffer = charToUInt32LE(uInt32Buffer);
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompletePhotoSize; // 14
             return false;
+        }
         p_photoSize = charToUInt32LE(uInt32Buffer);
 
         p_photoData = static_cast<char*>(malloc(p_photoSize));
         if (!p_photoData) {
+            p_error = Error::PhotoMallocError; // 15
             return false;
         }
-        size = bRead(data, p_photoData, &pos, p_photoSize, length);
+        size = readBuffer(data, p_photoData, &pos, p_photoSize, length);
         if (size != p_photoSize) {
             free(p_photoData);
+            p_photoData = nullptr;
+            p_error = Error::PhotoReadError; // 16
+            return false;
+        }
+        p_photoLoaded = true;
+
+        pos = p_jsonOffset + 264;
+        size = readBuffer(data, markerBuffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteJsonMarker; // 17
+            return false;
+        }
+        if (strncmp(markerBuffer, "JSON", 4) != 0) {
+            p_error = Error::IncorrectJsonMarker; // 18
             return false;
         }
 
-        pos = p_jsonOffset + 264;
-        size = bRead(data, markerBuffer, &pos, 4, length);
-        if (size != 4)
-            return false;
-        if (strncmp(markerBuffer, "JSON", 4) != 0)
-            return false;
-
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteJsonBuffer; // 19
             return false;
+        }
         p_jsonBuffer = charToUInt32LE(uInt32Buffer);
 
         char *t_jsonData = static_cast<char*>(malloc(p_jsonBuffer));
-        if (!t_jsonData)
+        if (!t_jsonData) {
+            p_error = Error::JsonMallocError; // 20
             return false;
-        size = bRead(data, t_jsonData, &pos, p_jsonBuffer, length);
+        }
+        size = readBuffer(data, t_jsonData, &pos, p_jsonBuffer, length);
         if (size != p_jsonBuffer) {
             free(t_jsonData);
+            p_error = Error::JsonReadError; // 21
             return false;
         }
         p_jsonString = std::string(t_jsonData);
         free(t_jsonData);
 
         pos = p_titlOffset + 264;
-        size = bRead(data, markerBuffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, markerBuffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteTitleMarker; // 22
             return false;
-        if (strncmp(markerBuffer, "TITL", 4) != 0)
+        }
+        if (strncmp(markerBuffer, "TITL", 4) != 0) {
+            p_error = Error::IncorrectTitleMarker; // 23
             return false;
+        }
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteTitleBuffer; // 24
             return false;
+        }
         p_titlBuffer = charToUInt32LE(uInt32Buffer);
 
         char *t_titlData = static_cast<char*>(malloc(p_titlBuffer));
-        if (!t_titlData)
+        if (!t_titlData) {
+            p_error = Error::TitleMallocError; // 25
             return false;
-        size = bRead(data, t_titlData, &pos, p_titlBuffer, length);
+        }
+        size = readBuffer(data, t_titlData, &pos, p_titlBuffer, length);
         if (size != p_titlBuffer) {
             free(t_titlData);
+            p_error = Error::TitleReadError; // 26
             return false;
         }
         p_titleString = std::string(t_titlData);
         free(t_titlData);
 
         pos = p_descOffset + 264;
-        size = bRead(data, markerBuffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, markerBuffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteDescMarker; // 27
             return false;
-        if (strncmp(markerBuffer, "DESC", 4) != 0)
+        }
+        if (strncmp(markerBuffer, "DESC", 4) != 0) {
+            p_error = Error::IncorrectDescMarker; // 28
             return false;
+        }
 
-        size = bRead(data, uInt32Buffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, uInt32Buffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteDescBuffer; // 29
             return false;
+        }
         p_descBuffer = charToUInt32LE(uInt32Buffer);
 
         char *t_descData = static_cast<char*>(malloc(p_descBuffer));
-        if (!t_descData)
+        if (!t_descData) {
+            p_error = Error::DescMallocError; // 30
             return false;
-        size = bRead(data, t_descData, &pos, p_descBuffer, length);
+        }
+        size = readBuffer(data, t_descData, &pos, p_descBuffer, length);
         if (size != p_descBuffer) {
             free(t_descData);
+            p_error = Error::DescReadError; // 31
             return false;
         }
         p_descriptionString = std::string(t_descData);
         free(t_descData);
 
         pos = p_endOfFile + 260;
-        size = bRead(data, markerBuffer, &pos, 4, length);
-        if (size != 4)
+        size = readBuffer(data, markerBuffer, &pos, 4, length);
+        if (size != 4) {
+            p_error = Error::IncompleteJendMarker; // 32
             return false;
-        if (strncmp(markerBuffer, "JEND", 4) != 0)
+        }
+        if (strncmp(markerBuffer, "JEND", 4) != 0) {
+            p_error = Error::IncorrectJendMarker; // 33
             return false;
+        }
 
 #ifdef RAGEPHOTO_BENCHMARK
         auto benchmark_parse_end = std::chrono::high_resolution_clock::now();
@@ -208,19 +288,32 @@ bool RagePhoto::load(const char *data, size_t length)
         std::cout << "Benchmark: " << benchmark_ns.count() << "ns" << std::endl;
 #endif
 
+        p_error = Error::NoError; // 255
         return true;
     }
+    p_error = Error::IncompatibleFormat; // 2
     return false;
 }
 
+RagePhoto::Error RagePhoto::error()
+{
+    return p_error;
+}
+
 const char* RagePhoto::photoData()
 {
-    return p_photoData;
+    if (p_photoLoaded)
+        return p_photoData;
+    else
+        return nullptr;
 }
 
 const uint32_t RagePhoto::photoSize()
 {
-    return p_photoSize;
+    if (p_photoLoaded)
+        return p_photoSize;
+    else
+        return 0;
 }
 
 const std::string RagePhoto::description()
@@ -243,7 +336,7 @@ const std::string RagePhoto::title()
     return p_titleString;
 }
 
-size_t RagePhoto::bRead(const char *input, char *output, size_t *pos, size_t len)
+size_t RagePhoto::readBuffer(const char *input, char *output, size_t *pos, size_t len)
 {
 #ifdef READ_USE_FOR
     for (size_t i = 0; i < len; i++) {
@@ -256,7 +349,7 @@ size_t RagePhoto::bRead(const char *input, char *output, size_t *pos, size_t len
     return len;
 }
 
-size_t RagePhoto::bRead(const char *input, char *output, size_t *pos, size_t len, size_t inputLen)
+size_t RagePhoto::readBuffer(const char *input, char *output, size_t *pos, size_t len, size_t inputLen)
 {
     size_t readLen = 0;
     if (*pos >= inputLen)
diff --git a/src/RagePhoto.h b/src/RagePhoto.h
index 8f4a899..51e29ea 100644
--- a/src/RagePhoto.h
+++ b/src/RagePhoto.h
@@ -27,14 +27,52 @@
 class LIBRAGEPHOTO_EXPORT RagePhoto
 {
 public:
+    enum class Error : uint8_t {
+        DescMallocError = 30,
+        DescReadError = 31,
+        IncompatibleFormat = 2,
+        IncompleteChecksum = 6,
+        IncompleteDescBuffer = 29,
+        IncompleteDescMarker = 27,
+        IncompleteDescOffset = 10,
+        IncompleteEOF = 7,
+        IncompleteHeader = 3,
+        IncompleteJendMarker = 32,
+        IncompleteJpegMarker = 11,
+        IncompleteJsonBuffer = 19,
+        IncompleteJsonMarker = 17,
+        IncompleteJsonOffset = 8,
+        IncompletePhotoBuffer = 13,
+        IncompletePhotoSize = 14,
+        IncompleteTitleBuffer = 24,
+        IncompleteTitleMarker = 22,
+        IncompleteTitleOffset = 9,
+        IncorrectDescMarker = 28,
+        IncorrectJendMarker = 33,
+        IncorrectJpegMarker = 12,
+        IncorrectJsonMarker = 18,
+        IncorrectTitleMarker = 23,
+        JsonMallocError = 20,
+        JsonReadError = 21,
+        NoError = 255,
+        NoFormatIdentifier = 1,
+        PhotoMallocError = 15,
+        PhotoReadError = 16,
+        TitleMallocError = 25,
+        TitleReadError = 26,
+        UnicodeHeaderError = 5,
+        UnicodeInitError = 4,
+        Uninitialised = 0,
+    };
     enum class PhotoFormat : uint32_t {
         GTA5 = 0x01000000U,
         RDR2 = 0x04000000U,
-        Undefined = 0,
     };
     RagePhoto();
     ~RagePhoto();
+    void clear();
     bool load(const char *data, size_t length);
+    Error error();
     const char *photoData();
     const uint32_t photoSize();
     const std::string description();
@@ -43,13 +81,15 @@ public:
     const std::string title();
 
 private:
-    inline size_t bRead(const char *input, char *output, size_t *pos, size_t len);
-    inline size_t bRead(const char *input, char *output, size_t *pos, size_t len, size_t inputLen);
+    inline size_t readBuffer(const char *input, char *output, size_t *pos, size_t len);
+    inline size_t readBuffer(const char *input, char *output, size_t *pos, size_t len, size_t inputLen);
     inline uint32_t charToUInt32BE(char *x);
     inline uint32_t charToUInt32LE(char *x);
     inline void uInt32ToCharBE(uint32_t x, char *y);
     inline void uInt32ToCharLE(uint32_t x, char *y);
+    bool p_photoLoaded;
     char* p_photoData;
+    Error p_error;
     std::string p_descriptionString;
     std::string p_jsonString;
     std::string p_photoString;