[lgogdownloader] 01/04: Imported Upstream version 2.27

Stephen Kitt skitt at moszumanska.debian.org
Fri Mar 18 12:03:08 UTC 2016


This is an automated email from the git hooks/post-receive script.

skitt pushed a commit to branch master
in repository lgogdownloader.

commit 6375924597eaee90bc2d10a616f7e40ae251968f
Author: Stephen Kitt <steve at sk2.org>
Date:   Fri Mar 18 12:47:45 2016 +0100

    Imported Upstream version 2.27
---
 .gitignore                            |   6 +
 CMakeLists.txt                        | 115 +++++++
 Makefile                              | 180 -----------
 README.md                             |   7 +-
 cmake/FindHtmlcxx.cmake               |  54 ++++
 cmake/FindJsoncpp.cmake               |  34 ++
 cmake/FindOAuth.cmake                 |  28 ++
 cmake/FindRhash.cmake                 |  22 ++
 cmake/FindTinyxml.cmake               |  33 ++
 include/api.h                         |   2 +-
 include/config.h                      |   4 +
 include/downloader.h                  |   3 +
 include/gamedetails.h                 |  10 +-
 include/globalconstants.h             |   2 +-
 include/util.h                        |  16 +
 lgogdownloader.cbp                    |  76 -----
 main.cpp                              |  86 +++---
 man/CMakeLists.txt                    |  22 ++
 man/lgogdownloader.supplemental.groff |  26 +-
 src/downloader.cpp                    | 567 +++++++++++++++++++++++-----------
 src/gamedetails.cpp                   |  15 +-
 src/util.cpp                          |  93 +++++-
 version.sh                            |   9 -
 23 files changed, 894 insertions(+), 516 deletions(-)

