├── .editorconfig ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE.txt ├── README.md ├── debian ├── changelog ├── compat ├── control └── rules ├── src ├── Arguments.h ├── ArgumentsChecker.cpp ├── ArgumentsChecker.h ├── DatFile.cpp ├── DatFile.h ├── DatFileItem.cpp ├── DatFileItem.h ├── DatFileUnpacker.cpp ├── DatFileUnpacker.h ├── Exception.cpp ├── Exception.h ├── Format.h ├── LZSS.cpp ├── LZSS.h └── main.cpp └── vcpkg.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{cpp,h}] 2 | indent_style = space 3 | indent_size = 4 -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build-on-ubuntu: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: install build dependencies 11 | run: sudo apt install -y build-essential cmake libboost-program-options-dev zlib1g-dev 12 | - name: cmake 13 | run: cmake -DUSE_CLANG_TIDY=1 . 14 | - name: make 15 | run: make -j`nproc` 16 | build-on-macos: 17 | runs-on: macos-12 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: install build dependencies 21 | run: | 22 | brew install llvm 23 | ln -s "/usr/local/opt/llvm/bin/clang-tidy" "/usr/local/bin/clang-tidy" 24 | brew install cmake boost zlib 25 | - name: cmake 26 | run: cmake -DUSE_CLANG_TIDY=1 . 27 | - name: make 28 | run: make -j`nproc` 29 | build-on-windows: 30 | runs-on: windows-2019 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: lukka/get-cmake@latest 34 | - name: Restore artifacts, or setup vcpkg (do not install any package) 35 | uses: lukka/run-vcpkg@v10 36 | with: 37 | vcpkgGitCommitId: '14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44' 38 | - name: Run CMake consuming CMakePreset.json and vcpkg.json by mean of vcpkg. 39 | uses: lukka/run-cmake@v10 40 | with: 41 | configurePreset: 'msbuild-vcpkg' 42 | buildPreset: 'msbuild-vcpkg' 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | *.user 3 | Makefile 4 | CMakeFiles 5 | *.cmake 6 | datfile 7 | *.exe 8 | *.cbp 9 | *.dat 10 | /.idea/ 11 | /cmake-build-debug -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Under development 2 | ----------------------- 3 | - Feature: boost::program_options for cli arguments (alexeevdv) 4 | - Feature: GitHub Actions build (alexeevdv) 5 | 6 | 0.0.5 (2018-01-04) 7 | ----------------------- 8 | - Option to transform file names to lowercase (alexeevdv) 9 | - Destination path will be created if not exist (alexeevdv) 10 | 11 | 0.0.4 (2018-01-03) 12 | ----------------------- 13 | - Fix for debian packaging zlib dependency (alexeevdv) 14 | 15 | 0.0.3 (2018-01-03) 16 | ----------------------- 17 | - TravisCI configuration (alexeevdv) 18 | - Files for Debian packaging (alexeevdv) 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(dat-unpacker VERSION 0.0.5) 3 | 4 | find_package(ZLIB REQUIRED) 5 | include_directories(${ZLIB_INCLUDE_DIRS}) 6 | 7 | find_package(Boost COMPONENTS program_options REQUIRED) 8 | include_directories(${Boost_INCLUDE_DIR}) 9 | 10 | aux_source_directory(src SRC_LIST) 11 | 12 | add_executable(${PROJECT_NAME} ${SRC_LIST}) 13 | 14 | set_target_properties(${PROJECT_NAME} PROPERTIES 15 | CXX_STANDARD 17 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS NO 18 | VERSION ${PROJECT_VERSION} 19 | ) 20 | 21 | target_link_libraries(${PROJECT_NAME} PRIVATE ZLIB::ZLIB) 22 | target_link_libraries(${PROJECT_NAME} PRIVATE ${Boost_LIBRARIES}) 23 | 24 | install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) 25 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 8, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "ninja", 11 | "displayName": "Ninja Configure Settings", 12 | "description": "Sets build and install directories", 13 | "binaryDir": "${sourceDir}/builds/${presetName}", 14 | "generator": "Ninja" 15 | }, 16 | { 17 | "name": "ninja-toolchain", 18 | "displayName": "Ninja Configure Settings with toolchain", 19 | "description": "Sets build and install directories", 20 | "binaryDir": "${sourceDir}/builds/${presetName}-toolchain", 21 | "generator": "Ninja", 22 | "toolchainFile": "$env{TOOLCHAINFILE}" 23 | }, 24 | { 25 | "name": "msbuild-vcpkg", 26 | "displayName": "MSBuild (vcpkg toolchain) Configure Settings", 27 | "description": "Configure with VS generators and with vcpkg toolchain", 28 | "binaryDir": "${sourceDir}/builds/${presetName}", 29 | "generator": "Visual Studio 16 2019", 30 | "architecture": { 31 | "strategy": "set", 32 | "value": "x64" 33 | }, 34 | "cacheVariables": { 35 | "CMAKE_TOOLCHAIN_FILE": { 36 | "type": "FILEPATH", 37 | "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" 38 | } 39 | } 40 | } 41 | ], 42 | "buildPresets": [ 43 | { 44 | "name": "ninja", 45 | "configurePreset": "ninja", 46 | "displayName": "Build with Ninja", 47 | "description": "Build with Ninja" 48 | }, 49 | { 50 | "name": "ninja-toolchain", 51 | "configurePreset": "ninja-toolchain", 52 | "displayName": "Build ninja-toolchain", 53 | "description": "Build ninja with a toolchain" 54 | }, 55 | { 56 | "name": "msbuild-vcpkg", 57 | "configurePreset": "msbuild-vcpkg", 58 | "displayName": "Build MSBuild", 59 | "description": "Build with MSBuild (VS)" 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2022 Falltergeist developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dat-unpacker 2 | === 3 | [![Version](https://img.shields.io/github/release/falltergeist/dat-unpacker.svg)](https://github.com/falltergeist/dat-unpacker/releases/latest) [![Discord](https://img.shields.io/discord/401990446747877376.svg)](https://discord.gg/jxs6WRq) 4 | 5 | Console utility to unpack Fallout 1 or Fallout 2 DAT files 6 | 7 | Installation: 8 | ============= 9 | 10 | Ubuntu 11 | ------ 12 | 13 | ```bash 14 | sudo add-apt-repository ppa:falltergeist/falltergeist 15 | sudo apt-get update 16 | sudo apt-get install dat-unpacker 17 | ``` 18 | 19 | Compilation from source: 20 | ============ 21 | 22 | Dependencies: 23 | ------------- 24 | - [zlib](https://github.com/madler/zlib) 25 | - [boost::program_options](https://github.com/boostorg/program_options) 26 | 27 | Build: 28 | 29 | ```bash 30 | cmake . && make 31 | ``` 32 | 33 | Usage 34 | === 35 | ``` 36 | $ dat-unpacker --help 37 | Unpacker for Fallout 1/2 DAT files 38 | v0.0.5 (c) 2012-2022 Falltergeist Developers 39 | Example: dat-unpacker -f dat1 -s ~/fallout1/master.dat -d ~/unpacked 40 | Arguments: 41 | --help Produce help message 42 | -f [ --format ] arg (=dat2) Fallout DAT file format version. 'dat1' or 43 | 'dat2'. 'dat2' is default 44 | -q [ --quiet ] Quite mode. Do not display anything 45 | -t [ --transform ] Transform file names to lowercase 46 | -s [ --source ] arg Path to the DAT file 47 | -d [ --destination ] arg Where to extract files 48 | ``` 49 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | dat-unpacker (0.0.5-1~artful) artful; urgency=medium 2 | 3 | * No-change port to artful 4 | 5 | -- Dmitry V. Alexeev Wed, 04 Jan 2018 12:17:16 +0300 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: dat-unpacker 2 | Section: utils 3 | Priority: optional 4 | Maintainer: Dmitry V. Alexeev 5 | Build-Depends: debhelper (>=9), cmake (>=2.8), zlib1g-dev 6 | Standards-Version: 3.9.6 7 | Homepage: https://github.com/falltergeist/dat-unpacker 8 | Vcs-Git: git://github.com/falltergeist/dat-unpacker.git 9 | Vcs-Browser: https://github.com/falltergeist/dat-unpacker.git 10 | 11 | Package: dat-unpacker 12 | Architecture: any 13 | Depends: zlib1g 14 | Description: DAT unpacker for Fallout 1/2 15 | DAT unpacker for Fallout 1/2 16 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #export DH_VERBOSE = 1 5 | 6 | 7 | # see FEATURE AREAS in dpkg-buildflags(1) 8 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 9 | 10 | # see ENVIRONMENT in dpkg-buildflags(1) 11 | # package maintainers to append CFLAGS 12 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 13 | # package maintainers to append LDFLAGS 14 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 15 | 16 | 17 | %: 18 | dh $@ --buildsystem=cmake 19 | 20 | 21 | # dh_make generated override targets 22 | # This is example for Cmake (See https://bugs.debian.org/641051 ) 23 | #override_dh_auto_configure: 24 | # dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) 25 | 26 | -------------------------------------------------------------------------------- /src/Arguments.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Project includes 4 | #include "Format.h" 5 | 6 | // Third party includes 7 | 8 | // stdlib 9 | #include 10 | 11 | namespace DatUnpacker { 12 | struct Arguments { 13 | bool quietMode = false; 14 | bool transformNames = false; 15 | std::string source; 16 | std::string destination; 17 | Format format = Format::FALLOUT2; 18 | }; 19 | } -------------------------------------------------------------------------------- /src/ArgumentsChecker.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "ArgumentsChecker.h" 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | #include 8 | #include 9 | 10 | namespace DatUnpacker { 11 | bool ArgumentsChecker::check(const Arguments& arguments) { 12 | if (!checkFormat(arguments.format)) { 13 | return false; 14 | } 15 | 16 | if (!checkSource(arguments.source)) { 17 | return false; 18 | } 19 | 20 | if (!checkDestination(arguments.destination)) { 21 | return false; 22 | } 23 | 24 | return true; 25 | } 26 | 27 | const std::string& ArgumentsChecker::getErrorMessage() const { 28 | return _errorMessage; 29 | } 30 | 31 | bool ArgumentsChecker::checkFormat(const Format &format) { 32 | if (format == Format::UNKNOWN) { 33 | _errorMessage = "Unknown DAT file format version"; 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | bool ArgumentsChecker::checkSource(const std::string& source) { 40 | if (source.length() == 0) { 41 | _errorMessage = "Path to DAT file not specified"; 42 | return false; 43 | } 44 | 45 | if (!std::filesystem::is_regular_file(source)) { 46 | _errorMessage = "DAT file not found: " + source; 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | bool ArgumentsChecker::checkDestination(const std::string& destination) { 54 | if (destination.length() == 0) { 55 | _errorMessage = "Destination path not specified"; 56 | return false; 57 | } 58 | 59 | if (!std::filesystem::is_directory(destination)) { 60 | std::error_code errc; 61 | std::filesystem::create_directories(destination, errc); 62 | 63 | if (errc) { 64 | _errorMessage = "Can't create destination directory: " + destination 65 | + " (" + errc.message() + ")"; 66 | return false; 67 | } 68 | } 69 | 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/ArgumentsChecker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Project includes 4 | #include "Arguments.h" 5 | #include "Format.h" 6 | 7 | // Third party includes 8 | 9 | // stdlib 10 | #include 11 | 12 | namespace DatUnpacker { 13 | class ArgumentsChecker { 14 | public: 15 | ArgumentsChecker() = default; 16 | 17 | bool check(const Arguments& arguments); 18 | 19 | bool checkFormat(const Format& format); 20 | 21 | bool checkSource(const std::string& source); 22 | 23 | bool checkDestination(const std::string& destination); 24 | 25 | const std::string& getErrorMessage() const; 26 | 27 | private: 28 | std::string _errorMessage; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/DatFile.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "DatFile.h" 3 | #include "DatFileItem.h" 4 | 5 | // Third party includes 6 | 7 | // stdlib 8 | #include 9 | #include 10 | 11 | namespace DatUnpacker { 12 | DatFile::DatFile(std::string filename, bool write) { 13 | _filename = filename; 14 | _endianness = Endianness::Little; 15 | _version = -1; 16 | _items = 0; 17 | 18 | if (write) { 19 | _ofstream.open(_filename.c_str(), std::ios::binary | std::ios::trunc); 20 | if (!_ofstream.is_open()) { 21 | throw 1; 22 | } 23 | } 24 | } 25 | 26 | DatFile::~DatFile() { 27 | if (_ifstream.is_open()) { 28 | _ifstream.close(); 29 | } 30 | if (_ofstream.is_open()) { 31 | _ofstream.close(); 32 | } 33 | delete _items; 34 | } 35 | 36 | unsigned int DatFile::_swap(unsigned int value) { 37 | return (value << 24) | ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) | (value >> 24); 38 | } 39 | 40 | int DatFile::_swap(int value) { 41 | return _swap((unsigned int ) value); 42 | } 43 | 44 | unsigned short DatFile::_swap(unsigned short value) { 45 | return (value << 8) | (value >> 8); 46 | } 47 | 48 | short DatFile::_swap(short value) { 49 | return _swap((unsigned short) value); 50 | } 51 | 52 | void DatFile::setVersion(unsigned int value) { 53 | _version = value; 54 | if (_version == 1) { 55 | _endianness = Endianness::Big; 56 | } 57 | } 58 | 59 | unsigned int DatFile::version() { 60 | if (_version >=0) { 61 | return _version; 62 | } 63 | 64 | try { 65 | _version = 2; 66 | _fetchItems(); 67 | } catch(...) { 68 | try { 69 | _version = 1; 70 | _fetchItems(); 71 | } catch(...) { 72 | _version = 0; 73 | } 74 | } 75 | return _version; 76 | } 77 | 78 | void DatFile::_fetchItems() { 79 | if (_items) { 80 | return; 81 | } 82 | 83 | if (!_ifstream.is_open()) { 84 | _ifstream.open(_filename.c_str(), std::ios::binary); 85 | if (!_ifstream.is_open()) { 86 | throw 1; 87 | } 88 | } 89 | 90 | unsigned int realSize = size(); 91 | 92 | switch(_version) { 93 | case 1: 94 | { 95 | //fetching items 96 | _endianness = Endianness::Big; 97 | setPosition(0); 98 | unsigned int directoriesCounter; 99 | std::vector directories; 100 | 101 | *this >> directoriesCounter; 102 | skipBytes(3*4); 103 | 104 | // reading directories 105 | for (unsigned int i = 0; i != directoriesCounter; ++i) 106 | { 107 | unsigned char length; 108 | std::string name; 109 | *this >> length; 110 | name.resize(length); 111 | *this >> name; 112 | directories.push_back(name); 113 | } 114 | // reading files 115 | _items = new std::vector; 116 | for (unsigned int i = 0; i != directoriesCounter; ++i) 117 | { 118 | unsigned int filesCounter; 119 | *this >> filesCounter; 120 | skipBytes(3*4); 121 | for (unsigned int j = 0; j != filesCounter; ++j) 122 | { 123 | unsigned char length; 124 | std::string name; 125 | unsigned int compression, packedSize, unpackedSize, dataOffset; 126 | DatFileItem* item = new DatFileItem(this); 127 | 128 | *this >> length; 129 | name.resize(length); 130 | *this >> name >> compression >> dataOffset >> unpackedSize >> packedSize; 131 | 132 | if (directories.at(i) != ".") 133 | { 134 | name = directories.at(i) + "/" + name; 135 | } 136 | // Replace slashes and transform to lower case 137 | //std::replace(name.begin(),name.end(),'\\','/'); 138 | //std::transform(name.begin(),name.end(),name.begin(), ::tolower); 139 | 140 | item->setName(name) 141 | ->setCompressed(compression == 0x20 ? false : true) 142 | ->setDataOffset(dataOffset) 143 | ->setUnpackedSize(unpackedSize) 144 | ->setPackedSize(packedSize); 145 | _items->push_back(item); 146 | } 147 | } 148 | break; 149 | } 150 | case 2: 151 | { 152 | // checking size signature 153 | if (realSize < 12) throw 1; 154 | setPosition(realSize - 4); 155 | unsigned int size; 156 | *this >> size; 157 | if (realSize != size) throw 1; 158 | 159 | // fetching items 160 | setPosition(realSize - 8); 161 | unsigned int filesTreeSize; 162 | *this >> filesTreeSize; 163 | //std::cout << "FilesTreeSize: " << filesTreeSize << std::endl; 164 | 165 | setPosition(realSize - filesTreeSize - 8); 166 | unsigned int filesCounter; 167 | *this >> filesCounter; 168 | //std::cout << "FilesCounter: " << filesCounter << std::endl; 169 | 170 | _items = new std::vector; 171 | for (unsigned int i = 0; i != filesCounter; ++i) 172 | { 173 | unsigned int length; 174 | std::string name; 175 | unsigned char compressed; 176 | unsigned int unpackedSize, packedSize, dataOffset; 177 | DatFileItem* item = new DatFileItem(this); 178 | 179 | //std::cout << "Pos: " << this->position() << std::endl; 180 | *this >> length; 181 | //std::cout << "Length: " << length << std::endl; 182 | name.resize(length); 183 | *this >> name >> compressed >> unpackedSize >> packedSize >> dataOffset; 184 | 185 | // Replace slashes and transform to lower case 186 | //std::replace(name.begin(),name.end(),'\\','/'); 187 | //std::transform(name.begin(),name.end(),name.begin(), ::tolower); 188 | 189 | item->setName(name) 190 | ->setUnpackedSize(unpackedSize) 191 | ->setPackedSize(packedSize) 192 | ->setCompressed((bool) compressed) 193 | ->setDataOffset(dataOffset); 194 | _items->push_back(item); 195 | } 196 | break; 197 | } 198 | default: 199 | throw 1; 200 | } 201 | } 202 | 203 | int DatFile::position() 204 | { 205 | if (_ifstream.is_open()) 206 | { 207 | return _ifstream.tellg(); 208 | } 209 | 210 | if (_ofstream.is_open()) 211 | { 212 | return _ofstream.tellp(); 213 | } 214 | 215 | return -1; 216 | } 217 | 218 | void DatFile::setPosition(unsigned int position) 219 | { 220 | if (_ifstream.is_open()) 221 | { 222 | _ifstream.seekg(position, std::ios::beg); 223 | return; 224 | } 225 | 226 | if (_ofstream.is_open()) 227 | { 228 | _ofstream.seekp(position, std::ios::beg); 229 | return; 230 | } 231 | } 232 | 233 | int DatFile::size() 234 | { 235 | int oldPosition = position(); 236 | 237 | if (_ifstream.is_open()) 238 | { 239 | _ifstream.seekg(0, std::ios::end); 240 | } 241 | 242 | if (_ofstream.is_open()) 243 | { 244 | _ofstream.seekp(0, std::ios::end); 245 | } 246 | 247 | int value = position(); 248 | setPosition(oldPosition); 249 | return value; 250 | } 251 | 252 | DatFile& DatFile::operator>>(unsigned int &value) 253 | { 254 | _ifstream.read((char*)&value, sizeof(unsigned int)); 255 | if (_endianness == Endianness::Big) 256 | { 257 | value = _swap(value); 258 | } 259 | return *this; 260 | } 261 | 262 | DatFile& DatFile::operator>>(int &value) 263 | { 264 | return operator>>((unsigned int&)value); 265 | } 266 | 267 | DatFile& DatFile::operator>>(unsigned short &value) 268 | { 269 | _ifstream.read((char*)&value, sizeof(unsigned short)); 270 | if (_endianness == Endianness::Big) 271 | { 272 | value = _swap(value); 273 | } 274 | return *this; 275 | } 276 | 277 | DatFile& DatFile::operator>>(short &value) 278 | { 279 | return operator>>((unsigned short&)value); 280 | } 281 | 282 | DatFile& DatFile::operator>>(unsigned char &value) 283 | { 284 | _ifstream.read((char*)&value, sizeof(unsigned char)); 285 | return *this; 286 | } 287 | 288 | DatFile& DatFile::operator>>(char &value) 289 | { 290 | return operator>>((unsigned char&)value); 291 | } 292 | 293 | DatFile& DatFile::operator>>(std::string &value) 294 | { 295 | _ifstream.read(&value[0], value.size()); 296 | return *this; 297 | } 298 | 299 | std::vector* DatFile::items() 300 | { 301 | return _items; 302 | } 303 | 304 | void DatFile::skipBytes(unsigned int value) 305 | { 306 | setPosition(position() + value); 307 | } 308 | 309 | void DatFile::readBytes(unsigned char* destination, unsigned int value) 310 | { 311 | _ifstream.read((char*)destination, value); 312 | } 313 | 314 | std::string DatFile::name() 315 | { 316 | size_t pos = _filename.find_last_of("/\\"); 317 | if (pos != std::string::npos) 318 | { 319 | return _filename.substr(pos + 1); 320 | } 321 | return _filename; 322 | } 323 | 324 | void DatFile::setItems(std::vector* value) 325 | { 326 | delete [] _items; 327 | _items = value; 328 | } 329 | 330 | DatFile& DatFile::operator<<(unsigned int value) 331 | { 332 | if (_endianness == Endianness::Big) 333 | { 334 | value = _swap(value); 335 | } 336 | _ofstream.write((char*)&value, 4); 337 | _ofstream.flush(); 338 | 339 | return *this; 340 | } 341 | 342 | DatFile& DatFile::operator<<(int value) 343 | { 344 | return operator<<((unsigned int) value); 345 | } 346 | 347 | DatFile& DatFile::operator<<(unsigned short value) 348 | { 349 | if (_endianness == Endianness::Big) 350 | { 351 | value = _swap(value); 352 | } 353 | _ofstream.write((char*)&value, 2); 354 | _ofstream.flush(); 355 | 356 | return *this; 357 | } 358 | 359 | DatFile& DatFile::operator<<(short value) 360 | { 361 | return operator<<((unsigned short) value); 362 | } 363 | 364 | DatFile& DatFile::operator<<(unsigned char value) 365 | { 366 | _ofstream.write((char*)&value, 1); 367 | _ofstream.flush(); 368 | return *this; 369 | } 370 | 371 | DatFile& DatFile::operator<<(char value) 372 | { 373 | return operator<<((unsigned char) value); 374 | } 375 | 376 | DatFile& DatFile::operator<<(std::string value) 377 | { 378 | _ofstream << value; 379 | _ofstream.flush(); 380 | return *this; 381 | } 382 | 383 | void DatFile::writeBytes(unsigned char* source, unsigned int value) 384 | { 385 | _ofstream.write((char*)source, value); 386 | _ofstream.flush(); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/DatFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Project includes 4 | 5 | // Third party includes 6 | 7 | // stdlib 8 | #include 9 | #include 10 | #include 11 | 12 | namespace DatUnpacker 13 | { 14 | class DatFileItem; 15 | 16 | class DatFile 17 | { 18 | public: 19 | enum class Endianness { 20 | Little, 21 | Big 22 | }; 23 | 24 | enum VERSION 25 | { 26 | FALLOUT1, 27 | FALLOUT2 28 | }; 29 | 30 | DatFile(std::string filename, bool write = false); 31 | ~DatFile(); 32 | 33 | std::string name(); 34 | unsigned int version(); 35 | void setVersion(unsigned int value); 36 | int position(); 37 | void setPosition(unsigned int position); 38 | int size(); 39 | void skipBytes(unsigned int value); 40 | void readBytes(unsigned char* destination, unsigned int value); 41 | void writeBytes(unsigned char* source, unsigned int value); 42 | std::vector* items(); 43 | void setItems(std::vector* value); 44 | DatFile& operator<<(unsigned int value); 45 | DatFile& operator<<(int value); 46 | DatFile& operator<<(unsigned short value); 47 | DatFile& operator<<(short value); 48 | DatFile& operator<<(unsigned char value); 49 | DatFile& operator<<(char value); 50 | DatFile& operator<<(std::string value); 51 | 52 | protected: 53 | int _version; 54 | Endianness _endianness; 55 | std::ifstream _ifstream; 56 | std::ofstream _ofstream; 57 | std::string _filename; 58 | std::vector* _items; 59 | unsigned int _swap(unsigned int value); 60 | int _swap(int value); 61 | unsigned short _swap(unsigned short value); 62 | short _swap(short value); 63 | void _fetchItems(); 64 | DatFile& operator>>(unsigned int &value); 65 | DatFile& operator>>(int &value); 66 | DatFile& operator>>(unsigned short &value); 67 | DatFile& operator>>(short &value); 68 | DatFile& operator>>(unsigned char &value); 69 | DatFile& operator>>(char &value); 70 | DatFile& operator>>(std::string &value); 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/DatFileItem.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "DatFileItem.h" 3 | #include "DatFile.h" 4 | #include "LZSS.h" 5 | 6 | // Third party includes 7 | #include "zlib.h" 8 | 9 | // stdlib 10 | 11 | namespace DatUnpacker 12 | { 13 | DatFileItem::DatFileItem(DatFile* datFile) 14 | { 15 | _datFile = datFile; 16 | _packedSize = 0; 17 | _unpackedSize = 0; 18 | _compressed = false; 19 | _dataOffset = 0; 20 | _packedData = 0; 21 | _unpackedData = 0; 22 | } 23 | 24 | DatFileItem::~DatFileItem() 25 | { 26 | delete [] _packedData; _packedData = 0; 27 | delete [] _unpackedData; _unpackedData = 0; 28 | 29 | } 30 | 31 | std::string DatFileItem::name() 32 | { 33 | return _name; 34 | } 35 | 36 | unsigned int DatFileItem::packedSize() 37 | { 38 | return _packedSize; 39 | } 40 | 41 | unsigned int DatFileItem::unpackedSize() 42 | { 43 | return _unpackedSize; 44 | } 45 | 46 | unsigned int DatFileItem::dataOffset() 47 | { 48 | return _dataOffset; 49 | } 50 | 51 | bool DatFileItem::compressed() 52 | { 53 | return _compressed; 54 | } 55 | 56 | DatFileItem* DatFileItem::setName(std::string value) 57 | { 58 | _name = value; 59 | return this; 60 | } 61 | 62 | DatFileItem* DatFileItem::setPackedSize(unsigned int value) 63 | { 64 | _packedSize = value; 65 | return this; 66 | } 67 | 68 | DatFileItem* DatFileItem::setUnpackedSize(unsigned int value) 69 | { 70 | _unpackedSize = value; 71 | return this; 72 | } 73 | 74 | DatFileItem* DatFileItem::setDataOffset(unsigned int value) 75 | { 76 | _dataOffset = value; 77 | return this; 78 | } 79 | 80 | DatFileItem* DatFileItem::setCompressed(bool value) 81 | { 82 | _compressed = value; 83 | return this; 84 | } 85 | 86 | unsigned char* DatFileItem::data() 87 | { 88 | if (_unpackedData) return _unpackedData; 89 | 90 | _unpackedData = new unsigned char[_unpackedSize]; 91 | _datFile->setPosition(_dataOffset); 92 | 93 | // If compressed 94 | if (!_compressed) 95 | { 96 | _datFile->readBytes(_unpackedData, _unpackedSize); 97 | return _unpackedData; 98 | } 99 | 100 | // If not compressed 101 | _packedData = new unsigned char[_packedSize]; 102 | _datFile->readBytes(_packedData, _packedSize); 103 | switch(_datFile->version()) 104 | { 105 | case 1: 106 | { 107 | LZSS::decompress(_packedData, _packedSize, _unpackedData, _unpackedSize); 108 | break; 109 | } 110 | case 2: 111 | { 112 | // unpacking 113 | z_stream zStream; 114 | zStream.total_in = zStream.avail_in = _packedSize; 115 | zStream.avail_in = _packedSize; 116 | zStream.next_in = _packedData; 117 | zStream.total_out = zStream.avail_out = _unpackedSize; 118 | zStream.next_out = _unpackedData; 119 | zStream.zalloc = Z_NULL; 120 | zStream.zfree = Z_NULL; 121 | zStream.opaque = Z_NULL; 122 | inflateInit( &zStream ); 123 | inflate( &zStream, Z_FINISH ); 124 | inflateEnd( &zStream ); 125 | break; 126 | } 127 | } 128 | delete [] _packedData; _packedData = 0; 129 | 130 | return _unpackedData; 131 | } 132 | 133 | std::string DatFileItem::extension() 134 | { 135 | return _name.substr(_name.length() - 3); 136 | } 137 | 138 | void DatFileItem::freeData() 139 | { 140 | delete [] _packedData; _packedData = 0; 141 | delete [] _unpackedData; _unpackedData = 0; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/DatFileItem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Project includes 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | #include 8 | 9 | namespace DatUnpacker 10 | { 11 | class DatFile; 12 | 13 | class DatFileItem 14 | { 15 | public: 16 | DatFileItem(DatFile* datFile); 17 | ~DatFileItem(); 18 | std::string name(); 19 | std::string extension(); 20 | unsigned int packedSize(); 21 | unsigned int unpackedSize(); 22 | bool compressed(); 23 | unsigned int dataOffset(); 24 | unsigned char* data(); 25 | void freeData(); 26 | DatFileItem* setPackedSize(unsigned int value); 27 | DatFileItem* setUnpackedSize(unsigned int value); 28 | DatFileItem* setDataOffset(unsigned int value); 29 | DatFileItem* setCompressed(bool value); 30 | DatFileItem* setName(std::string value); 31 | protected: 32 | DatFile* _datFile; 33 | unsigned char* _packedData; 34 | unsigned char* _unpackedData; 35 | std::string _name; 36 | unsigned int _packedSize; 37 | unsigned int _unpackedSize; 38 | bool _compressed; 39 | unsigned int _dataOffset; 40 | }; 41 | } -------------------------------------------------------------------------------- /src/DatFileUnpacker.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "DatFile.h" 3 | #include "DatFileItem.h" 4 | #include "DatFileUnpacker.h" 5 | #include "Arguments.h" 6 | 7 | // Third party includes 8 | 9 | // stdlib 10 | #include 11 | #include 12 | #include 13 | 14 | namespace DatUnpacker 15 | { 16 | bool DatFileUnpacker::unpack(const Arguments& arguments) 17 | { 18 | DatFile datFile(arguments.source); 19 | 20 | //if (arguments.format == Format::FALLOUT2) { 21 | // datFile.setVersion(DatFile::VERSION::FALLOUT2); 22 | //} else { 23 | // datFile.setVersion(DatFile::VERSION::FALLOUT1); 24 | //} 25 | 26 | // TODO: refactor DatFile to get rid of this call 27 | if (datFile.version()) { 28 | 29 | } 30 | 31 | if (!checkDatFile(datFile, arguments)) { 32 | return false; 33 | } 34 | 35 | // extract items 36 | for (auto item : *datFile.items()) { 37 | 38 | std::string name = item->name(); 39 | std::replace(name.begin(), name.end(), '\\', '/'); 40 | 41 | if (arguments.transformNames) { 42 | std::transform(name.begin(), name.end(), name.begin(), ::tolower); 43 | } 44 | 45 | std::string fullpath = arguments.destination + "/" + name; 46 | std::string directory = fullpath.substr(0, fullpath.find_last_of('/')); 47 | 48 | std::filesystem::create_directories(directory); 49 | 50 | if (!arguments.quietMode) { 51 | std::cout << fullpath << std::endl; 52 | } 53 | 54 | std::ofstream stream; 55 | stream.open(fullpath.c_str(), std::ofstream::out | std::ofstream::binary); 56 | stream.write((char*)item->data(), item->unpackedSize()); 57 | stream.close(); 58 | delete [] item->data(); 59 | } 60 | 61 | return true; 62 | } 63 | 64 | std::string DatFileUnpacker::getErrorMessage() const 65 | { 66 | return _errorMessage; 67 | } 68 | 69 | bool DatFileUnpacker::checkDatFile(const DatFile& datFile, const Arguments& arguments) 70 | { 71 | // TODO check file integrity 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/DatFileUnpacker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Project includes 4 | #include "Arguments.h" 5 | 6 | // Third party includes 7 | 8 | // stdlib 9 | #include 10 | 11 | namespace DatUnpacker 12 | { 13 | class DatFile; 14 | 15 | class DatFileUnpacker 16 | { 17 | public: 18 | DatFileUnpacker() = default; 19 | bool unpack(const Arguments& arguments); 20 | std::string getErrorMessage() const; 21 | bool checkDatFile(const DatFile& datFile, const Arguments& arguments); 22 | 23 | private: 24 | std::string _errorMessage; 25 | }; 26 | } -------------------------------------------------------------------------------- /src/Exception.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "Exception.h" 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | 8 | namespace DatUnpacker 9 | { 10 | Exception::Exception(const char* message) : _message(message) 11 | { 12 | } 13 | 14 | Exception::Exception(const std::string& message) : _message(message) 15 | { 16 | } 17 | 18 | Exception::~Exception() throw() 19 | { 20 | } 21 | 22 | const char* Exception::what() const throw() 23 | { 24 | return _message.c_str(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Project includes 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | #include 8 | #include 9 | 10 | namespace DatUnpacker 11 | { 12 | class Exception : std::exception 13 | { 14 | public: 15 | explicit Exception(const char* message); 16 | explicit Exception(const std::string& message); 17 | virtual ~Exception() throw(); 18 | virtual const char* what() const throw(); 19 | private: 20 | std::string _message; 21 | }; 22 | } -------------------------------------------------------------------------------- /src/Format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Project includes 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | 8 | namespace DatUnpacker { 9 | enum class Format { 10 | UNKNOWN = 0, 11 | FALLOUT1, 12 | FALLOUT2 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/LZSS.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "LZSS.h" 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | #include 8 | #include 9 | 10 | namespace DatUnpacker 11 | { 12 | void LZSS::decompress(unsigned char* input, unsigned int inputSize, unsigned char* output, unsigned int outputSize) 13 | { 14 | const unsigned short D = 4096; 15 | unsigned short DO, DI = 0; 16 | short N; 17 | unsigned char dictionary[D]; 18 | unsigned char F = 0; 19 | unsigned char L = 0; 20 | unsigned char* inputEnd = input + inputSize; 21 | unsigned char* outputEnd = output + outputSize; 22 | 23 | while (input < inputEnd) 24 | { 25 | 26 | N = *(input++) << 8; 27 | N |= *(input++); 28 | if (N == 0) return; 29 | 30 | if (N < 0) 31 | { 32 | unsigned char* end = input - N; 33 | while (input < end && output < outputEnd) 34 | { 35 | unsigned char byte = *(input++); 36 | *(output++) = byte; 37 | } 38 | } 39 | else 40 | { 41 | DO = D-18; 42 | memset(dictionary, ' ', D); 43 | unsigned char* end = input + N; 44 | while (input < end) 45 | { 46 | F = *(input++); 47 | for (unsigned int i = 0; i != 8 && input < end; ++i) 48 | { 49 | if ((F & 1) != 0) 50 | { 51 | unsigned char byte = *(input++); 52 | *(output++) = byte; 53 | dictionary[DO] = byte; 54 | DO++; 55 | if (DO >= D) DO = 0; 56 | } 57 | else 58 | { 59 | DI = *(input++); 60 | L = *(input++); 61 | DI = DI | ((0xF0 & L) << 4); 62 | L &= 0x0F; 63 | 64 | for (int j = 0; j < L+3; j++) 65 | { 66 | unsigned char byte = dictionary[DI]; 67 | *(output++) = byte; 68 | dictionary[DO] = byte; 69 | DI++; 70 | DO++; 71 | if (DO >= D) DO = 0; 72 | if (DI >= D) DI = 0; 73 | } 74 | } 75 | F >>= 1; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/LZSS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Project includes 3 | 4 | // Third party includes 5 | 6 | // stdlib 7 | 8 | namespace DatUnpacker 9 | { 10 | class LZSS 11 | { 12 | public: 13 | static void decompress(unsigned char* input, unsigned int inputSize, unsigned char* output, unsigned int outputSize); 14 | }; 15 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Project includes 2 | #include "ArgumentsChecker.h" 3 | #include "DatFileUnpacker.h" 4 | 5 | // Third party includes 6 | #include 7 | 8 | // stdlib 9 | #include 10 | 11 | using namespace DatUnpacker; 12 | 13 | void usage(const boost::program_options::options_description& description) 14 | { 15 | std::cout << "Unpacker for Fallout 1/2 DAT files" << std::endl; 16 | std::cout << "v0.0.5 (c) 2012-2022 Falltergeist Developers" << std::endl; 17 | std::cout << "Example: dat-unpacker -f dat1 -s ~/fallout1/master.dat -d ~/unpacked" << std::endl; 18 | std::cout << description << std::endl; 19 | std::cout << std::endl; 20 | } 21 | 22 | int main(int argc, char** argv) 23 | { 24 | namespace po = boost::program_options; 25 | 26 | po::options_description argumentsDescription("Arguments"); 27 | argumentsDescription.add_options() 28 | ("help", "Produce help message") 29 | ("format,f", po::value()->required()->default_value("dat2"), "Fallout DAT file format version. 'dat1' or 'dat2'. 'dat2' is default") 30 | ("quiet,q", po::bool_switch()->default_value(false), "Quite mode. Do not display anything") 31 | ("transform,t", po::bool_switch()->default_value(false), "Transform file names to lowercase") 32 | ("source,s", po::value()->required(), "Path to the DAT file") 33 | ("destination,d", po::value()->required(), "Where to extract files"); 34 | 35 | po::variables_map vm; 36 | po::store(po::command_line_parser(argc, argv).options(argumentsDescription).run(), vm); 37 | 38 | if (vm.count("help")) { 39 | usage(argumentsDescription); 40 | return 1; 41 | } 42 | 43 | try { 44 | po::notify(vm); 45 | } catch (std::exception& e) { 46 | std::cout << "Error: " << e.what() << std::endl; 47 | usage(argumentsDescription); 48 | return 1; 49 | } 50 | 51 | Arguments arguments = { 52 | vm["quiet"].as(), 53 | vm["transform"].as(), 54 | vm["source"].as(), 55 | vm["destination"].as(), 56 | vm["format"].as() == "dat2" ? Format::FALLOUT2 : Format::FALLOUT1 57 | }; 58 | 59 | ArgumentsChecker argumentsChecker; 60 | if (!argumentsChecker.check(arguments)) { 61 | if (!arguments.quietMode) { 62 | std::cerr << argumentsChecker.getErrorMessage() << std::endl << std::endl; 63 | } 64 | return 1; 65 | } 66 | 67 | DatFileUnpacker datFileUnpacker; 68 | if (!datFileUnpacker.unpack(arguments)) { 69 | if (!arguments.quietMode) { 70 | std::cerr << datFileUnpacker.getErrorMessage() << std::endl << std::endl; 71 | } 72 | return 1; 73 | } 74 | 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "falltergeist-dat-unpacker", 3 | "version-string": "0.0.5", 4 | "dependencies": [ 5 | "boost-program-options", 6 | "zlib" 7 | ] 8 | } 9 | --------------------------------------------------------------------------------