diff --git a/.gitignore b/.gitignore
index aa3b985..461ec87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,9 @@ bin/*
 obj/*
 *.1
 *.gz
+Makefile
+CMakeCache.txt
+CMakeFiles/
+cmake_install.cmake
+build/
+*.cbp
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..743f955
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,115 @@
+cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
+project (lgogdownloader LANGUAGES CXX VERSION 2.27)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG=1")
+
+find_package(Boost
+  REQUIRED
+  system
+  filesystem
+  regex
+  program_options
+  date_time
+  )
+find_package(CURL REQUIRED)
+find_package(OAuth REQUIRED)
+find_package(Jsoncpp REQUIRED)
+find_package(Htmlcxx REQUIRED)
+find_package(Tinyxml REQUIRED)
+find_package(Rhash REQUIRED)
+
+file(GLOB SRC_FILES
+  main.cpp
+  src/api.cpp
+  src/downloader.cpp
+  src/progressbar.cpp
+  src/util.cpp
+  src/blacklist.cpp
+  src/gamefile.cpp
+  src/gamedetails.cpp
+  )
+
+set(GIT_CHECKOUT FALSE)
+if(EXISTS ${PROJECT_SOURCE_DIR}/.git)
+  if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow)
+    find_package(Git)
+    if(GIT_FOUND)
+      set(GIT_CHECKOUT TRUE)
+    else(GIT_FOUND)
+      message(WARNING "Git executable not found")
+    endif(GIT_FOUND)
+  else(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow)
+    message(STATUS "Shallow Git clone detected, not attempting to retrieve version info")
+  endif(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow)
+endif(EXISTS ${PROJECT_SOURCE_DIR}/.git)
+
+if(GIT_CHECKOUT)
+  execute_process(COMMAND ${GIT_EXECUTABLE} diff --shortstat
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    OUTPUT_VARIABLE GIT_SHORTSTAT
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+  )
+  execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    OUTPUT_VARIABLE GIT_REV_PARSE
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+  )
+  if(GIT_SHORTSTAT)
+    set(GIT_DIRTY ON)
+  endif(GIT_SHORTSTAT)
+
+  if(GIT_DIRTY)
+    set(PROJECT_VERSION_MINOR ${PROJECT_VERSION_MINOR}M)
+  endif(GIT_DIRTY)
+
+  set(PROJECT_VERSION_PATCH ${GIT_REV_PARSE})
+  set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
+endif(GIT_CHECKOUT)
+
+set(VERSION_NUMBER ${PROJECT_VERSION})
+set(VERSION_STRING "LGOGDownloader ${VERSION_NUMBER}")
+
+add_definitions(-D_FILE_OFFSET_BITS=64 -DVERSION_NUMBER="${VERSION_NUMBER}" -DVERSION_STRING="${VERSION_STRING}")
+
+add_executable (${PROJECT_NAME} ${SRC_FILES})
+
+
+target_include_directories(${PROJECT_NAME}
+  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
+  PRIVATE ${Boost_INCLUDE_DIRS}
+  PRIVATE ${CURL_INCLUDE_DIRS}
+  PRIVATE ${OAuth_INCLUDE_DIRS}
+  PRIVATE ${Jsoncpp_INCLUDE_DIRS}
+  PRIVATE ${Htmlcxx_INCLUDE_DIRS}
+  PRIVATE ${Tinyxml_INCLUDE_DIRS}
+  PRIVATE ${Rhash_INCLUDE_DIRS}
+  )
+
+target_link_libraries(${PROJECT_NAME}
+  PRIVATE ${Boost_LIBRARIES}
+  PRIVATE ${CURL_LIBRARIES}
+  PRIVATE ${OAuth_LIBRARIES}
+  PRIVATE ${Jsoncpp_LIBRARIES}
+  PRIVATE ${Htmlcxx_LIBRARIES}
+  PRIVATE ${Tinyxml_LIBRARIES}
+  PRIVATE ${Rhash_LIBRARIES}
+  )
+
+if(MSVC)
+  # Force to always compile with W4
+  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
+    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  else()
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
+  endif()
+elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  # Update if necessary
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-long-long -fexceptions")
+endif()
+
+set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables")
+set(INSTALL_SHARE_DIR share CACHE PATH "Installation directory for resource files")
+
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX} DESTINATION ${INSTALL_BIN_DIR})
+add_subdirectory(man)
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 9e7d4d7..0000000
--- a/Makefile
+++ /dev/null
@@ -1,180 +0,0 @@
-#------------------------------------------------------------------------------#
-# This makefile was generated by 'cbp2make' tool rev.127                       #
-#------------------------------------------------------------------------------#
-
-
-WORKDIR = `pwd`
-
-DESTDIR = 
-PREFIX = /usr
-MANPREFIX = $(PREFIX)/share
-
-CC = gcc
-CXX = g++
-AR = ar
-LD = g++
-WINDRES = windres
-
-INC =  -Iinclude -I/usr/include/rhash -I/usr/include/jsoncpp
-CFLAGS =  -std=c++11 -Wall -fexceptions -D_FILE_OFFSET_BITS=64
-RESINC = 
-LIBDIR = 
-LIB =  -lcurl -loauth -ljsoncpp -lhtmlcxx -lboost_system -lboost_filesystem -lboost_regex -lboost_program_options -lboost_date_time -ltinyxml -lrhash
-LDFLAGS = 
-
-VERSION = -DVERSION_STRING="\"$(shell sh version.sh)\""
-HELP2MAN = $(shell which help2man 2> /dev/null)
-MAN_DIR = man
-MAN_PAGE = lgogdownloader.1
-
-INC_DEBUG =  $(INC)
-CFLAGS_DEBUG =  $(CFLAGS) -g -DDEBUG
-RESINC_DEBUG =  $(RESINC)
-RCFLAGS_DEBUG =  $(RCFLAGS)
-LIBDIR_DEBUG =  $(LIBDIR)
-LIB_DEBUG = $(LIB)
-LDFLAGS_DEBUG =  $(LDFLAGS)
-OBJDIR_DEBUG = obj/Debug
-DEP_DEBUG = 
-OUT_DEBUG = bin/Debug/lgogdownloader
-
-INC_RELEASE =  $(INC)
-CFLAGS_RELEASE =  $(CFLAGS) -O2
-RESINC_RELEASE =  $(RESINC)
-RCFLAGS_RELEASE =  $(RCFLAGS)
-LIBDIR_RELEASE =  $(LIBDIR)
-LIB_RELEASE = $(LIB)
-LDFLAGS_RELEASE =  $(LDFLAGS) -s
-OBJDIR_RELEASE = obj/Release
-DEP_RELEASE = 
-OUT_RELEASE = bin/Release/lgogdownloader
-
-OBJ_DEBUG = $(OBJDIR_DEBUG)/main.o $(OBJDIR_DEBUG)/src/api.o $(OBJDIR_DEBUG)/src/downloader.o $(OBJDIR_DEBUG)/src/progressbar.o $(OBJDIR_DEBUG)/src/util.o $(OBJDIR_DEBUG)/src/blacklist.o $(OBJDIR_DEBUG)/src/gamedetails.o $(OBJDIR_DEBUG)/src/gamefile.o
-
-OBJ_RELEASE = $(OBJDIR_RELEASE)/main.o $(OBJDIR_RELEASE)/src/api.o $(OBJDIR_RELEASE)/src/downloader.o $(OBJDIR_RELEASE)/src/progressbar.o $(OBJDIR_RELEASE)/src/util.o $(OBJDIR_RELEASE)/src/blacklist.o $(OBJDIR_RELEASE)/src/gamedetails.o $(OBJDIR_RELEASE)/src/gamefile.o
-
-all: debug release
-
-clean: clean_debug clean_release
-
-before_debug: 
-	test -d bin/Debug || mkdir -p bin/Debug
-	test -d $(OBJDIR_DEBUG) || mkdir -p $(OBJDIR_DEBUG)
-	test -d $(OBJDIR_DEBUG)/src || mkdir -p $(OBJDIR_DEBUG)/src
-
-after_debug: out_debug
-ifdef HELP2MAN
-	if ! test -f $(MAN_DIR)/$(MAN_PAGE).gz; then \
-		help2man -N -i $(MAN_DIR)/lgogdownloader.supplemental.groff -o $(MAN_DIR)/$(MAN_PAGE) $(OUT_DEBUG); \
-		gzip -f -9 $(MAN_DIR)/$(MAN_PAGE); \
-	fi
-endif
-
-debug: before_debug out_debug after_debug
-
-out_debug: $(OBJ_DEBUG) $(DEP_DEBUG)
-	$(LD) $(LDFLAGS_DEBUG) $(LIBDIR_DEBUG) $(OBJ_DEBUG) $(LIB_DEBUG) -o $(OUT_DEBUG)
-
-$(OBJ_DEBUG): | before_debug
-
-$(OBJDIR_DEBUG)/main.o: main.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(VERSION) $(INC_DEBUG) -c main.cpp -o $(OBJDIR_DEBUG)/main.o
-
-$(OBJDIR_DEBUG)/src/api.o: src/api.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/api.cpp -o $(OBJDIR_DEBUG)/src/api.o
-
-$(OBJDIR_DEBUG)/src/downloader.o: src/downloader.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/downloader.cpp -o $(OBJDIR_DEBUG)/src/downloader.o
-
-$(OBJDIR_DEBUG)/src/progressbar.o: src/progressbar.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/progressbar.cpp -o $(OBJDIR_DEBUG)/src/progressbar.o
-
-$(OBJDIR_DEBUG)/src/util.o: src/util.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/util.cpp -o $(OBJDIR_DEBUG)/src/util.o
-
-$(OBJDIR_DEBUG)/src/blacklist.o: src/blacklist.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/blacklist.cpp -o $(OBJDIR_DEBUG)/src/blacklist.o
-
-$(OBJDIR_DEBUG)/src/gamefile.o: src/gamefile.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/gamefile.cpp -o $(OBJDIR_DEBUG)/src/gamefile.o
-
-$(OBJDIR_DEBUG)/src/gamedetails.o: src/gamedetails.cpp
-	$(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/gamedetails.cpp -o $(OBJDIR_DEBUG)/src/gamedetails.o
-
-clean_debug: 
-	rm -f $(OBJ_DEBUG) $(OUT_DEBUG)
-	rm -rf bin/Debug
-	rm -rf $(OBJDIR_DEBUG)
-	rm -rf $(OBJDIR_DEBUG)/src
-
-before_release: 
-	test -d bin/Release || mkdir -p bin/Release
-	test -d $(OBJDIR_RELEASE) || mkdir -p $(OBJDIR_RELEASE)
-	test -d $(OBJDIR_RELEASE)/src || mkdir -p $(OBJDIR_RELEASE)/src
-
-after_release: out_release
-ifdef HELP2MAN
-	if ! test -f $(MAN_DIR)/$(MAN_PAGE).gz; then \
-		help2man -N -i $(MAN_DIR)/lgogdownloader.supplemental.groff -o $(MAN_DIR)/$(MAN_PAGE) $(OUT_RELEASE); \
-		gzip -f -9 $(MAN_DIR)/$(MAN_PAGE); \
-	fi
-endif
-
-release: before_release out_release after_release
-
-out_release: $(OBJ_RELEASE) $(DEP_RELEASE)
-	$(LD) $(LDFLAGS_RELEASE) $(LIBDIR_RELEASE) $(OBJ_RELEASE) $(LIB_RELEASE) -o $(OUT_RELEASE)
-
-$(OBJ_RELEASE): | before_release
-
-$(OBJDIR_RELEASE)/main.o: main.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(VERSION) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)/main.o
-
-$(OBJDIR_RELEASE)/src/api.o: src/api.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/api.cpp -o $(OBJDIR_RELEASE)/src/api.o
-
-$(OBJDIR_RELEASE)/src/downloader.o: src/downloader.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/downloader.cpp -o $(OBJDIR_RELEASE)/src/downloader.o
-
-$(OBJDIR_RELEASE)/src/progressbar.o: src/progressbar.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/progressbar.cpp -o $(OBJDIR_RELEASE)/src/progressbar.o
-
-$(OBJDIR_RELEASE)/src/util.o: src/util.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/util.cpp -o $(OBJDIR_RELEASE)/src/util.o
-
-$(OBJDIR_RELEASE)/src/blacklist.o: src/blacklist.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/blacklist.cpp -o $(OBJDIR_RELEASE)/src/blacklist.o
-
-$(OBJDIR_RELEASE)/src/gamefile.o: src/gamefile.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/gamefile.cpp -o $(OBJDIR_RELEASE)/src/gamefile.o
-
-$(OBJDIR_RELEASE)/src/gamedetails.o: src/gamedetails.cpp
-	$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/gamedetails.cpp -o $(OBJDIR_RELEASE)/src/gamedetails.o
-
-clean_release: 
-	rm -f $(OBJ_RELEASE) $(OUT_RELEASE)
-	rm -rf bin/Release
-	rm -rf $(OBJDIR_RELEASE)
-	rm -rf $(OBJDIR_RELEASE)/src
-	rm -f $(MAN_DIR)/$(MAN_PAGE) $(MAN_DIR)/$(MAN_PAGE).gz
-
-install: release
-	install -d $(DESTDIR)/$(PREFIX)/bin/
-	if test -f $(OUT_DEBUG); then \
-		install -m 755 $(OUT_DEBUG) $(DESTDIR)/$(PREFIX)/bin/lgogdownloader; \
-	else \
-		install -m 755 $(OUT_RELEASE) $(DESTDIR)/$(PREFIX)/bin/lgogdownloader; \
-	fi
-	if test -f $(MAN_DIR)/$(MAN_PAGE).gz; then \
-		install -d $(DESTDIR)/$(MANPREFIX)/man/man1/; \
-		install -m 644 $(MAN_DIR)/$(MAN_PAGE).gz $(DESTDIR)/$(MANPREFIX)/man/man1/$(MAN_PAGE).gz; \
-	fi
-
-uninstall:
-	rm $(DESTDIR)/$(PREFIX)/bin/lgogdownloader
-	if test -f $(DESTDIR)/$(MANPREFIX)/man/man1/$(MAN_PAGE).gz; then \
-		rm $(DESTDIR)/$(MANPREFIX)/man/man1/$(MAN_PAGE).gz; \
-	fi
-
-.PHONY: before_debug after_debug clean_debug before_release after_release clean_release
-
diff --git a/README.md b/README.md
index 007d181..0d28587 100644
--- a/README.md
+++ b/README.md
@@ -18,11 +18,14 @@ This repository contains the code of unofficial [GOG](http://www.gog.com/) downl
     # apt install build-essential libcurl4-openssl-dev libboost-regex-dev \
     libjsoncpp-dev liboauth-dev librhash-dev libtinyxml-dev libhtmlcxx-dev\
     libboost-system-dev libboost-filesystem-dev libboost-program-options-dev\
-    libboost-date-time-dev help2man
+    libboost-date-time-dev help2man cmake
 
 ## Build and install
 
-    $ make release
+    $ mkdir build
+    $ cd build
+    $ cmake ..
+    $ make
     # sudo make install
 
 ## Use
diff --git a/cmake/FindHtmlcxx.cmake b/cmake/FindHtmlcxx.cmake
new file mode 100644
index 0000000..6dbf4b1
--- /dev/null
+++ b/cmake/FindHtmlcxx.cmake
@@ -0,0 +1,54 @@
+# - Try to find htmlcxx
+#
+# Once done this will define
+#  Htmlcxx_FOUND - System has htmlcxx
+#  Htmlcxx_INCLUDE_DIRS - The htmlcxx include directories
+#  Htmlcxx_LIBRARIES - The libraries needed to use htmlcxx
+
+find_package(PkgConfig)
+pkg_check_modules(PC_HTMLCXX REQUIRED htmlcxx)
+
+find_path(HTMLCXX_INCLUDE_DIR
+  NAMES
+    css/parser.h
+    html/tree.h
+  HINTS
+    ${PC_HTMLCXX_INCLUDEDIR}
+    ${PC_HTMLCXX_INCLUDE_DIRS}
+  PATH_SUFFIXES
+    htmlcxx
+  PATHS
+    ${PC_HTMLCXX_INCLUDE_DIRS}
+  )
+
+find_library(HTMLCXX_LIBRARY_HTMLCXX htmlcxx
+  HINTS
+    ${PC_HTMLCXX_LIBDIR}
+    ${PC_HTMLCXX_LIBRARY_DIRS}
+  PATHS
+    ${PC_HTMLCXX_LIBRARY_DIRS}
+  )
+
+find_library(HTMLCXX_LIBRARY_CSS_PARSER css_parser
+  HINTS
+    ${PC_HTMLCXX_LIBDIR}
+    ${PC_HTMLCXX_LIBRARY_DIRS}
+  PATHS
+    ${PC_HTMLCXX_LIBRARY_DIRS}
+  )
+
+find_library(HTMLCXX_LIBRARY_CSS_PARSER_PP css_parser_pp
+  HINTS
+    ${PC_HTMLCXX_LIBDIR}
+    ${PC_HTMLCXX_LIBRARY_DIRS}
+  PATHS
+    ${PC_HTMLCXX_LIBRARY_DIRS}
+  )
+
+mark_as_advanced(HTMLCXX_INCLUDE_DIR HTMLCXX_LIBRARY_HTMLCXX HTMLCXX_LIBRARY_CSS_PARSER HTMLCXX_LIBRARY_CSS_PARSER_PP)
+
+if(PC_HTMLCXX_FOUND)
+  set(Htmlcxx_FOUND ON)
+  set(Htmlcxx_INCLUDE_DIRS ${HTMLCXX_INCLUDE_DIR})
+  set(Htmlcxx_LIBRARIES ${HTMLCXX_LIBRARY_HTMLCXX} ${HTMLCXX_LIBRARY_CSS_PARSER} ${HTMLCXX_LIBRARY_CSS_PARSER_PP})
+endif(PC_HTMLCXX_FOUND)
diff --git a/cmake/FindJsoncpp.cmake b/cmake/FindJsoncpp.cmake
new file mode 100644
index 0000000..5731980
--- /dev/null
+++ b/cmake/FindJsoncpp.cmake
@@ -0,0 +1,34 @@
+# - Try to find Jsoncpp
+#
+# Once done, this will define
+#  Jsoncpp_FOUND - system has Jsoncpp
+#  Jsoncpp_INCLUDE_DIRS - the Jsoncpp include directories
+#  Jsoncpp_LIBRARIES - link these to use Jsoncpp
+
+find_package(PkgConfig)
+pkg_check_modules(PC_JSONCPP REQUIRED jsoncpp)
+
+find_path(JSONCPP_INCLUDE_DIR
+  NAMES
+    json/features.h
+  HINTS
+    ${PC_JSONCPP_INCLUDEDIR}
+    ${PC_JSONCPP_INCLUDEDIRS}
+  PATH_SUFFIXES
+    jsoncpp
+  PATHS
+    ${PC_JSONCPP_INCLUDE_DIRS}
+  )
+
+find_library(JSONCPP_LIBRARY jsoncpp
+  PATHS
+    ${PC_JSONCPP_LIBRARY_DIRS}
+  )
+
+mark_as_advanced(JSONCPP_INCLUDE_DIR JSONCPP_LIBRARY)
+
+if(PC_JSONCPP_FOUND)
+  set(Jsoncpp_FOUND ON)
+  set(Jsoncpp_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIR})
+  set(Jsoncpp_LIBRARIES ${JSONCPP_LIBRARY})
+endif(PC_JSONCPP_FOUND)
diff --git a/cmake/FindOAuth.cmake b/cmake/FindOAuth.cmake
new file mode 100644
index 0000000..55dbd63
--- /dev/null
+++ b/cmake/FindOAuth.cmake
@@ -0,0 +1,28 @@
+# - Try to find oauth
+#
+# Once done this will define
+#  OAuth_FOUND - System has oauth
+#  OAuth_INCLUDE_DIRS - The oauth include directories
+#  OAuth_LIBRARIES - The libraries needed to use oauth
+
+find_package(PkgConfig)
+pkg_check_modules(PC_OAUTH REQUIRED oauth)
+
+find_path(OAUTH_INCLUDE_DIR oauth.h
+  HINTS ${PC_OAUTH_INCLUDEDIR}
+  ${PC_OAUTH_INCLUDE_DIRS}
+  PATH_SUFFIXES oauth
+  )
+
+find_library(OAUTH_LIBRARY NAMES oauth
+  HINTS ${PC_OAUTH_LIBDIR}
+  ${PC_OAUTH_LIBRARY_DIRS}
+  )
+
+mark_as_advanced(OAUTH_INCLUDE_DIR OAUTH_LIBRARY)
+
+if(PC_OAUTH_FOUND)
+  set(OAuth_FOUND ON)
+  set(OAuth_INCLUDE_DIRS ${OAUTH_INCLUDE_DIR})
+  set(OAuth_LIBRARIES ${OAUTH_LIBRARY})
+endif(PC_OAUTH_FOUND)
diff --git a/cmake/FindRhash.cmake b/cmake/FindRhash.cmake
new file mode 100644
index 0000000..cdfecd3
--- /dev/null
+++ b/cmake/FindRhash.cmake
@@ -0,0 +1,22 @@
+# - Try to find rhash
+#
+# Once done this will define
+#  Rhash_FOUND - System has rhash
+#  Rhash_INCLUDE_DIRS - The rhash include directories
+#  Rhash_LIBRARIES - The libraries needed to use rhash
+
+find_path(RHASH_INCLUDE_DIR rhash.h)
+find_library(RHASH_LIBRARY rhash)
+
+mark_as_advanced(RHASH_INCLUDE_DIR RHASH_LIBRARY)
+
+if(RHASH_LIBRARY AND RHASH_INCLUDE_DIR)
+  set(Rhash_FOUND ON)
+  set(Rhash_LIBRARIES ${RHASH_LIBRARY})
+  set(Rhash_INCLUDE_DIRS ${RHASH_INCLUDE_DIR})
+else()
+  set(Rhash_FOUND OFF)
+  if(Rhash_FIND_REQUIRED)
+    message(FATAL_ERROR "Could not find rhash")
+  endif(Rhash_FIND_REQUIRED)
+endif(RHASH_LIBRARY AND RHASH_INCLUDE_DIR)
diff --git a/cmake/FindTinyxml.cmake b/cmake/FindTinyxml.cmake
new file mode 100644
index 0000000..f399381
--- /dev/null
+++ b/cmake/FindTinyxml.cmake
@@ -0,0 +1,33 @@
+# - Try to find tinyxml
+#
+# Once done this will define
+#  Tinyxml_FOUND - System has tinyxml
+#  Tinyxml_INCLUDE_DIRS - The tinyxml include directories
+#  Tinyxml_LIBRARIES - The libraries needed to use tinyxml
+
+find_package(PkgConfig)
+pkg_check_modules(PC_TINYXML tinyxml)
+
+find_path(TINYXML_INCLUDE_DIR tinyxml.h
+  HINTS
+    ${PC_TINYXML_INCLUDEDIR}
+    ${PC_TINYXML_INCLUDE_DIRS}
+  PATHS
+    ${PC_TINYXML_INCLUDE_DIRS}
+  )
+
+find_library(TINYXML_LIBRARY tinyxml
+  HINTS
+    ${PC_TINYXML_LIBDIR}
+    ${PC_TINYXML_LIBRARY_DIRS}
+  PATHS
+    ${PC_TINYXML_LIBRARY_DIRS}
+  )
+
+mark_as_advanced(TINYXML_INCLUDE_DIR TINYXML_LIBRARY)
+
+if(TINYXML_INCLUDE_DIR)
+  set(Tinyxml_FOUND ON)
+  set(Tinyxml_INCLUDE_DIRS ${TINYXML_INCLUDE_DIR})
+  set(Tinyxml_LIBRARIES ${TINYXML_LIBRARY})
+endif(TINYXML_INCLUDE_DIR)
diff --git a/include/api.h b/include/api.h
index 3104230..97abe45 100644
--- a/include/api.h
+++ b/include/api.h
@@ -72,7 +72,7 @@ class API
         std::string getErrorMessage() { return this->error_message; };
         std::string getToken() { return this->config.oauth_token; };
         std::string getSecret() { return this->config.oauth_secret; };
-        template <typename T> CURLcode curlSetOpt(CURLoption option, T value) { return curl_easy_setopt(this->curlhandle, option, value); };
+        template <typename T> CURLcode curlSetOpt(CURLoption option, T value) { return curl_easy_setopt(this->curlhandle, option, value); }
         virtual ~API();
     protected:
     private:
diff --git a/include/config.h b/include/config.h
index ea6fe6c..75cc478 100644
--- a/include/config.h
+++ b/include/config.h
@@ -46,6 +46,8 @@ class Config
         bool bSaveSerials;
         bool bPlatformDetection;
         bool bShowWishlist;
+        bool bAutomaticXMLCreation;
+        bool bSaveChangelogs;
         std::string sGameRegex;
         std::string sDirectory;
         std::string sCacheDirectory;
@@ -59,6 +61,7 @@ class Config
         std::string sCookiePath;
         std::string sConfigFilePath;
         std::string sBlacklistFilePath;
+        std::string sIgnorelistFilePath;
         std::string sOrphanRegex;
         std::string sCoverList;
         std::string sReportFilePath;
@@ -86,6 +89,7 @@ class Config
         curl_off_t iDownloadRate;
         long int iTimeout;
         Blacklist blacklist;
+        Blacklist ignorelist;
 };
 
 #endif // CONFIG_H__
diff --git a/include/downloader.h b/include/downloader.h
index 666eecd..b52153c 100644
--- a/include/downloader.h
+++ b/include/downloader.h
@@ -52,6 +52,7 @@ class gameItem {
         std::string name;
         std::string id;
         std::vector<std::string> dlcnames;
+        Json::Value gamedetailsjson;
 };
 
 class Downloader
@@ -97,6 +98,8 @@ class Downloader
         Json::Value getGameDetailsJSON(const std::string& gameid);
         std::string getSerialsFromJSON(const Json::Value& json);
         void saveSerials(const std::string& serials, const std::string& filepath);
+        std::string getChangelogFromJSON(const Json::Value& json);
+        void saveChangelog(const std::string& changelog, const std::string& filepath);
 
         static int progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
         static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
diff --git a/include/gamedetails.h b/include/gamedetails.h
index 4ef052a..1737fff 100644
--- a/include/gamedetails.h
+++ b/include/gamedetails.h
@@ -4,6 +4,7 @@
 #include "globalconstants.h"
 #include "gamefile.h"
 #include "config.h"
+#include "util.h"
 
 #include <iostream>
 #include <vector>
@@ -22,15 +23,18 @@ class gameDetails
         std::string title;
         std::string icon;
         std::string serials;
-        void filterWithPriorities(const Config& config);
-        void makeFilepaths(const Config& config);
+        std::string changelog;
+        void filterWithPriorities(const gameSpecificConfig& config);
+        void makeFilepaths(const gameSpecificDirectoryConfig& config);
         std::string getSerialsFilepath();
+        std::string getChangelogFilepath();
         Json::Value getDetailsAsJson();
         virtual ~gameDetails();
     protected:
-        void filterListWithPriorities(std::vector<gameFile>& list, const Config& config);
+        void filterListWithPriorities(std::vector<gameFile>& list, const gameSpecificConfig& config);
     private:
         std::string serialsFilepath;
+        std::string changelogFilepath;
 };
 
 #endif // GAMEDETAILS_H
diff --git a/include/globalconstants.h b/include/globalconstants.h
index 47ada3a..2ae0f96 100644
--- a/include/globalconstants.h
+++ b/include/globalconstants.h
@@ -74,6 +74,6 @@ namespace GlobalConstants
         { PLATFORM_MAC,     "mac",   "Mac"     , "m|mac|osx"     },
         { PLATFORM_LINUX,   "linux", "Linux"   , "l|lin|linux"   }
     };
-};
+}
 
 #endif // GLOBALCONSTANTS_H_INCLUDED
diff --git a/include/util.h b/include/util.h
index d8d2813..8f77162 100644
--- a/include/util.h
+++ b/include/util.h
@@ -20,12 +20,27 @@
 #include <boost/regex.hpp>
 #include <json/json.h>
 
+struct gameSpecificDirectoryConfig
+{
+    bool bSubDirectories;
+    std::string sDirectory;
+    std::string sGameSubdir;
+    std::string sInstallersSubdir;
+    std::string sExtrasSubdir;
+    std::string sPatchesSubdir;
+    std::string sLanguagePackSubdir;
+    std::string sDLCSubdir;
+};
+
 struct gameSpecificConfig
 {
     unsigned int iInstallerPlatform;
     unsigned int iInstallerLanguage;
     bool bDLC;
     bool bIgnoreDLCCount;
+    gameSpecificDirectoryConfig dirConf;
+    std::vector<unsigned int> vLanguagePriority;
+    std::vector<unsigned int> vPlatformPriority;
 };
 
 namespace Util
@@ -48,6 +63,7 @@ namespace Util
     std::vector<std::string> tokenize(const std::string& str, const std::string& separator = ",");
     unsigned int getOptionValue(const std::string& str, const std::vector<GlobalConstants::optionsStruct>& options);
     std::string getOptionNameString(const unsigned int& value, const std::vector<GlobalConstants::optionsStruct>& options);
+    void parseOptionString(const std::string &option_string, std::vector<unsigned int> &priority, unsigned int &type, const std::vector<GlobalConstants::optionsStruct>& options);
 }
 
 #endif // UTIL_H
diff --git a/lgogdownloader.cbp b/lgogdownloader.cbp
deleted file mode 100644
index dfb1012..0000000
--- a/lgogdownloader.cbp
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
-<CodeBlocks_project_file>
-	<FileVersion major="1" minor="6" />
-	<Project>
-		<Option title="lgogdownloader" />
-		<Option pch_mode="2" />
-		<Option compiler="gcc" />
-		<Build>
-			<Target title="Debug">
-				<Option output="bin/Debug/lgogdownloader" prefix_auto="1" extension_auto="1" />
-				<Option object_output="obj/Debug/" />
-				<Option type="1" />
-				<Option compiler="gcc" />
-				<Compiler>
-					<Add option="-g" />
-					<Add option="-DDEBUG" />
-					<Add directory="include" />
-				</Compiler>
-			</Target>
-			<Target title="Release">
-				<Option output="bin/Release/lgogdownloader" prefix_auto="1" extension_auto="1" />
-				<Option object_output="obj/Release/" />
-				<Option type="1" />
-				<Option compiler="gcc" />
-				<Compiler>
-					<Add option="-O2" />
-					<Add directory="include" />
-				</Compiler>
-				<Linker>
-					<Add option="-s" />
-				</Linker>
-			</Target>
-		</Build>
-		<Compiler>
-			<Add option="-std=c++0x" />
-			<Add option="-Wall" />
-			<Add option="-fexceptions" />
-			<Add directory="include" />
-		</Compiler>
-		<Linker>
-			<Add library="curl" />
-			<Add library="oauth" />
-			<Add library="jsoncpp" />
-			<Add library="htmlcxx" />
-			<Add library="boost_system" />
-			<Add library="boost_filesystem" />
-			<Add library="boost_regex" />
-			<Add library="boost_program_options" />
-			<Add library="boost_date_time" />
-			<Add library="tinyxml" />
-			<Add library="rhash" />
-		</Linker>
-		<Unit filename="include/api.h" />
-		<Unit filename="include/blacklist.h" />
-		<Unit filename="include/config.h" />
-		<Unit filename="include/downloader.h" />
-		<Unit filename="include/gamedetails.h" />
-		<Unit filename="include/gamefile.h" />
-		<Unit filename="include/globalconstants.h" />
-		<Unit filename="include/progressbar.h" />
-		<Unit filename="include/util.h" />
-		<Unit filename="main.cpp" />
-		<Unit filename="src/api.cpp" />
-		<Unit filename="src/blacklist.cpp" />
-		<Unit filename="src/downloader.cpp" />
-		<Unit filename="src/gamedetails.cpp" />
-		<Unit filename="src/gamefile.cpp" />
-		<Unit filename="src/progressbar.cpp" />
-		<Unit filename="src/util.cpp" />
-		<Extensions>
-			<code_completion />
-			<debugger />
-			<envvars />
-		</Extensions>
-	</Project>
-</CodeBlocks_project_file>
diff --git a/main.cpp b/main.cpp
index 020f3a7..3307dcc 100644
--- a/main.cpp
+++ b/main.cpp
@@ -13,12 +13,6 @@
 #include <boost/filesystem.hpp>
 #include <boost/program_options.hpp>
 
-#define VERSION_NUMBER "2.26"
-
-#ifndef VERSION_STRING
-#   define VERSION_STRING "LGOGDownloader " VERSION_NUMBER
-#endif
-
 namespace bpo = boost::program_options;
 
 template<typename T> void set_vm_value(std::map<std::string, bpo::variable_value>& vm, const std::string& option, const T& value)
@@ -26,24 +20,6 @@ template<typename T> void set_vm_value(std::map<std::string, bpo::variable_value
     vm[option].value() = boost::any(value);
 }
 
-// Parse the options string
-void parseOptionString(const std::string &option_string, std::vector<unsigned int> &priority, unsigned int &type, const std::vector<GlobalConstants::optionsStruct>& options)
-{
-    type = 0;
-    std::vector<std::string> tokens_priority = Util::tokenize(option_string, ",");
-    for (std::vector<std::string>::iterator it_priority = tokens_priority.begin(); it_priority != tokens_priority.end(); it_priority++)
-    {
-        unsigned int value = 0;
-        std::vector<std::string> tokens_value = Util::tokenize(*it_priority, "+");
-        for (std::vector<std::string>::iterator it_value = tokens_value.begin(); it_value != tokens_value.end(); it_value++)
-        {
-            value |= Util::getOptionValue(*it_value, options);
-        }
-        priority.push_back(value);
-        type |= value;
-    }
-}
-
 int main(int argc, char *argv[])
 {
     // Constants for option selection with include/exclude
@@ -78,6 +54,7 @@ int main(int argc, char *argv[])
     config.sCookiePath = config.sConfigDirectory + "/cookies.txt";
     config.sConfigFilePath = config.sConfigDirectory + "/config.cfg";
     config.sBlacklistFilePath = config.sConfigDirectory + "/blacklist.txt";
+    config.sIgnorelistFilePath = config.sConfigDirectory + "/ignorelist.txt";
 
     std::string priority_help_text = "Set priority by separating values with \",\"\nCombine values by separating with \"+\"";
     // Create help text for --platform option
@@ -198,6 +175,8 @@ int main(int argc, char *argv[])
             ("ignore-dlc-count", bpo::value<std::string>(&config.sIgnoreDLCCountRegex)->implicit_value(".*"), "Set regular expression filter for games to ignore DLC count information\nIgnoring DLC count information helps in situations where the account page doesn't provide accurate information about DLCs")
             ("include", bpo::value<std::string>(&sIncludeOptions)->default_value("all"), ("Select what to download/list/repair\n" + include_options_text).c_str())
             ("exclude", bpo::value<std::string>(&sExcludeOptions)->default_value("covers"), ("Select what not to download/list/repair\n" + include_options_text).c_str())
+            ("automatic-xml-creation", bpo::value<bool>(&config.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed")
+            ("save-changelogs", bpo::value<bool>(&config.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading")
         ;
         // Options read from config file
         options_cfg_only.add_options()
@@ -232,7 +211,7 @@ int main(int argc, char *argv[])
         {
             if (!boost::filesystem::create_directories(path))
             {
-                std::cout << "Failed to create directory: " << path << std::endl;
+                std::cerr << "Failed to create directory: " << path << std::endl;
                 return 1;
             }
         }
@@ -242,7 +221,7 @@ int main(int argc, char *argv[])
         {
             if (!boost::filesystem::create_directories(path))
             {
-                std::cout << "Failed to create directory: " << path << std::endl;
+                std::cerr << "Failed to create directory: " << path << std::endl;
                 return 1;
             }
         }
@@ -252,7 +231,7 @@ int main(int argc, char *argv[])
         {
             if (!boost::filesystem::create_directories(path))
             {
-                std::cout << "Failed to create directory: " << path << std::endl;
+                std::cerr << "Failed to create directory: " << path << std::endl;
                 return 1;
             }
         }
@@ -262,7 +241,7 @@ int main(int argc, char *argv[])
             std::ifstream ifs(config.sConfigFilePath.c_str());
             if (!ifs)
             {
-                std::cout << "Could not open config file: " << config.sConfigFilePath << std::endl;
+                std::cerr << "Could not open config file: " << config.sConfigFilePath << std::endl;
                 return 1;
             }
             else
@@ -279,7 +258,7 @@ int main(int argc, char *argv[])
             std::ifstream ifs(config.sBlacklistFilePath.c_str());
             if (!ifs)
             {
-                std::cout << "Could not open blacklist file: " << config.sBlacklistFilePath << std::endl;
+                std::cerr << "Could not open blacklist file: " << config.sBlacklistFilePath << std::endl;
                 return 1;
             }
             else
@@ -295,6 +274,27 @@ int main(int argc, char *argv[])
             }
         }
 
+        if (boost::filesystem::exists(config.sIgnorelistFilePath))
+        {
+            std::ifstream ifs(config.sIgnorelistFilePath.c_str());
+            if (!ifs)
+            {
+                std::cerr << "Could not open ignorelist file: " << config.sIgnorelistFilePath << std::endl;
+                return 1;
+            }
+            else
+            {
+                std::string line;
+                std::vector<std::string> lines;
+                while (!ifs.eof())
+                {
+                    std::getline(ifs, line);
+                    lines.push_back(std::move(line));
+                }
+                config.ignorelist.initialize(lines);
+            }
+        }
+
         if (vm.count("chunk-size"))
             config.iChunkSize <<= 20; // Convert chunk size from bytes to megabytes
 
@@ -344,8 +344,11 @@ int main(int argc, char *argv[])
             config.bLoginHTTP = true;
         }
 
-        parseOptionString(sInstallerLanguage, config.vLanguagePriority, config.iInstallerLanguage, GlobalConstants::LANGUAGES);
-        parseOptionString(sInstallerPlatform, config.vPlatformPriority, config.iInstallerPlatform, GlobalConstants::PLATFORMS);
+        if (config.sXMLFile == "automatic")
+            config.bAutomaticXMLCreation = true;
+
+        Util::parseOptionString(sInstallerLanguage, config.vLanguagePriority, config.iInstallerLanguage, GlobalConstants::LANGUAGES);
+        Util::parseOptionString(sInstallerPlatform, config.vPlatformPriority, config.iInstallerPlatform, GlobalConstants::PLATFORMS);
 
         unsigned int include_value = 0;
         unsigned int exclude_value = 0;
@@ -383,13 +386,13 @@ int main(int argc, char *argv[])
 
     if (config.iInstallerPlatform < GlobalConstants::PLATFORMS[0].id || config.iInstallerPlatform > platform_all)
     {
-        std::cout << "Invalid value for --platform" << std::endl;
+        std::cerr << "Invalid value for --platform" << std::endl;
         return 1;
     }
 
     if (config.iInstallerLanguage < GlobalConstants::LANGUAGES[0].id || config.iInstallerLanguage > language_all)
     {
-        std::cout << "Invalid value for --language" << std::endl;
+        std::cerr << "Invalid value for --language" << std::endl;
         return 1;
     }
 
@@ -451,12 +454,11 @@ int main(int argc, char *argv[])
         {
             set_vm_value(vm, "token", downloader.config.sToken);
             set_vm_value(vm, "secret", downloader.config.sSecret);
-            bpo::notify(vm);
         }
         std::ofstream ofs(config.sConfigFilePath.c_str());
         if (ofs)
         {
-            std::cout << "Saving config: " << config.sConfigFilePath << std::endl;
+            std::cerr << "Saving config: " << config.sConfigFilePath << std::endl;
             for (bpo::variables_map::iterator it = vm.begin(); it != vm.end(); ++it)
             {
                 std::string option = it->first;
@@ -507,7 +509,7 @@ int main(int argc, char *argv[])
         }
         else
         {
-            std::cout << "Failed to create config: " << config.sConfigFilePath << std::endl;
+            std::cerr << "Failed to create config: " << config.sConfigFilePath << std::endl;
             return 1;
         }
     }
@@ -527,7 +529,7 @@ int main(int argc, char *argv[])
         }
         else
         {
-            std::cout << "Failed to create config: " << config.sConfigFilePath << std::endl;
+            std::cerr << "Failed to create config: " << config.sConfigFilePath << std::endl;
             return 1;
         }
     }
@@ -555,9 +557,13 @@ int main(int argc, char *argv[])
     else if (config.bCheckStatus)
         downloader.checkStatus();
     else
-    {   // Show help message
-        std::cout   << config.sVersionString << std::endl
-                    << options_cli_all << std::endl;
+    {
+        if (!(config.bLoginAPI || config.bLoginHTTP))
+        {
+            // Show help message
+            std::cerr   << config.sVersionString << std::endl
+                        << options_cli_all << std::endl;
+        }
     }
 
     // Orphan check was called at the same time as download. Perform it after download has finished
diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt
new file mode 100644
index 0000000..7f123b0
--- /dev/null
+++ b/man/CMakeLists.txt
@@ -0,0 +1,22 @@
+find_program(HELP2MAN help2man DOC "Location of the help2man program")
+find_program(GZIP gzip DOC "Location of the gzip program")
+mark_as_advanced(HELP2MAN)
+mark_as_advanced(GZIP)
+
+if(HELP2MAN AND GZIP)	
+  set(H2M_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.supplemental.groff)
+  set(MAN_PAGE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.1)
+  set(MAN_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.1.gz)
+  add_custom_command(
+    OUTPUT ${MAN_FILE}
+    COMMAND ${HELP2MAN} -N -i ${H2M_FILE} -o ${MAN_PAGE} ${PROJECT_BINARY_DIR}/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}
+    COMMAND ${GZIP} -f -9 ${MAN_PAGE}
+    MAIN_DEPENDENCY ${H2M_FILE}
+	COMMENT "Building man page"
+	VERBATIM
+	)
+  add_custom_target(manpage ALL DEPENDS ${MAN_FILE} ${PROJECT_NAME})
+  install(FILES ${MAN_FILE} DESTINATION ${INSTALL_SHARE_DIR}/man/man1)
+else(HELP2MAN AND GZIP)
+  message("WARNING: One of the following is missing: help2man, gzip; man page will not be generated")
+endif(HELP2MAN AND GZIP)
diff --git a/man/lgogdownloader.supplemental.groff b/man/lgogdownloader.supplemental.groff
index 22423a0..a376412 100644
--- a/man/lgogdownloader.supplemental.groff
+++ b/man/lgogdownloader.supplemental.groff
@@ -62,7 +62,15 @@ If \fB$XDG_CACHE_HOME\fP is not set, it will use \fI$HOME/.cache/lgogdownloader/
 
 .TP
 \fI$XDG_CONFIG_HOME/lgogdownloader/blacklist.txt\fP
-Allows user to specify individual files that should not be downloaded or mentioned as orphans.
+Allows user to specify individual files that should not be downloaded.
+.br
+It doesn't have to exist, but if it does exist, it must be readable to lgogdownloader.
+
+.TP
+\fI$XDG_CONFIG_HOME/lgogdownloader/ignorelist.txt\fP
+Allows user to specify individual files that should not be mentioned
+as orphans.  The file has the same format and interpretation as a
+blacklist.
 .br
 It doesn't have to exist, but if it does exist, it must be readable to lgogdownloader.
 
@@ -70,17 +78,25 @@ It doesn't have to exist, but if it does exist, it must be readable to lgogdownl
 \fI$XDG_CONFIG_HOME/lgogdownloader/gamespecific/gamename.conf\fP
 JSON formatted file. Sets game specific settings for \fBgamename\fP.
 .br
-Allowed settings are \fBlanguage\fP, \fBplatform\fP, \fBdlc\fP and \fBignore-dlc-count\fP.
+Allowed settings are \fBlanguage\fP, \fBplatform\fP, \fBdlc\fP, \fBignore-dlc-count\fP \fBsubdirectories\fP, \fBdirectory\fP, \fBsubdir-game\fP, \fBsubdir-installers\fP, \fBsubdir-extras\fP, \fBsubdir-patches\fP, \fBsubdir-language-packs\fP and \fBsubdir-dlc\fP.
 .br
 The \fBdlc\fP option is limited to disabling DLC for specific game. It can't enable DLC listing/downloading if \fB--no-dlc\fP option is used.
 .br
 Must be in the following format:
 .br
 {
-    "language" : <int>,
-    "platform" : <int>,
+    "language" : <string>,
+    "platform" : <string>,
     "dlc" : <bool>,
-    "ignore-dlc-count" : <bool>
+    "ignore-dlc-count" : <bool>,
+    "subdirectories" : <bool>,
+    "directory" : <string>,
+    "subdir-game" : <string>,
+    "subdir-installers" : <string>,
+    "subdir-extras" : <string>,
+    "subdir-patches" : <string>,
+    "subdir-language-packs" : <string>,
+    "subdir-dlc" : <string>
 .br
 }
 
diff --git a/src/downloader.cpp b/src/downloader.cpp
index 9a5ed31..650da80 100644
--- a/src/downloader.cpp
+++ b/src/downloader.cpp
@@ -32,7 +32,7 @@ Downloader::Downloader(Config &conf)
     this->config = conf;
     if (config.bLoginHTTP && boost::filesystem::exists(config.sCookiePath))
         if (!boost::filesystem::remove(config.sCookiePath))
-            std::cout << "Failed to delete " << config.sCookiePath << std::endl;
+            std::cerr << "Failed to delete " << config.sCookiePath << std::endl;
 }
 
 Downloader::~Downloader()
@@ -105,7 +105,7 @@ int Downloader::init()
         this->report_ofs.open(config.sReportFilePath);
         if (!this->report_ofs)
         {
-            std::cout << "Failed to create " << config.sReportFilePath << std::endl;
+            std::cerr << "Failed to create " << config.sReportFilePath << std::endl;
             return 1;
         }
     }
@@ -122,16 +122,16 @@ int Downloader::login()
     char *pwd;
     std::string email;
     if (!isatty(STDIN_FILENO)) {
-        std::cout << "Unable to read email and password" << std::endl;
+        std::cerr << "Unable to read email and password" << std::endl;
         return 0;
     }
-    std::cout << "Email: ";
+    std::cerr << "Email: ";
     std::getline(std::cin,email);
     pwd = getpass("Password: ");
     std::string password = (std::string)pwd;
     if (email.empty() || password.empty())
     {
-        std::cout << "Email and/or password empty" << std::endl;
+        std::cerr << "Email and/or password empty" << std::endl;
         return 0;
     }
     else
@@ -141,12 +141,12 @@ int Downloader::login()
         {
             if (!HTTP_Login(email, password))
             {
-                std::cout << "HTTP: Login failed" << std::endl;
+                std::cerr << "HTTP: Login failed" << std::endl;
                 return 0;
             }
             else
             {
-                std::cout << "HTTP: Login successful" << std::endl;
+                std::cerr << "HTTP: Login successful" << std::endl;
                 if (!config.bLoginAPI)
                     return 1;
             }
@@ -156,12 +156,12 @@ int Downloader::login()
         {
             if (!gogAPI->login(email, password))
             {
-                std::cout << "API: Login failed" << std::endl;
+                std::cerr << "API: Login failed" << std::endl;
                 return 0;
             }
             else
             {
-                std::cout << "API: Login successful" << std::endl;
+                std::cerr << "API: Login successful" << std::endl;
                 config.sToken = gogAPI->getToken();
                 config.sSecret = gogAPI->getSecret();
                 return 1;
@@ -211,32 +211,48 @@ void Downloader::getGameList()
 */
 int Downloader::getGameDetails()
 {
+    // Set default game specific directory options to values from config
+    gameSpecificDirectoryConfig dirConfDefault;
+    dirConfDefault.sDirectory = config.sDirectory;
+    dirConfDefault.bSubDirectories = config.bSubDirectories;
+    dirConfDefault.sGameSubdir = config.sGameSubdir;
+    dirConfDefault.sInstallersSubdir = config.sInstallersSubdir;
+    dirConfDefault.sExtrasSubdir = config.sExtrasSubdir;
+    dirConfDefault.sLanguagePackSubdir = config.sLanguagePackSubdir;
+    dirConfDefault.sDLCSubdir = config.sDLCSubdir;
+    dirConfDefault.sPatchesSubdir = config.sPatchesSubdir;
+
     if (config.bUseCache && !config.bUpdateCache)
     {
         // GameRegex filter alias for all games
         if (config.sGameRegex == "all")
             config.sGameRegex = ".*";
         else if (config.sGameRegex == "free")
-            std::cout << "Warning: regex alias \"free\" doesn't work with cached details" << std::endl;
+            std::cerr << "Warning: regex alias \"free\" doesn't work with cached details" << std::endl;
 
         int result = this->loadGameDetailsCache();
         if (result == 0)
         {
             for (unsigned int i = 0; i < this->games.size(); ++i)
-                this->games[i].makeFilepaths(config);
+            {
+                gameSpecificConfig conf;
+                conf.dirConf = dirConfDefault;
+                Util::getGameSpecificConfig(games[i].gamename, &conf);
+                this->games[i].makeFilepaths(conf.dirConf);
+            }
             return 0;
         }
         else
         {
             if (result == 1)
             {
-                std::cout << "Cache doesn't exist." << std::endl;
-                std::cout << "Create cache with --update-cache" << std::endl;
+                std::cerr << "Cache doesn't exist." << std::endl;
+                std::cerr << "Create cache with --update-cache" << std::endl;
             }
             else if (result == 3)
             {
-                std::cout << "Cache is too old." << std::endl;
-                std::cout << "Update cache with --update-cache or use bigger --cache-valid" << std::endl;
+                std::cerr << "Cache is too old." << std::endl;
+                std::cerr << "Update cache with --update-cache or use bigger --cache-valid" << std::endl;
             }
             return 1;
         }
@@ -246,7 +262,7 @@ int Downloader::getGameDetails()
     int updated = 0;
     for (unsigned int i = 0; i < gameItems.size(); ++i)
     {
-        std::cout << "Getting game info " << i+1 << " / " << gameItems.size() << "\r" << std::flush;
+        std::cerr << "Getting game info " << i+1 << " / " << gameItems.size() << "\r" << std::flush;
         bool bHasDLC = !gameItems[i].dlcnames.empty();
 
         gameSpecificConfig conf;
@@ -254,21 +270,58 @@ int Downloader::getGameDetails()
         conf.bIgnoreDLCCount = false;
         conf.iInstallerLanguage = config.iInstallerLanguage;
         conf.iInstallerPlatform = config.iInstallerPlatform;
+        conf.dirConf = dirConfDefault;
+        conf.vLanguagePriority = config.vLanguagePriority;
+        conf.vPlatformPriority = config.vPlatformPriority;
         if (!config.bUpdateCache) // Disable game specific config files for cache update
         {
-            if (Util::getGameSpecificConfig(gameItems[i].name, &conf) > 0)
-                std::cout << std::endl << gameItems[i].name << " - Language: " << conf.iInstallerLanguage << ", Platform: " << conf.iInstallerPlatform << ", DLC: " << (conf.bDLC ? "true" : "false") << ", Ignore DLC count: " << (conf.bIgnoreDLCCount ? "true" : "false") << std::endl;
+            int iOptionsOverridden = Util::getGameSpecificConfig(gameItems[i].name, &conf);
+            if (iOptionsOverridden > 0)
+            {
+                std::cerr << std::endl << gameItems[i].name << " - " << iOptionsOverridden << " options overridden with game specific options" << std::endl;
+                if (config.bVerbose)
+                {
+                    if (conf.bIgnoreDLCCount)
+                        std::cerr << "\tIgnore DLC count" << std::endl;
+                    if (conf.bDLC != config.bDLC)
+                        std::cerr << "\tDLC: " << (conf.bDLC ? "true" : "false") << std::endl;
+                    if (conf.iInstallerLanguage != config.iInstallerLanguage)
+                        std::cerr << "\tLanguage: " << Util::getOptionNameString(conf.iInstallerLanguage, GlobalConstants::LANGUAGES) << std::endl;
+                    if (conf.vLanguagePriority != config.vLanguagePriority)
+                    {
+                        std::cerr << "\tLanguage priority:" << std::endl;
+                        for (unsigned int j = 0; j < conf.vLanguagePriority.size(); ++j)
+                        {
+                            std::cerr << "\t  " << j << ": " << Util::getOptionNameString(conf.vLanguagePriority[j], GlobalConstants::LANGUAGES) << std::endl;
+                        }
+                    }
+                    if (conf.iInstallerPlatform != config.iInstallerPlatform)
+                        std::cerr << "\tPlatform: " << Util::getOptionNameString(conf.iInstallerPlatform, GlobalConstants::PLATFORMS) << std::endl;
+                    if (conf.vPlatformPriority != config.vPlatformPriority)
+                    {
+                        std::cerr << "\tPlatform priority:" << std::endl;
+                        for (unsigned int j = 0; j < conf.vPlatformPriority.size(); ++j)
+                        {
+                            std::cerr << "\t  " << j << ": " << Util::getOptionNameString(conf.vPlatformPriority[j], GlobalConstants::PLATFORMS) << std::endl;
+                        }
+                    }
+                }
+            }
         }
 
         game = gogAPI->getGameDetails(gameItems[i].name, conf.iInstallerPlatform, conf.iInstallerLanguage, config.bDuplicateHandler);
         if (!gogAPI->getError())
         {
-            game.filterWithPriorities(config);
+            game.filterWithPriorities(conf);
             Json::Value gameDetailsJSON;
 
+            if (!gameItems[i].gamedetailsjson.empty())
+                gameDetailsJSON = gameItems[i].gamedetailsjson;
+
             if (game.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
             {
-                gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+                if (gameDetailsJSON.empty())
+                    gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
                 game.extras = this->getExtrasFromJSON(gameDetailsJSON, gameItems[i].name);
             }
             if (config.bSaveSerials)
@@ -277,6 +330,12 @@ int Downloader::getGameDetails()
                     gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
                 game.serials = this->getSerialsFromJSON(gameDetailsJSON);
             }
+            if (config.bSaveChangelogs)
+            {
+                if (gameDetailsJSON.empty())
+                    gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+                game.changelog = this->getChangelogFromJSON(gameDetailsJSON);
+            }
 
             // Ignore DLC count and try to get DLCs from JSON
             if (game.dlcs.empty() && !bHasDLC && conf.bDLC && conf.bIgnoreDLCCount)
@@ -294,7 +353,7 @@ int Downloader::getGameDetails()
                 {
                     gameDetails dlc;
                     dlc = gogAPI->getGameDetails(gameItems[i].dlcnames[j], conf.iInstallerPlatform, conf.iInstallerLanguage, config.bDuplicateHandler);
-                    dlc.filterWithPriorities(config);
+                    dlc.filterWithPriorities(conf);
                     if (dlc.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
                     {
                         if (gameDetailsJSON.empty())
@@ -342,11 +401,36 @@ int Downloader::getGameDetails()
                         }
                     }
 
+                    if (config.bSaveChangelogs)
+                    {
+                        if (gameDetailsJSON.empty())
+                            gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+
+                        // Make sure we save changelog for the right DLC
+                        for (unsigned int k = 0; k < gameDetailsJSON["dlcs"].size(); ++k)
+                        {
+                            std::vector<std::string> urls;
+                            if (gameDetailsJSON["dlcs"][k].isMember("changelog") && gameDetailsJSON["dlcs"][k].isMember("downloads"))
+                            {
+                                // Assuming that only DLC with installers can have changelog
+                                Util::getDownloaderUrlsFromJSON(gameDetailsJSON["dlcs"][k]["downloads"], urls);
+                            }
+
+                            if (!urls.empty())
+                            {
+                                if (urls[0].find("/" + gameItems[i].dlcnames[j] + "/") != std::string::npos)
+                                {
+                                    dlc.changelog = this->getChangelogFromJSON(gameDetailsJSON["dlcs"][k]);
+                                }
+                            }
+                        }
+                    }
+
                     game.dlcs.push_back(dlc);
                 }
             }
 
-            game.makeFilepaths(config);
+            game.makeFilepaths(conf.dirConf);
 
             if (!config.bUpdateCheck)
                 games.push_back(game);
@@ -363,19 +447,19 @@ int Downloader::getGameDetails()
                 }
                 if (updated >= gogAPI->user.notifications_games)
                 { // Gone through all updated games. No need to go through the rest.
-                    std::cout << std::endl << "Got info for all updated games. Moving on..." << std::endl;
+                    std::cerr << std::endl << "Got info for all updated games. Moving on..." << std::endl;
                     break;
                 }
             }
         }
         else
         {
-            std::cout << gogAPI->getErrorMessage() << std::endl;
+            std::cerr << gogAPI->getErrorMessage() << std::endl;
             gogAPI->clearError();
             continue;
         }
     }
-    std::cout << std::endl;
+    std::cerr << std::endl;
     return 0;
 }
 
@@ -406,7 +490,7 @@ void Downloader::listGames()
                         if (config.blacklist.isBlacklisted(filepath))
                         {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -432,7 +516,7 @@ void Downloader::listGames()
                     if (config.blacklist.isBlacklisted(filepath))
                     {
                         if (config.bVerbose)
-                            std::cout << "skipped blacklisted file " << filepath << std::endl;
+                            std::cerr << "skipped blacklisted file " << filepath << std::endl;
                         continue;
                     }
 
@@ -453,7 +537,7 @@ void Downloader::listGames()
                     if (config.blacklist.isBlacklisted(filepath))
                     {
                         if (config.bVerbose)
-                            std::cout << "skipped blacklisted file " << filepath << std::endl;
+                            std::cerr << "skipped blacklisted file " << filepath << std::endl;
                         continue;
                     }
 
@@ -478,7 +562,7 @@ void Downloader::listGames()
                     if (config.blacklist.isBlacklisted(filepath))
                     {
                         if (config.bVerbose)
-                            std::cout << "skipped blacklisted file " << filepath << std::endl;
+                            std::cerr << "skipped blacklisted file " << filepath << std::endl;
                         continue;
                     }
 
@@ -506,7 +590,7 @@ void Downloader::listGames()
                         if (config.blacklist.isBlacklisted(filepath))
                         {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -523,7 +607,7 @@ void Downloader::listGames()
                         std::string filepath = games[i].dlcs[j].patches[k].getFilepath();
                         if (config.blacklist.isBlacklisted(filepath)) {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -539,7 +623,7 @@ void Downloader::listGames()
                         std::string filepath = games[i].dlcs[j].extras[k].getFilepath();
                         if (config.blacklist.isBlacklisted(filepath)) {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -582,7 +666,7 @@ void Downloader::repair()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
@@ -593,7 +677,7 @@ void Downloader::repair()
                     XML = gogAPI->getXML(games[i].gamename, games[i].installers[j].id);
                     if (gogAPI->getError())
                     {
-                        std::cout << gogAPI->getErrorMessage() << std::endl;
+                        std::cerr << gogAPI->getErrorMessage() << std::endl;
                         gogAPI->clearError();
                         continue;
                     }
@@ -606,7 +690,7 @@ void Downloader::repair()
                     std::string url = gogAPI->getInstallerLink(games[i].gamename, games[i].installers[j].id);
                     if (gogAPI->getError())
                     {
-                        std::cout << gogAPI->getErrorMessage() << std::endl;
+                        std::cerr << gogAPI->getErrorMessage() << std::endl;
                         gogAPI->clearError();
                         continue;
                     }
@@ -626,14 +710,14 @@ void Downloader::repair()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
                 std::string url = gogAPI->getExtraLink(games[i].gamename, games[i].extras[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -652,7 +736,7 @@ void Downloader::repair()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
@@ -663,7 +747,7 @@ void Downloader::repair()
                     XML = gogAPI->getXML(games[i].gamename, games[i].patches[j].id);
                     if (gogAPI->getError())
                     {
-                        std::cout << gogAPI->getErrorMessage() << std::endl;
+                        std::cerr << gogAPI->getErrorMessage() << std::endl;
                         gogAPI->clearError();
                     }
                 }
@@ -671,7 +755,7 @@ void Downloader::repair()
                 std::string url = gogAPI->getPatchLink(games[i].gamename, games[i].patches[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -690,14 +774,14 @@ void Downloader::repair()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
                 std::string url = gogAPI->getLanguagePackLink(games[i].gamename, games[i].languagepacks[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -718,7 +802,7 @@ void Downloader::repair()
                         if (config.blacklist.isBlacklisted(filepath))
                         {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -729,7 +813,7 @@ void Downloader::repair()
                             XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id);
                             if (gogAPI->getError())
                             {
-                                std::cout << gogAPI->getErrorMessage() << std::endl;
+                                std::cerr << gogAPI->getErrorMessage() << std::endl;
                                 gogAPI->clearError();
                                 continue;
                             }
@@ -742,7 +826,7 @@ void Downloader::repair()
                             std::string url = gogAPI->getInstallerLink(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id);
                             if (gogAPI->getError())
                             {
-                                std::cout << gogAPI->getErrorMessage() << std::endl;
+                                std::cerr << gogAPI->getErrorMessage() << std::endl;
                                 gogAPI->clearError();
                                 continue;
                             }
@@ -759,7 +843,7 @@ void Downloader::repair()
                         std::string filepath = games[i].dlcs[j].patches[k].getFilepath();
                         if (config.blacklist.isBlacklisted(filepath)) {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -770,7 +854,7 @@ void Downloader::repair()
                             XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].patches[k].id);
                             if (gogAPI->getError())
                             {
-                                std::cout << gogAPI->getErrorMessage() << std::endl;
+                                std::cerr << gogAPI->getErrorMessage() << std::endl;
                                 gogAPI->clearError();
                             }
                         }
@@ -778,7 +862,7 @@ void Downloader::repair()
                         std::string url = gogAPI->getPatchLink(games[i].dlcs[j].gamename, games[i].dlcs[j].patches[k].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                             continue;
                         }
@@ -794,14 +878,14 @@ void Downloader::repair()
                         std::string filepath = games[i].dlcs[j].extras[k].getFilepath();
                         if (config.blacklist.isBlacklisted(filepath)) {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
                         std::string url = gogAPI->getExtraLink(games[i].dlcs[j].gamename, games[i].dlcs[j].extras[k].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                             continue;
                         }
@@ -828,6 +912,12 @@ void Downloader::download()
             this->saveSerials(games[i].serials, filepath);
         }
 
+        if (config.bSaveChangelogs && !games[i].changelog.empty())
+        {
+            std::string filepath = games[i].getChangelogFilepath();
+            this->saveChangelog(games[i].changelog, filepath);
+        }
+
         // Download covers
         if (config.bCover && !config.bUpdateCheck)
         {
@@ -858,7 +948,7 @@ void Downloader::download()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
@@ -866,7 +956,7 @@ void Downloader::download()
                 std::string url = gogAPI->getInstallerLink(games[i].gamename, games[i].installers[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -880,7 +970,7 @@ void Downloader::download()
                         XML = gogAPI->getXML(games[i].gamename, games[i].installers[j].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                         }
                     }
@@ -901,7 +991,7 @@ void Downloader::download()
                 std::string url = gogAPI->getExtraLink(games[i].gamename, games[i].extras[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -910,7 +1000,7 @@ void Downloader::download()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
@@ -922,7 +1012,7 @@ void Downloader::download()
                     std::cout << filepath << std::endl;
                     CURLcode result = this->downloadFile(url, filepath);
                     std::cout << std::endl;
-                    if (result==CURLE_OK && config.sXMLFile == "automatic")
+                    if (result==CURLE_OK && config.bAutomaticXMLCreation)
                     {
                         std::cout << "Starting automatic XML creation" << std::endl;
                         std::string xml_dir = config.sXMLDirectory + "/" + games[i].gamename;
@@ -945,7 +1035,7 @@ void Downloader::download()
                 std::string url = gogAPI->getPatchLink(games[i].gamename, games[i].patches[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -954,7 +1044,7 @@ void Downloader::download()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
@@ -967,7 +1057,7 @@ void Downloader::download()
                         XML = gogAPI->getXML(games[i].gamename, games[i].patches[j].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                         }
                     }
@@ -988,7 +1078,7 @@ void Downloader::download()
                 std::string url = gogAPI->getLanguagePackLink(games[i].gamename, games[i].languagepacks[j].id);
                 if (gogAPI->getError())
                 {
-                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                     gogAPI->clearError();
                     continue;
                 }
@@ -997,7 +1087,7 @@ void Downloader::download()
                 if (config.blacklist.isBlacklisted(filepath))
                 {
                     if (config.bVerbose)
-                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                        std::cerr << "skipped blacklisted file " << filepath << std::endl;
                     continue;
                 }
 
@@ -1010,7 +1100,7 @@ void Downloader::download()
                         XML = gogAPI->getXML(games[i].gamename, games[i].languagepacks[j].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                         }
                     }
@@ -1031,6 +1121,11 @@ void Downloader::download()
                     std::string filepath = games[i].dlcs[j].getSerialsFilepath();
                     this->saveSerials(games[i].dlcs[j].serials, filepath);
                 }
+                if (config.bSaveChangelogs && !games[i].dlcs[j].changelog.empty())
+                {
+                    std::string filepath = games[i].dlcs[j].getChangelogFilepath();
+                    this->saveChangelog(games[i].dlcs[j].changelog, filepath);
+                }
 
                 if (config.bInstallers)
                 {
@@ -1040,7 +1135,7 @@ void Downloader::download()
                         if (config.blacklist.isBlacklisted(filepath))
                         {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -1048,7 +1143,7 @@ void Downloader::download()
                         std::string url = gogAPI->getInstallerLink(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                             continue;
                         }
@@ -1062,7 +1157,7 @@ void Downloader::download()
                                 XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id);
                                 if (gogAPI->getError())
                                 {
-                                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                                     gogAPI->clearError();
                                 }
                             }
@@ -1082,7 +1177,7 @@ void Downloader::download()
                         if (config.blacklist.isBlacklisted(filepath))
                         {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -1090,7 +1185,7 @@ void Downloader::download()
                         std::string url = gogAPI->getPatchLink(games[i].dlcs[j].gamename, games[i].dlcs[j].patches[k].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                             continue;
                         }
@@ -1104,7 +1199,7 @@ void Downloader::download()
                                 XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].patches[k].id);
                                 if (gogAPI->getError())
                                 {
-                                    std::cout << gogAPI->getErrorMessage() << std::endl;
+                                    std::cerr << gogAPI->getErrorMessage() << std::endl;
                                     gogAPI->clearError();
                                 }
                             }
@@ -1124,7 +1219,7 @@ void Downloader::download()
                         if (config.blacklist.isBlacklisted(filepath))
                         {
                             if (config.bVerbose)
-                                std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                std::cerr << "skipped blacklisted file " << filepath << std::endl;
                             continue;
                         }
 
@@ -1132,7 +1227,7 @@ void Downloader::download()
                         std::string url = gogAPI->getExtraLink(games[i].dlcs[j].gamename, games[i].dlcs[j].extras[k].id);
                         if (gogAPI->getError())
                         {
-                            std::cout << gogAPI->getErrorMessage() << std::endl;
+                            std::cerr << gogAPI->getErrorMessage() << std::endl;
                             gogAPI->clearError();
                             continue;
                         }
@@ -1144,7 +1239,7 @@ void Downloader::download()
                                 std::cout << "Dowloading: " << games[i].dlcs[j].extras[k].name << std::endl;
                             CURLcode result = this->downloadFile(url, filepath);
                             std::cout << std::endl;
-                            if (result==CURLE_OK && config.sXMLFile == "automatic")
+                            if (result==CURLE_OK && config.bAutomaticXMLCreation)
                             {
                                 std::cout << "Starting automatic XML creation" << std::endl;
                                 std::string xml_dir = config.sXMLDirectory + "/" + games[i].dlcs[j].gamename;
@@ -1210,7 +1305,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
     {
         if (!boost::filesystem::is_directory(path))
         {
-            std::cout << path << " is not directory" << std::endl;
+            std::cerr << path << " is not directory" << std::endl;
             return res;
         }
     }
@@ -1218,7 +1313,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
     {
         if (!boost::filesystem::create_directories(path))
         {
-            std::cout << "Failed to create directory: " << path << std::endl;
+            std::cerr << "Failed to create directory: " << path << std::endl;
             return res;
         }
     }
@@ -1240,22 +1335,22 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
             }
             else
             {
-                std::cout << "Failed to reopen " << filepath << std::endl;
+                std::cerr << "Failed to reopen " << filepath << std::endl;
                 return res;
             }
         }
         else
         {   // File exists but is not the same version
             fclose(outfile);
-            std::cout << "Remote file is different, renaming local file" << std::endl;
+            std::cerr << "Remote file is different, renaming local file" << std::endl;
             std::string date_old = "." + bptime::to_iso_string(bptime::second_clock::local_time()) + ".old";
             boost::filesystem::path new_name = filepath + date_old; // Rename old file by appending date and ".old" to filename
             boost::system::error_code ec;
             boost::filesystem::rename(pathname, new_name, ec); // Rename the file
             if (ec)
             {
-                std::cout << "Failed to rename " << filepath << " to " << new_name.string() << std::endl;
-                std::cout << "Skipping file" << std::endl;
+                std::cerr << "Failed to rename " << filepath << " to " << new_name.string() << std::endl;
+                std::cerr << "Skipping file" << std::endl;
                 return res;
             }
             else
@@ -1268,7 +1363,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
                 }
                 else
                 {
-                    std::cout << "Failed to create " << filepath << std::endl;
+                    std::cerr << "Failed to create " << filepath << std::endl;
                     return res;
                 }
             }
@@ -1284,7 +1379,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
         }
         else
         {
-            std::cout << "Failed to create " << filepath << std::endl;
+            std::cerr << "Failed to create " << filepath << std::endl;
             return res;
         }
     }
@@ -1300,14 +1395,14 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
             {
                 if (!boost::filesystem::is_directory(path))
                 {
-                    std::cout << path << " is not directory" << std::endl;
+                    std::cerr << path << " is not directory" << std::endl;
                 }
             }
             else
             {
                 if (!boost::filesystem::create_directories(path))
                 {
-                    std::cout << "Failed to create directory: " << path << std::endl;
+                    std::cerr << "Failed to create directory: " << path << std::endl;
                 }
             }
             std::ofstream ofs(local_xml_file.string().c_str());
@@ -1318,7 +1413,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
             }
             else
             {
-                std::cout << "Can't create " << local_xml_file.string() << std::endl;
+                std::cerr << "Can't create " << local_xml_file.string() << std::endl;
             }
         }
     }
@@ -1335,7 +1430,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil
         boost::filesystem::path path = filepath;
         if (boost::filesystem::exists(path))
             if (!boost::filesystem::remove(path))
-                std::cout << "Failed to delete " << path << std::endl;
+                std::cerr << "Failed to delete " << path << std::endl;
     }
 
     if (config.bReport)
@@ -1462,7 +1557,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath,
             {
                 bLocalXMLExists = boost::filesystem::exists(xml_file); // Check to see if downloadFile saved XML data
 
-                if (config.sXMLFile == "automatic" && !bLocalXMLExists)
+                if (config.bAutomaticXMLCreation && !bLocalXMLExists)
                 {
                     std::cout << "Starting automatic XML creation" << std::endl;
                     Util::createXML(filepath, config.iChunkSize, xml_directory);
@@ -1503,7 +1598,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath,
             std::cout << std::endl;
             if (result == CURLE_OK)
             {
-                if (config.sXMLFile == "automatic" && bParsingFailed)
+                if (config.bAutomaticXMLCreation && bParsingFailed)
                 {
                     std::cout << "Starting automatic XML creation" << std::endl;
                     Util::createXML(filepath, config.iChunkSize, xml_directory);
@@ -2040,6 +2135,84 @@ int Downloader::HTTP_Login(const std::string& email, const std::string& password
     char *redirect_url;
     curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
 
+    // Handle two step authorization
+    if (std::string(redirect_url).find("two_step") != std::string::npos)
+    {
+        std::string security_code, tagname_two_step_send, tagname_two_step_auth_letter_1, tagname_two_step_auth_letter_2, tagname_two_step_auth_letter_3, tagname_two_step_auth_letter_4, tagname_two_step_token, token_two_step;
+        std::string two_step_html = this->getResponse(redirect_url);
+        redirect_url = NULL;
+
+        tree<htmlcxx::HTML::Node> two_step_dom = parser.parseTree(two_step_html);
+        tree<htmlcxx::HTML::Node>::iterator two_step_it = two_step_dom.begin();
+        tree<htmlcxx::HTML::Node>::iterator two_step_it_end = two_step_dom.end();
+        for (; two_step_it != two_step_it_end; ++two_step_it)
+        {
+            if (two_step_it->tagName()=="input")
+            {
+                two_step_it->parseAttributes();
+                std::string id_two_step = two_step_it->attribute("id").second;
+                if (id_two_step == "second_step_authentication_token_letter_1")
+                {
+                    tagname_two_step_auth_letter_1 = two_step_it->attribute("name").second;
+                }
+                else if (id_two_step == "second_step_authentication_token_letter_2")
+                {
+                    tagname_two_step_auth_letter_2 = two_step_it->attribute("name").second;
+                }
+                else if (id_two_step == "second_step_authentication_token_letter_3")
+                {
+                    tagname_two_step_auth_letter_3 = two_step_it->attribute("name").second;
+                }
+                else if (id_two_step == "second_step_authentication_token_letter_4")
+                {
+                    tagname_two_step_auth_letter_4 = two_step_it->attribute("name").second;
+                }
+                else if (id_two_step == "second_step_authentication__token")
+                {
+                    token_two_step = two_step_it->attribute("value").second; // two step token
+                    tagname_two_step_token = two_step_it->attribute("name").second;
+                }
+            }
+            else if (two_step_it->tagName()=="button")
+            {
+                two_step_it->parseAttributes();
+                std::string id_two_step = two_step_it->attribute("id").second;
+                if (id_two_step == "second_step_authentication_send")
+                {
+                    tagname_two_step_send = two_step_it->attribute("name").second;
+                }
+            }
+        }
+        std::cerr << "Security code: ";
+        std::getline(std::cin,security_code);
+        if (security_code.size() != 4)
+        {
+            std::cerr << "Security code must be 4 characters long" << std::endl;
+            exit(1);
+        }
+        postdata = (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_1.c_str(), tagname_two_step_auth_letter_1.size()) + "=" + security_code[0]
+                + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_2.c_str(), tagname_two_step_auth_letter_2.size()) + "=" + security_code[1]
+                + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_3.c_str(), tagname_two_step_auth_letter_3.size()) + "=" + security_code[2]
+                + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_4.c_str(), tagname_two_step_auth_letter_4.size()) + "=" + security_code[3]
+                + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_send.c_str(), tagname_two_step_send.size()) + "="
+                + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_token.c_str(), tagname_two_step_token.size()) + "=" + (std::string)curl_easy_escape(curlhandle, token_two_step.c_str(), token_two_step.size());
+
+        curl_easy_setopt(curlhandle, CURLOPT_URL, "https://login.gog.com/login/two_step");
+        curl_easy_setopt(curlhandle, CURLOPT_POST, 1);
+        curl_easy_setopt(curlhandle, CURLOPT_POSTFIELDS, postdata.c_str());
+        curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeMemoryCallback);
+        curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
+        curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+        curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, 0);
+        curl_easy_setopt(curlhandle, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
+
+        // Don't follow to redirect location because it doesn't work properly. Must clean up the redirect url first.
+        curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
+        result = curl_easy_perform(curlhandle);
+        memory.str(std::string());
+        curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
+    }
+
     curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
     curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
     curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, -1);
@@ -2213,24 +2386,9 @@ std::vector<gameItem> Downloader::getGames()
 
                     if (bDownloadDLCInfo)
                     {
-                        std::string gameinfo = this->getResponse("https://www.gog.com/account/gameDetails/" + game.id + ".json");
-                        Json::Value info;
-                        if (!jsonparser->parse(gameinfo, info))
-                        {
-                            #ifdef DEBUG
-                                std::cerr << "DEBUG INFO (Downloader::getGames)" << std::endl << gameinfo << std::endl;
-                            #endif
-                            std::cout << jsonparser->getFormattedErrorMessages();
-                            delete jsonparser;
-                            exit(1);
-                        }
-                        else
-                        {
-                            #ifdef DEBUG
-                                std::cerr << "DEBUG INFO (Downloader::getGames)" << std::endl << info << std::endl;
-                            #endif
-                            game.dlcnames = Util::getDLCNamesFromJSON(info["dlcs"]);
-                        }
+                        game.gamedetailsjson = this->getGameDetailsJSON(game.id);
+                        if (!game.gamedetailsjson.empty())
+                            game.dlcnames = Util::getDLCNamesFromJSON(game.gamedetailsjson["dlcs"]);
                     }
                 }
                 games.push_back(game);
@@ -2419,6 +2577,38 @@ std::string Downloader::getSerialsFromJSON(const Json::Value& json)
     return serials.str();
 }
 
+std::string Downloader::getChangelogFromJSON(const Json::Value& json)
+{
+    std::string changelog;
+    std::string title = "Changelog";
+
+    if (!json.isMember("changelog"))
+        return std::string();
+
+    changelog = json["changelog"].asString();
+
+    if (changelog.empty())
+        return std::string();
+
+    if (json.isMember("title"))
+        title = title + ": " + json["title"].asString();
+
+    changelog = "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>" + title + "</title>\n</head>\n<body>" + changelog + "</body>\n</html>";
+
+    return changelog;
+}
+
+// Linear search.  Good thing computers are fast and lists are small.
+static int isPresent(std::vector<gameFile>& list, const boost::filesystem::path& path, Blacklist& blacklist)
+{
+    if(blacklist.isBlacklisted(path.native()))
+	return false;
+    for (unsigned int k = 0; k < list.size(); ++k)
+	if (list[k].getFilepath() == path.native())
+	    return true;
+    return false;
+}
+
 void Downloader::checkOrphans()
 {
     // Always check everything when checking for orphaned files
@@ -2433,7 +2623,7 @@ void Downloader::checkOrphans()
     std::vector<std::string> orphans;
     for (unsigned int i = 0; i < games.size(); ++i)
     {
-        std::cout << "Checking for orphaned files " << i+1 << " / " << games.size() << "\r" << std::flush;
+        std::cerr << "Checking for orphaned files " << i+1 << " / " << games.size() << "\r" << std::flush;
         std::vector<boost::filesystem::path> filepath_vector;
 
         try
@@ -2481,9 +2671,9 @@ void Downloader::checkOrphans()
                             if (boost::filesystem::is_regular_file(dir_iter->status()))
                             {
                                 std::string filepath = dir_iter->path().string();
-                                if (config.blacklist.isBlacklisted(filepath.substr(pathlen))) {
+                                if (config.ignorelist.isBlacklisted(filepath.substr(pathlen))) {
                                     if (config.bVerbose)
-                                        std::cout << "skipped blacklisted file " << filepath << std::endl;
+                                        std::cerr << "skipped ignorelisted file " << filepath << std::endl;
                                 } else {
                                     boost::regex expression(config.sOrphanRegex); // Limit to files matching the regex
                                     boost::match_results<std::string::const_iterator> what;
@@ -2496,7 +2686,7 @@ void Downloader::checkOrphans()
                     }
                 }
                 else
-                    std::cout << paths[j] << " does not exist" << std::endl;
+                    std::cerr << paths[j] << " does not exist" << std::endl;
             }
         }
         catch (const boost::filesystem::filesystem_error& ex)
@@ -2508,80 +2698,20 @@ void Downloader::checkOrphans()
         {
             for (unsigned int j = 0; j < filepath_vector.size(); ++j)
             {
-                bool bFoundFile = false; // Assume that the file is orphaned
+                bool bFoundFile = isPresent(games[i].installers, filepath_vector[j], config.blacklist)
+		               || isPresent(games[i].extras, filepath_vector[j], config.blacklist)
+		               || isPresent(games[i].patches, filepath_vector[j], config.blacklist)
+		               || isPresent(games[i].languagepacks, filepath_vector[j], config.blacklist);
 
-                // Check installers
-                for (unsigned int k = 0; k < games[i].installers.size(); ++k)
-                {
-                    if (games[i].installers[k].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                    {
-                        bFoundFile = true;
-                        break;
-                    }
-                }
-                if (!bFoundFile)
-                {   // Check extras
-                    for (unsigned int k = 0; k < games[i].extras.size(); ++k)
-                    {
-                        if (games[i].extras[k].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                        {
-                            bFoundFile = true;
-                            break;
-                        }
-                    }
-                }
-                if (!bFoundFile)
-                {   // Check patches
-                    for (unsigned int k = 0; k < games[i].patches.size(); ++k)
-                    {
-                        if (games[i].patches[k].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                        {
-                            bFoundFile = true;
-                            break;
-                        }
-                    }
-                }
-                if (!bFoundFile)
-                {   // Check language packs
-                    for (unsigned int k = 0; k < games[i].languagepacks.size(); ++k)
-                    {
-                        if (games[i].languagepacks[k].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                        {
-                            bFoundFile = true;
-                            break;
-                        }
-                    }
-                }
                 if (!bFoundFile)
                 {   // Check dlcs
                     for (unsigned int k = 0; k < games[i].dlcs.size(); ++k)
                     {
-                        for (unsigned int index = 0; index < games[i].dlcs[k].installers.size(); ++index)
-                        {
-                            if (games[i].dlcs[k].installers[index].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                            {
-                                bFoundFile = true;
-                                break;
-                            }
-                        }
-                        if (bFoundFile) break;
-                        for (unsigned int index = 0; index < games[i].dlcs[k].patches.size(); ++index)
-                        {
-                            if (games[i].dlcs[k].patches[index].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                            {
-                                bFoundFile = true;
-                                break;
-                            }
-                        }
-                        for (unsigned int index = 0; index < games[i].dlcs[k].extras.size(); ++index)
-                        {
-                            if (games[i].dlcs[k].extras[index].path.find(filepath_vector[j].filename().string()) != std::string::npos)
-                            {
-                                bFoundFile = true;
-                                break;
-                            }
-                        }
-                        if (bFoundFile) break;
+			bFoundFile = isPresent(games[i].dlcs[k].installers, filepath_vector[j], config.blacklist)
+			          || isPresent(games[i].dlcs[k].extras, filepath_vector[j], config.blacklist)
+				  || isPresent(games[i].dlcs[k].patches, filepath_vector[j], config.blacklist);
+			if(bFoundFile)
+			    break;
                     }
                 }
                 if (!bFoundFile)
@@ -2620,6 +2750,8 @@ void Downloader::checkStatus()
             {
                 boost::filesystem::path filepath = games[i].installers[j].getFilepath();
 
+		if (config.blacklist.isBlacklisted(filepath.native()))
+		    continue;
                 std::string remoteHash;
                 std::string localHash;
                 bool bHashOK = true; // assume hash OK
@@ -2682,6 +2814,8 @@ void Downloader::checkStatus()
             {
                 boost::filesystem::path filepath = games[i].extras[j].getFilepath();
 
+		if (config.blacklist.isBlacklisted(filepath.native()))
+		    continue;
                 std::string localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
                 uintmax_t filesize;
 
@@ -2703,6 +2837,8 @@ void Downloader::checkStatus()
             {
                 boost::filesystem::path filepath = games[i].patches[j].getFilepath();
 
+		if (config.blacklist.isBlacklisted(filepath.native()))
+		    continue;
                 std::string localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
                 uintmax_t filesize;
 
@@ -2724,6 +2860,8 @@ void Downloader::checkStatus()
             {
                 boost::filesystem::path filepath = games[i].languagepacks[j].getFilepath();
 
+		if (config.blacklist.isBlacklisted(filepath.native()))
+		    continue;
                 std::string localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
                 uintmax_t filesize;
 
@@ -2749,6 +2887,8 @@ void Downloader::checkStatus()
                     {
                         boost::filesystem::path filepath = games[i].dlcs[j].installers[k].getFilepath();
 
+			if (config.blacklist.isBlacklisted(filepath.native()))
+			    continue;
                         std::string remoteHash;
                         std::string localHash;
                         bool bHashOK = true; // assume hash OK
@@ -2811,6 +2951,8 @@ void Downloader::checkStatus()
                     {
                         boost::filesystem::path filepath = games[i].dlcs[j].patches[k].getFilepath();
 
+			if (config.blacklist.isBlacklisted(filepath.native()))
+			    continue;
                         std::string localHash = this->getLocalFileHash(filepath.string(), games[i].dlcs[j].gamename);
                         uintmax_t filesize;
 
@@ -2832,6 +2974,8 @@ void Downloader::checkStatus()
                     {
                         boost::filesystem::path filepath = games[i].dlcs[j].extras[k].getFilepath();
 
+			if (config.blacklist.isBlacklisted(filepath.native()))
+			    continue;
                         std::string localHash = this->getLocalFileHash(filepath.string(), games[i].dlcs[j].gamename);
                         uintmax_t filesize;
 
@@ -2863,23 +3007,35 @@ std::string Downloader::getLocalFileHash(const std::string& filepath, const std:
     else
         local_xml_file = config.sXMLDirectory + "/" + path.filename().string() + ".xml";
 
+    if (config.bAutomaticXMLCreation && !boost::filesystem::exists(local_xml_file) && boost::filesystem::exists(path))
+    {
+	std::string xml_directory = config.sXMLDirectory + "/" + gamename;
+	Util::createXML(filepath, config.iChunkSize, xml_directory);
+    }
+
     if (boost::filesystem::exists(local_xml_file))
     {
         TiXmlDocument local_xml;
         local_xml.LoadFile(local_xml_file.string());
         TiXmlNode *fileNodeLocal = local_xml.FirstChild("file");
+	if (!fileNodeLocal && config.bAutomaticXMLCreation)
+	{
+	    std::string xml_directory = config.sXMLDirectory + "/" + gamename;
+	    Util::createXML(filepath, config.iChunkSize, xml_directory);
+	    local_xml.LoadFile(local_xml_file.string());
+	    fileNodeLocal = local_xml.FirstChild("file");
+	}
         if (fileNodeLocal)
         {
             TiXmlElement *fileElemLocal = fileNodeLocal->ToElement();
             localHash = fileElemLocal->Attribute("md5");
+	    return localHash;
         }
     }
-    else
+
+    if (boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path))
     {
-        if (boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path))
-        {
-            localHash = Util::getFileHash(path.string(), RHASH_MD5);
-        }
+	localHash = Util::getFileHash(path.string(), RHASH_MD5);
     }
     return localHash;
 }
@@ -3028,6 +3184,7 @@ std::vector<gameDetails> Downloader::getGameDetailsFromJsonNode(Json::Value root
         game.title = gameDetailsNode["title"].asString();
         game.icon = gameDetailsNode["icon"].asString();
         game.serials = gameDetailsNode["serials"].asString();
+        game.changelog = gameDetailsNode["changelog"].asString();
 
         // Make a vector of valid node names to make things easier
         std::vector<std::string> nodes;
@@ -3041,8 +3198,10 @@ std::vector<gameDetails> Downloader::getGameDetailsFromJsonNode(Json::Value root
         conf.bDLC = config.bDLC;
         conf.iInstallerLanguage = config.iInstallerLanguage;
         conf.iInstallerPlatform = config.iInstallerPlatform;
+        conf.vLanguagePriority = config.vLanguagePriority;
+        conf.vPlatformPriority = config.vPlatformPriority;
         if (Util::getGameSpecificConfig(game.gamename, &conf) > 0)
-            std::cout << game.gamename << " - Language: " << conf.iInstallerLanguage << ", Platform: " << conf.iInstallerPlatform << ", DLC: " << (conf.bDLC ? "true" : "false") << std::endl;
+            std::cerr << game.gamename << " - Language: " << conf.iInstallerLanguage << ", Platform: " << conf.iInstallerPlatform << ", DLC: " << (conf.bDLC ? "true" : "false") << std::endl;
 
         for (unsigned int j = 0; j < nodes.size(); ++j)
         {
@@ -3090,7 +3249,7 @@ std::vector<gameDetails> Downloader::getGameDetailsFromJsonNode(Json::Value root
         }
         if (!game.extras.empty() || !game.installers.empty() || !game.patches.empty() || !game.languagepacks.empty() || !game.dlcs.empty())
             {
-                game.filterWithPriorities(config);
+                game.filterWithPriorities(conf);
                 details.push_back(game);
             }
     }
@@ -3110,6 +3269,7 @@ void Downloader::updateCache()
     config.iInstallerPlatform = Util::getOptionValue("all", GlobalConstants::PLATFORMS);
     config.vLanguagePriority.clear();
     config.vPlatformPriority.clear();
+    config.sIgnoreDLCCountRegex = ".*"; // Ignore DLC count for all games because GOG doesn't report DLC count correctly
 
     this->getGameList();
     this->getGameDetails();
@@ -3165,6 +3325,47 @@ void Downloader::saveSerials(const std::string& serials, const std::string& file
     return;
 }
 
+// Save changelog to file
+void Downloader::saveChangelog(const std::string& changelog, const std::string& filepath)
+{
+    // Get directory from filepath
+    boost::filesystem::path pathname = filepath;
+    std::string directory = pathname.parent_path().string();
+
+    // Check that directory exists and create subdirectories
+    boost::filesystem::path path = directory;
+    if (boost::filesystem::exists(path))
+    {
+        if (!boost::filesystem::is_directory(path))
+        {
+            std::cout << path << " is not directory" << std::endl;
+            return;
+        }
+    }
+    else
+    {
+        if (!boost::filesystem::create_directories(path))
+        {
+            std::cout << "Failed to create directory: " << path << std::endl;
+            return;
+        }
+    }
+
+    std::ofstream ofs(filepath);
+    if (ofs)
+    {
+        std::cout << "Saving changelog: " << filepath << std::endl;
+        ofs << changelog;
+        ofs.close();
+    }
+    else
+    {
+        std::cout << "Failed to create file: " << filepath << std::endl;
+    }
+
+    return;
+}
+
 void Downloader::downloadFileWithId(const std::string& fileid_string, const std::string& output_filepath)
 {
     size_t pos = fileid_string.find("/");
diff --git a/src/gamedetails.cpp b/src/gamedetails.cpp
index 5e57777..2ac9067 100644
--- a/src/gamedetails.cpp
+++ b/src/gamedetails.cpp
@@ -1,5 +1,4 @@
 #include "gamedetails.h"
-#include "util.h"
 
 gameDetails::gameDetails()
 {
@@ -11,7 +10,7 @@ gameDetails::~gameDetails()
     //dtor
 }
 
-void gameDetails::filterWithPriorities(const Config& config)
+void gameDetails::filterWithPriorities(const gameSpecificConfig& config)
 {
     if (config.vPlatformPriority.empty() && config.vLanguagePriority.empty())
         return;
@@ -21,7 +20,7 @@ void gameDetails::filterWithPriorities(const Config& config)
     filterListWithPriorities(languagepacks, config);
 }
 
-void gameDetails::filterListWithPriorities(std::vector<gameFile>& list, const Config& config)
+void gameDetails::filterListWithPriorities(std::vector<gameFile>& list, const gameSpecificConfig& config)
 {
     /*
       Compute the score of each item - we use a scoring mechanism and we keep all ties
@@ -66,12 +65,13 @@ void gameDetails::filterListWithPriorities(std::vector<gameFile>& list, const Co
         }
 }
 
-void gameDetails::makeFilepaths(const Config& config)
+void gameDetails::makeFilepaths(const gameSpecificDirectoryConfig& config)
 {
     std::string filepath;
     std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
     std::string subdir;
     this->serialsFilepath = Util::makeFilepath(directory, "serials.txt", this->gamename, subdir, 0);
+    this->changelogFilepath = Util::makeFilepath(directory, "changelog_" + gamename + ".html", this->gamename, subdir, 0);
 
     for (unsigned int i = 0; i < this->installers.size(); ++i)
     {
@@ -105,6 +105,7 @@ void gameDetails::makeFilepaths(const Config& config)
     {
         subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sInstallersSubdir : "";
         this->dlcs[i].serialsFilepath = Util::makeFilepath(directory, "serials.txt", this->gamename, subdir, 0);
+        this->dlcs[i].changelogFilepath = Util::makeFilepath(directory, "changelog_" + this->dlcs[i].gamename + ".html", this->gamename, subdir, 0);
         for (unsigned int j = 0; j < this->dlcs[i].installers.size(); ++j)
         {
             subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sInstallersSubdir : "";
@@ -136,6 +137,7 @@ Json::Value gameDetails::getDetailsAsJson()
     json["title"] = this->title;
     json["icon"] = this->icon;
     json["serials"] = this->serials;
+    json["changelog"] = this->changelog;
 
     for (unsigned int i = 0; i < this->extras.size(); ++i)
         json["extras"].append(this->extras[i].getAsJson());
@@ -161,3 +163,8 @@ std::string gameDetails::getSerialsFilepath()
 {
     return this->serialsFilepath;
 }
+
+std::string gameDetails::getChangelogFilepath()
+{
+    return this->changelogFilepath;
+}
diff --git a/src/util.cpp b/src/util.cpp
index fcfb93e..4b98c45 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -64,7 +64,7 @@ std::string Util::getFileHash(const std::string& filename, unsigned hash_id)
     rhash_library_init();
     int i = rhash_file(hash_id, filename.c_str(), digest);
     if (i < 0)
-        std::cout << "LibRHash error: " << strerror(errno) << std::endl;
+        std::cerr << "LibRHash error: " << strerror(errno) << std::endl;
     else
         rhash_print_bytes(result, digest, rhash_get_digest_size(hash_id), RHPR_HEX);
 
@@ -79,7 +79,7 @@ std::string Util::getChunkHash(unsigned char *chunk, uintmax_t chunk_size, unsig
     rhash_library_init();
     int i = rhash_msg(hash_id, chunk, chunk_size, digest);
     if (i < 0)
-        std::cout << "LibRHash error: " << strerror(errno) << std::endl;
+        std::cerr << "LibRHash error: " << strerror(errno) << std::endl;
     else
         rhash_print_bytes(result, digest, rhash_get_digest_size(hash_id), RHPR_HEX);
 
@@ -104,7 +104,7 @@ int Util::createXML(std::string filepath, uintmax_t chunk_size, std::string xml_
     boost::filesystem::path path = xml_dir;
     if (!boost::filesystem::exists(path)) {
         if (!boost::filesystem::create_directories(path)) {
-            std::cout << "Failed to create directory: " << path << std::endl;
+            std::cerr << "Failed to create directory: " << path << std::endl;
             return res;
         }
     }
@@ -115,7 +115,7 @@ int Util::createXML(std::string filepath, uintmax_t chunk_size, std::string xml_
         filesize = ftell(infile);
         rewind(infile);
     } else {
-        std::cout << filepath << " doesn't exist" << std::endl;
+        std::cerr << filepath << " doesn't exist" << std::endl;
         return res;
     }
 
@@ -159,13 +159,13 @@ int Util::createXML(std::string filepath, uintmax_t chunk_size, std::string xml_
         unsigned char *chunk = (unsigned char *) malloc(chunk_size * sizeof(unsigned char *));
         if (chunk == NULL)
         {
-            std::cout << "Memory error" << std::endl;
+            std::cerr << "Memory error" << std::endl;
             return res;
         }
         size = fread(chunk, 1, chunk_size, infile);
         if (size != chunk_size)
         {
-            std::cout << "Read error" << std::endl;
+            std::cerr << "Read error" << std::endl;
             free(chunk);
             return res;
         }
@@ -204,7 +204,7 @@ int Util::createXML(std::string filepath, uintmax_t chunk_size, std::string xml_
         fclose(xmlfile);
         res = 1;
     } else {
-        std::cout << "Can't create " << filenameXML << std::endl;
+        std::cerr << "Can't create " << filenameXML << std::endl;
         return res;
     }
 
@@ -240,12 +240,22 @@ int Util::getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf,
     {
         if (root.isMember("language"))
         {
-            conf->iInstallerLanguage = root["language"].asUInt();
+            if (root["language"].isInt())
+                conf->iInstallerLanguage = root["language"].asUInt();
+            else
+            {
+                Util::parseOptionString(root["language"].asString(), conf->vLanguagePriority, conf->iInstallerLanguage, GlobalConstants::LANGUAGES);
+            }
             res++;
         }
         if (root.isMember("platform"))
         {
-            conf->iInstallerPlatform = root["platform"].asUInt();
+            if (root["platform"].isInt())
+                conf->iInstallerPlatform = root["platform"].asUInt();
+            else
+            {
+                Util::parseOptionString(root["platform"].asString(), conf->vPlatformPriority, conf->iInstallerPlatform, GlobalConstants::PLATFORMS);
+            }
             res++;
         }
         if (root.isMember("dlc"))
@@ -258,11 +268,51 @@ int Util::getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf,
             conf->bIgnoreDLCCount = root["ignore-dlc-count"].asBool();
             res++;
         }
+        if (root.isMember("subdirectories"))
+        {
+            conf->dirConf.bSubDirectories = root["subdirectories"].asBool();
+            res++;
+        }
+        if (root.isMember("directory"))
+        {
+            conf->dirConf.sDirectory = root["directory"].asString();
+            res++;
+        }
+        if (root.isMember("subdir-game"))
+        {
+            conf->dirConf.sGameSubdir = root["subdir-game"].asString();
+            res++;
+        }
+        if (root.isMember("subdir-installers"))
+        {
+            conf->dirConf.sInstallersSubdir = root["subdir-installers"].asString();
+            res++;
+        }
+        if (root.isMember("subdir-extras"))
+        {
+            conf->dirConf.sExtrasSubdir = root["subdir-extras"].asString();
+            res++;
+        }
+        if (root.isMember("subdir-patches"))
+        {
+            conf->dirConf.sPatchesSubdir = root["subdir-patches"].asString();
+            res++;
+        }
+        if (root.isMember("subdir-language-packs"))
+        {
+            conf->dirConf.sLanguagePackSubdir = root["subdir-language-packs"].asString();
+            res++;
+        }
+        if (root.isMember("subdir-dlc"))
+        {
+            conf->dirConf.sDLCSubdir = root["subdir-dlc"].asString();
+            res++;
+        }
     }
     else
     {
-        std::cout << "Failed to parse game specific config" << std::endl;
-        std::cout << jsonparser->getFormattedErrorMessages() << std::endl;
+        std::cerr << "Failed to parse game specific config " << filepath << std::endl;
+        std::cerr << jsonparser->getFormattedErrorMessages() << std::endl;
     }
     delete jsonparser;
     if (json)
@@ -320,7 +370,7 @@ void Util::setFilePermissions(const boost::filesystem::path& path, const boost::
                 boost::filesystem::permissions(path, permissions, ec);
                 if (ec)
                 {
-                    std::cout << "Failed to set file permissions for " << path.string() << std::endl;
+                    std::cerr << "Failed to set file permissions for " << path.string() << std::endl;
                 }
             }
         }
@@ -471,3 +521,22 @@ std::string Util::getOptionNameString(const unsigned int& value, const std::vect
     }
     return str;
 }
+
+// Parse the options string
+void Util::parseOptionString(const std::string &option_string, std::vector<unsigned int> &priority, unsigned int &type, const std::vector<GlobalConstants::optionsStruct>& options)
+{
+    type = 0;
+    priority.clear();
+    std::vector<std::string> tokens_priority = Util::tokenize(option_string, ",");
+    for (std::vector<std::string>::iterator it_priority = tokens_priority.begin(); it_priority != tokens_priority.end(); it_priority++)
+    {
+        unsigned int value = 0;
+        std::vector<std::string> tokens_value = Util::tokenize(*it_priority, "+");
+        for (std::vector<std::string>::iterator it_value = tokens_value.begin(); it_value != tokens_value.end(); it_value++)
+        {
+            value |= Util::getOptionValue(*it_value, options);
+        }
+        priority.push_back(value);
+        type |= value;
+    }
+}
diff --git a/version.sh b/version.sh
deleted file mode 100644
index 113ebf6..0000000
--- a/version.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-version="LGOGDownloader `grep VERSION_NUMBER < main.cpp | head -n 1 | sed -e 's/.*\([0-9]\+\.[0-9]\+\).*/\1/'`"
-if [ -e .git/HEAD ]; then
-	if git status | grep -q 'modified:'; then
-		version="${version}M"
-	fi
-	version="$version git `git rev-parse --short HEAD`"
-fi
-echo "$version"

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/lgogdownloader.git



More information about the Pkg-games-commits mailing list