├── valgrind.sh ├── rebuild.sh ├── lib ├── md5 │ ├── CMakeLists.txt │ ├── global.h │ ├── md5.h │ └── md5c.c ├── log.c ├── directory.c ├── log.h ├── CMakeLists.txt ├── component.c ├── converter.h ├── file_group.c ├── unshield_config.h.in ├── cabfile.h ├── libunshield.h ├── internal.h ├── helper.c ├── converter.c ├── libunshield.c └── file.c ├── libunshield.pc.in ├── .editorconfig ├── .gitignore ├── ChangeLog ├── src ├── CMakeLists.txt ├── unshield-deobfuscate.c └── unshield.c ├── LICENSE ├── win32_msvc ├── unistd.h ├── getopt.h └── dirent.h ├── .github └── workflows │ ├── codeql-analysis.yml │ └── cmake.yml ├── man └── unshield.1 ├── README.md └── CMakeLists.txt /valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | libtool --mode=execute valgrind --num-callers=10 --leak-check=yes `dirname $0`/src/unshield $@ 3 | -------------------------------------------------------------------------------- /rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -x 4 | export CFLAGS="-Wall -Werror -ggdb3" 5 | cd `dirname $0` 6 | mkdir -p build 7 | cd build 8 | cmake -DCMAKE_INSTALL_PREFIX:PATH=/var/tmp/unshield .. && make && make install 9 | -------------------------------------------------------------------------------- /lib/md5/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LIBMD5_HEADERS 2 | global.h 3 | md5.h 4 | ) 5 | 6 | set(LIBMD5_SOURCES 7 | md5c.c 8 | ) 9 | 10 | if(BUILD_STATIC AND MSVC) 11 | set_msvc_runtime_static() 12 | endif() 13 | 14 | add_library(md5 OBJECT ${LIBMD5_HEADERS} ${LIBMD5_SOURCES}) 15 | -------------------------------------------------------------------------------- /lib/md5/global.h: -------------------------------------------------------------------------------- 1 | /* GLOBAL.H - RSAREF types and constants 2 | */ 3 | 4 | /* POINTER defines a generic pointer type */ 5 | typedef unsigned char *POINTER; 6 | 7 | /* UINT2 defines a two byte word */ 8 | typedef unsigned short int UINT2; 9 | 10 | /* UINT4 defines a four byte word */ 11 | typedef unsigned int UINT4; 12 | 13 | #define PROTO_LIST(list) list 14 | -------------------------------------------------------------------------------- /libunshield.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_FULL_BINDIR@ 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 5 | 6 | Name: libunshield 7 | Description: Library to extract CAB files from InstallShield installers 8 | Version: @PROJECT_VERSION@ 9 | Libs: -L${libdir} -lunshield 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Tab indentation (no size specified) 12 | [Makefile] 13 | indent_style = tab 14 | 15 | [*.{c,h,cpp,hpp}] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /lib/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include 3 | #include 4 | 5 | /* evil static data */ 6 | static int current_log_level = UNSHIELD_LOG_LEVEL_HIGHEST; 7 | 8 | void unshield_set_log_level(int level) 9 | { 10 | current_log_level = level; 11 | } 12 | 13 | void _unshield_log(int level, const char* file, int line, const char* format, ...) 14 | { 15 | va_list ap; 16 | 17 | if (level > current_log_level) 18 | return; 19 | 20 | fprintf(stderr, "[%s:%i] ", file, line); 21 | 22 | va_start(ap, format); 23 | vfprintf(stderr, format, ap); 24 | va_end(ap); 25 | 26 | fprintf(stderr, "\n"); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | **/CMakeCache.txt 3 | **/CMakeFiles/ 4 | *cmake_install.cmake 5 | **/CMakeScripts/ 6 | *.core 7 | .gdb_history 8 | *.la 9 | lib/libunshield.so* 10 | .libs 11 | lib/unshield_config.h 12 | libunshield.pc 13 | *.lo 14 | ltmain.sh 15 | Makefile 16 | *.o 17 | src/unshield 18 | src/unshield-deobfuscate 19 | .*.swp 20 | .idea 21 | build 22 | /cmake-build-debug/ 23 | .vs 24 | *.sln 25 | *.vcxproj 26 | *.vcxproj.filters 27 | *.vcxproj.user 28 | DartConfiguration.tcl 29 | CTestTestfile.cmake 30 | *.tlog 31 | *.obj 32 | *.FileListAbsolute.txt 33 | *.lastbuildstate 34 | *.log 35 | *.pdb 36 | *.lib 37 | *.recipe 38 | *.exp 39 | *.ilk 40 | *.exe 41 | *.dll 42 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2009-07-16 Mark Ellis 2 | 3 | * 0.6 release 4 | 5 | 2009-06-18 Mark Ellis 6 | 7 | * src/unshield.c - fix use of -L (lowercase) option with -d 8 | 9 | 2008-05-06 Jonny Lamb 10 | 11 | * .cvsignore: 12 | * cvsclean: 13 | * lib/.cvsignore: 14 | * lib/md5/.cvsignore: 15 | * src/.cvsignore: Removed useless CVS ignore and clean files. 16 | 17 | * bootstrap: 18 | * unshield.spec.in: Removed spec file. 19 | 20 | 2008-05-06 Jonny Lamb 21 | 22 | * VERSION: Upped version number to 0.5.1. 23 | 24 | 2008-02-15 Mark Ellis 25 | 26 | * added man page for unshield 27 | 28 | 2008-01-09 Mark Ellis 29 | 30 | * started changelog 31 | * added LICENSE to EXTRA_DIST 32 | * added cabfile.h to libunshield_la_SOURCES 33 | 34 | -------------------------------------------------------------------------------- /lib/directory.c: -------------------------------------------------------------------------------- 1 | #include "internal.h" 2 | #include "log.h" 3 | 4 | int unshield_directory_count(Unshield* unshield) 5 | { 6 | if (unshield) 7 | { 8 | /* XXX: multi-volume support... */ 9 | Header* header = unshield->header_list; 10 | 11 | return header->cab.directory_count; 12 | } 13 | else 14 | return -1; 15 | } 16 | 17 | const char* unshield_directory_name(Unshield* unshield, int index) 18 | { 19 | if (unshield && index >= 0) 20 | { 21 | /* XXX: multi-volume support... */ 22 | Header* header = unshield->header_list; 23 | 24 | if (index < (int)header->cab.directory_count) 25 | return unshield_get_utf8_string(header, 26 | header->data + 27 | header->common.cab_descriptor_offset + 28 | header->cab.file_table_offset + 29 | header->file_table[index]); 30 | } 31 | 32 | unshield_warning("Failed to get directory name %i", index); 33 | return NULL; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /lib/log.h: -------------------------------------------------------------------------------- 1 | #ifndef __log_h__ 2 | #define __log_h__ 3 | 4 | #include "internal.h" 5 | 6 | #define UNSHIELD_LOG_LEVEL_LOWEST 0 7 | 8 | #define UNSHIELD_LOG_LEVEL_ERROR 1 9 | #define UNSHIELD_LOG_LEVEL_WARNING 2 10 | #define UNSHIELD_LOG_LEVEL_TRACE 3 11 | 12 | #define UNSHIELD_LOG_LEVEL_HIGHEST 4 13 | 14 | #ifdef __cplusplus 15 | extern "C" 16 | { 17 | #endif 18 | 19 | void _unshield_log(int level, const char* file, int line, const char* format, ...); 20 | 21 | #define unshield_trace(format, ...) \ 22 | _unshield_log(UNSHIELD_LOG_LEVEL_TRACE,__FUNCTION__, __LINE__, format, ##__VA_ARGS__) 23 | 24 | #define unshield_warning(format, ...) \ 25 | _unshield_log(UNSHIELD_LOG_LEVEL_WARNING,__FUNCTION__, __LINE__, format, ##__VA_ARGS__) 26 | 27 | #define unshield_error(format, ...) \ 28 | _unshield_log(UNSHIELD_LOG_LEVEL_ERROR,__FUNCTION__, __LINE__, format, ##__VA_ARGS__) 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | 35 | #endif 36 | 37 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) 2 | if("${isSystemDir}" STREQUAL "-1") 3 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") 4 | endif() 5 | 6 | add_executable(unshield unshield.c) 7 | target_link_libraries(unshield libunshield) 8 | if(WIN32) 9 | if(HAVE_ICONV) 10 | target_link_libraries(unshield iconv) 11 | endif() 12 | target_link_libraries(unshield ${ZLIB_LIBRARIES}) 13 | endif() 14 | 15 | add_executable(unshield-deobfuscate unshield-deobfuscate.c) 16 | target_link_libraries(unshield-deobfuscate libunshield) 17 | if(WIN32) 18 | target_link_libraries(unshield-deobfuscate ${ZLIB_LIBRARIES}) 19 | endif() 20 | 21 | if(BUILD_STATIC AND MSVC) 22 | set_msvc_runtime_static() 23 | endif() 24 | 25 | install(TARGETS unshield EXPORT unshieldConfig RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2003 David Eriksson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /src/unshield-deobfuscate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../lib/libunshield.h" 5 | 6 | int main(int argc, char** argv) 7 | { 8 | unsigned seed = 0; 9 | FILE* input = NULL; 10 | FILE* output = NULL; 11 | size_t size; 12 | unsigned char buffer[16384]; 13 | 14 | if (argc != 3) 15 | { 16 | fprintf(stderr, 17 | "Syntax:\n" 18 | " %s INPUT-FILE OUTPUT-FILE\n", 19 | argv[0]); 20 | exit(1); 21 | } 22 | 23 | input = fopen(argv[1], "rb"); 24 | if (!input) 25 | { 26 | fprintf(stderr, 27 | "Failed to open %s for reading\n", 28 | argv[1]); 29 | exit(2); 30 | } 31 | 32 | output = fopen(argv[2], "wb"); 33 | if (!output) 34 | { 35 | fprintf(stderr, 36 | "Failed to open %s for writing\n", 37 | argv[2]); 38 | exit(3); 39 | } 40 | 41 | 42 | while ((size = fread(buffer, 1, sizeof(buffer), input)) != 0) 43 | { 44 | unshield_deobfuscate(buffer, size, &seed); 45 | if (fwrite(buffer, 1, size, output) != size) 46 | { 47 | fprintf(stderr, 48 | "Failed to write %lu bytes to %s\n", 49 | (unsigned long)size, argv[2]); 50 | exit(4); 51 | } 52 | } 53 | 54 | fclose(input); 55 | fclose(output); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /win32_msvc/unistd.h: -------------------------------------------------------------------------------- 1 | #ifndef _UNISTD_H 2 | #define _UNISTD_H 1 3 | 4 | /* This file intended to serve as a drop-in replacement for 5 | * unistd.h on Windows 6 | * Please add functionality as neeeded 7 | */ 8 | 9 | #include 10 | #include 11 | #include /* for getpid() and the exec..() family */ 12 | #include /* for _getcwd() and _chdir() */ 13 | 14 | #define srandom srand 15 | #define random rand 16 | 17 | /* Values for the second argument to access. 18 | These may be OR'd together. */ 19 | #define R_OK 4 /* Test for read permission. */ 20 | #define W_OK 2 /* Test for write permission. */ 21 | //#define X_OK 1 /* execute permission - unsupported in windows*/ 22 | #define F_OK 0 /* Test for existence. */ 23 | 24 | #define access _access 25 | #define dup2 _dup2 26 | #define execve _execve 27 | #define ftruncate _chsize 28 | #define unlink _unlink 29 | #define fileno _fileno 30 | #define getcwd _getcwd 31 | #define chdir _chdir 32 | #define isatty _isatty 33 | #define lseek _lseek 34 | /* read, write, and close are NOT being #defined here, because while there are file handle specific versions for Windows, they probably don't work for sockets. You need to look at your app and consider whether to call e.g. closesocket(). */ 35 | 36 | #define ssize_t int 37 | 38 | #define STDIN_FILENO 0 39 | #define STDOUT_FILENO 1 40 | #define STDERR_FILENO 2 41 | 42 | #endif /* unistd.h */ 43 | -------------------------------------------------------------------------------- /lib/md5/md5.h: -------------------------------------------------------------------------------- 1 | /* MD5.H - header file for MD5C.C 2 | */ 3 | 4 | /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 5 | rights reserved. 6 | 7 | License to copy and use this software is granted provided that it 8 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 9 | Algorithm" in all material mentioning or referencing this software 10 | or this function. 11 | 12 | License is also granted to make and use derivative works provided 13 | that such works are identified as "derived from the RSA Data 14 | Security, Inc. MD5 Message-Digest Algorithm" in all material 15 | mentioning or referencing the derived work. 16 | 17 | RSA Data Security, Inc. makes no representations concerning either 18 | the merchantability of this software or the suitability of this 19 | software for any particular purpose. It is provided "as is" 20 | without express or implied warranty of any kind. 21 | 22 | These notices must be retained in any copies of any part of this 23 | documentation and/or software. 24 | */ 25 | 26 | /* MD5 context. */ 27 | typedef struct { 28 | UINT4 state[4]; /* state (ABCD) */ 29 | UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ 30 | unsigned char buffer[64]; /* input buffer */ 31 | } MD5_CTX; 32 | 33 | void MD5Init PROTO_LIST ((MD5_CTX *)); 34 | void MD5Update PROTO_LIST 35 | ((MD5_CTX *, unsigned char *, unsigned int)); 36 | void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); 37 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(USE_OUR_OWN_MD5) 2 | add_subdirectory(md5) 3 | endif() 4 | 5 | set(LIBUNSHIELD_HEADERS 6 | internal.h 7 | libunshield.h 8 | log.h 9 | cabfile.h 10 | converter.h 11 | ) 12 | 13 | set(LIBUNSHIELD_SOURCES 14 | component.c 15 | converter.c 16 | directory.c 17 | file.c 18 | file_group.c 19 | helper.c 20 | libunshield.c 21 | log.c 22 | ) 23 | 24 | if(BUILD_STATIC) 25 | add_library(libunshield STATIC ${LIBUNSHIELD_HEADERS} ${LIBUNSHIELD_SOURCES}) 26 | else() 27 | add_library(libunshield SHARED ${LIBUNSHIELD_HEADERS} ${LIBUNSHIELD_SOURCES}) 28 | target_compile_definitions(libunshield PUBLIC UNSHIELD_DYNAMIC_LIBRARY) 29 | endif() 30 | 31 | target_include_directories(libunshield PUBLIC 32 | $ 33 | $) 34 | target_link_libraries(libunshield PUBLIC ZLIB::ZLIB) 35 | target_compile_definitions(libunshield PRIVATE UNSHIELD_EXPORT) 36 | set_target_properties(libunshield PROPERTIES OUTPUT_NAME unshield) 37 | set_target_properties(libunshield PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) 38 | 39 | if(USE_OUR_OWN_MD5) 40 | target_link_libraries(libunshield PRIVATE $) 41 | else() 42 | target_link_libraries(libunshield PRIVATE OpenSSL::Crypto) 43 | endif() 44 | 45 | install(TARGETS libunshield EXPORT unshieldConfig RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 46 | install(FILES libunshield.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 47 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: '0 1 * * 3' 16 | 17 | jobs: 18 | analyze: 19 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | # required for all workflows 24 | security-events: write 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v3 33 | with: 34 | languages: cpp 35 | 36 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 37 | # If this step fails, then you should remove it and run the build manually (see below). 38 | - name: Autobuild 39 | uses: github/codeql-action/autobuild@v3 40 | 41 | # ℹ️ Command-line programs to run using the OS shell. 42 | # 📚 https://git.io/JvXDl 43 | 44 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 45 | # three lines and modify them (or add more) to build your code if your 46 | # project uses a compiled language 47 | 48 | #- run: | 49 | # make bootstrap 50 | # make release 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@v3 54 | -------------------------------------------------------------------------------- /lib/component.c: -------------------------------------------------------------------------------- 1 | #include "internal.h" 2 | #include "log.h" 3 | #include 4 | #include 5 | 6 | int unshield_component_count(Unshield* unshield) 7 | { 8 | Header* header = unshield->header_list; 9 | return header->component_count; 10 | } 11 | 12 | const char* unshield_component_name(Unshield* unshield, int index) 13 | { 14 | Header* header = unshield->header_list; 15 | 16 | if (index >= 0 && index < header->component_count) 17 | return header->components[index]->name; 18 | else 19 | return NULL; 20 | } 21 | 22 | UnshieldComponent* unshield_component_new(Header* header, uint32_t offset) 23 | { 24 | UnshieldComponent* self = NEW1(UnshieldComponent); 25 | uint8_t* p = unshield_header_get_buffer(header, offset); 26 | uint32_t file_group_table_offset; 27 | unsigned i; 28 | 29 | self->name = unshield_header_get_string(header, READ_UINT32(p)); p += 4; 30 | 31 | switch (header->major_version) 32 | { 33 | case 0: 34 | case 5: 35 | p += 0x6c; 36 | break; 37 | 38 | case 6: 39 | case 7: 40 | case 8: 41 | case 9: 42 | case 10: 43 | case 11: 44 | case 12: 45 | case 13: 46 | default: 47 | p += 0x6b; 48 | break; 49 | } 50 | 51 | self->file_group_count = READ_UINT16(p); p += 2; 52 | if (self->file_group_count > MAX_FILE_GROUP_COUNT) 53 | abort(); 54 | 55 | self->file_group_names = NEW(const char*, self->file_group_count); 56 | 57 | file_group_table_offset = READ_UINT32(p); p += 4; 58 | 59 | p = unshield_header_get_buffer(header, file_group_table_offset); 60 | 61 | for (i = 0; i < self->file_group_count; i++) 62 | { 63 | self->file_group_names[i] = unshield_header_get_string(header, READ_UINT32(p)); 64 | p += 4; 65 | } 66 | 67 | return self; 68 | } 69 | 70 | void unshield_component_destroy(UnshieldComponent* self) 71 | { 72 | if (self) 73 | { 74 | FREE(self->file_group_names); 75 | free(self); 76 | } 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /lib/converter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | typedef uint8_t utf8_t; // The type of a single UTF-8 character 7 | typedef uint16_t utf16_t; // The type of a single UTF-16 character 8 | 9 | /* 10 | * Converts a UTF-16 string to a UTF-8 string. 11 | * 12 | * utf16: 13 | * The UTF-16 string, not null-terminated. 14 | * 15 | * utf16_len: 16 | * The length of the UTF-16 string, in 16-bit characters. 17 | * 18 | * utf8: 19 | * The buffer where the resulting UTF-8 string will be stored. 20 | * If set to NULL, indicates that the function should just calculate 21 | * the required buffer size and not actually perform any conversions. 22 | * 23 | * utf8_len: 24 | * The length of the UTF-8 buffer, in 8-bit characters. 25 | * Ignored if utf8 is NULL. 26 | * 27 | * return: 28 | * If utf8 is NULL, the size of the required UTF-8 buffer. 29 | * Otherwise, the number of characters written to the utf8 buffer. 30 | * 31 | */ 32 | size_t utf16_to_utf8( 33 | utf16_t const* utf16, size_t utf16_len, 34 | utf8_t* utf8, size_t utf8_len 35 | ); 36 | 37 | /* 38 | * Converts a UTF-8 string to a UTF-16 string. 39 | * 40 | * utf8: 41 | * The UTF-8 string, not null-terminated. 42 | * 43 | * utf8_len: 44 | * The length of the UTF-8 string, in 8-bit characters. 45 | * 46 | * utf16: 47 | * The buffer where the resulting UTF-16 string will be stored. 48 | * If set to NULL, indicates that the function should just calculate 49 | * the required buffer size and not actually perform any conversions. 50 | * 51 | * utf16_len: 52 | * The length of the UTF-16 buffer, in 16-bit characters. 53 | * Ignored if utf16 is NULL. 54 | * 55 | * return: 56 | * If utf16 is NULL, the size of the required UTF-16 buffer, 57 | * in 16-bit characters. 58 | * Otherwise, the number of characters written to the utf8 buffer, in 59 | * 16-bit characters. 60 | * 61 | */ 62 | size_t utf8_to_utf16( 63 | utf8_t const* utf8, size_t utf8_len, 64 | utf16_t* utf16, size_t utf16_len 65 | ); 66 | -------------------------------------------------------------------------------- /lib/file_group.c: -------------------------------------------------------------------------------- 1 | #include "internal.h" 2 | #include "log.h" 3 | #include 4 | #include 5 | 6 | #define VERBOSE 1 7 | 8 | UnshieldFileGroup* unshield_file_group_new(Header* header, uint32_t offset)/*{{{*/ 9 | { 10 | UnshieldFileGroup* self = NEW1(UnshieldFileGroup); 11 | uint8_t* p = unshield_header_get_buffer(header, offset); 12 | 13 | #if VERBOSE 14 | unshield_trace("File group descriptor offset: %08x", offset); 15 | #endif 16 | 17 | self->name = unshield_header_get_string(header, READ_UINT32(p)); p += 4; 18 | 19 | if (header->major_version <= 5) 20 | p += 0x48; 21 | else 22 | p += 0x12; 23 | 24 | self->first_file = READ_INT32(p); p += 4; 25 | self->last_file = READ_INT32(p); p += 4; 26 | 27 | #if VERBOSE 28 | unshield_trace("File group %08x first file = %i, last file = %i", 29 | offset, self->first_file, self->last_file); 30 | #endif 31 | 32 | return self; 33 | }/*}}}*/ 34 | 35 | void unshield_file_group_destroy(UnshieldFileGroup* self)/*{{{*/ 36 | { 37 | FREE(self); 38 | }/*}}}*/ 39 | 40 | int unshield_file_group_count(Unshield* unshield)/*{{{*/ 41 | { 42 | Header* header = unshield->header_list; 43 | return header->file_group_count; 44 | }/*}}}*/ 45 | 46 | UnshieldFileGroup* unshield_file_group_get(Unshield* unshield, int index) 47 | { 48 | Header* header = unshield->header_list; 49 | 50 | if (index >= 0 && index < header->file_group_count) 51 | return header->file_groups[index]; 52 | else 53 | return NULL; 54 | } 55 | 56 | UnshieldFileGroup* unshield_file_group_find(Unshield* unshield, const char* name) 57 | { 58 | Header* header = unshield->header_list; 59 | int i; 60 | 61 | for (i = 0; i < header->file_group_count; i++) 62 | { 63 | if (STREQ(header->file_groups[i]->name, name)) 64 | return header->file_groups[i]; 65 | } 66 | 67 | return NULL; 68 | } 69 | 70 | const char* unshield_file_group_name(Unshield* unshield, int index)/*{{{*/ 71 | { 72 | Header* header = unshield->header_list; 73 | 74 | if (index >= 0 && index < header->file_group_count) 75 | return header->file_groups[index]->name; 76 | else 77 | return NULL; 78 | }/*}}}*/ 79 | 80 | -------------------------------------------------------------------------------- /man/unshield.1: -------------------------------------------------------------------------------- 1 | .TH UNSHIELD "1" "November 2023" "The Unshield project" "https://github.com/twogood/unshield" 2 | .SH NAME 3 | unshield \- extract CAB files from an InstallShield installer archive 4 | .SH SYNOPSIS 5 | unshield [\-c COMPONENT] [\-d DIRECTORY] [\-D LEVEL] [\-g GROUP] [\-h] [\-i VERSION] [\-e ENCODING] [\-j] [\-L] [\-O] [\-r] [\-R] [\-V] c|g|l|t|x CABFILE [FILENAME...] 6 | .SH DESCRIPTION 7 | Unshield extracts CAB files from InstallShield installers, used to 8 | install software on Microsoft Windows based machines. 9 | .SH OPTIONS 10 | .TP 11 | \fB\-c\fR COMPONENT 12 | Only list/extract this component 13 | .TP 14 | \fB\-d\fR DIRECTORY 15 | Extract files to DIRECTORY 16 | .TP 17 | \fB\-D\fR LEVEL 18 | Set debug log level 19 | 0 \- No logging (default) 20 | 1 \- Errors only 21 | 2 \- Errors and warnings 22 | 3 \- Errors, warnings and debug messages 23 | .TP 24 | \fB\-g\fR GROUP 25 | Only list/extract this file group 26 | .TP 27 | \fB\-h\fR 28 | Show help message 29 | .TP 30 | \fB\-i\fR VERSION 31 | Force InstallShield version number (don't autodetect) 32 | .br 33 | Use this option if you have a very old archive (generated with InstallShield <= 4) and / or the automatic detection fails 34 | .TP 35 | \fB\-e\fR ENCODING 36 | Convert filename character encoding to local codepage from ENCODING (implicitly sets -R) 37 | .TP 38 | \fB\-j\fR 39 | Junk paths (do not make directories) 40 | .TP 41 | \fB\-L\fR 42 | Make file and directory names lowercase 43 | .TP 44 | \fB\-O\fR 45 | Use old compression 46 | .TP 47 | \fB\-r\fR 48 | Save raw data (do not decompress) 49 | .TP 50 | \fB\-R\fR 51 | Don't do any conversion to file and directory names when extracting 52 | .TP 53 | \fB\-V\fR, \fB\-\-version\fR 54 | Print version information 55 | .SS "Commands:" 56 | .TP 57 | c 58 | List components 59 | .TP 60 | g 61 | List file groups 62 | .TP 63 | l 64 | List files 65 | .TP 66 | t 67 | Test files 68 | .TP 69 | x 70 | Extract files 71 | .SS Other: 72 | .TP 73 | CABFILE 74 | The file to list or extract contents of 75 | .TP 76 | FILENAME 77 | Optionally specify names of specific files to extract (wildcards are supported) 78 | .SH AUTHOR 79 | This manual page was adapted by Mark Ellis , from 80 | the skeleton generated by help2man 81 | -------------------------------------------------------------------------------- /lib/unshield_config.h.in: -------------------------------------------------------------------------------- 1 | /* Define to 1 if you have the header file. */ 2 | #cmakedefine HAVE_DLFCN_H 1 3 | 4 | /* Define to 1 if you have the header file. */ 5 | #cmakedefine HAVE_INTTYPES_H 1 6 | 7 | /* Define to 1 if you have the header file. */ 8 | #cmakedefine HAVE_MEMORY_H 1 9 | 10 | /* Define to 1 if you have the header file. */ 11 | #cmakedefine HAVE_STDBOOL_H 1 12 | 13 | /* Define to 1 if you have the header file. */ 14 | #cmakedefine HAVE_STDINT_H 1 15 | 16 | /* Define to 1 if you have the header file. */ 17 | #cmakedefine HAVE_STDLIB_H 1 18 | 19 | /* Define to 1 if you have the header file. */ 20 | #cmakedefine HAVE_STRINGS_H 1 21 | 22 | /* Define to 1 if you have the header file. */ 23 | #cmakedefine HAVE_STRING_H 1 24 | 25 | /* Define to 1 if you have the header file. */ 26 | #cmakedefine HAVE_SYS_STAT_H 1 27 | 28 | /* Define to 1 if you have the header file. */ 29 | #cmakedefine HAVE_SYS_TYPES_H 1 30 | 31 | /* Define to 1 if you have the header file. */ 32 | #cmakedefine HAVE_UNISTD_H 1 33 | 34 | /* Name of package */ 35 | #define PACKAGE "@PROJECT_NAME@" 36 | 37 | /* Define to the address where bug reports for this package should be sent. */ 38 | #define PACKAGE_BUGREPORT "" 39 | 40 | /* Define to the full name of this package. */ 41 | #define PACKAGE_NAME "@PROJECT_NAME@" 42 | 43 | /* Define to the full name and version of this package. */ 44 | #define PACKAGE_STRING "@PROJECT_NAME@ @PROJECT_VERSION@" 45 | 46 | /* Define to the one symbol short name of this package. */ 47 | #define PACKAGE_TARNAME "@PROJECT_NAME@" 48 | 49 | /* Define to the home page for this package. */ 50 | #define PACKAGE_URL "" 51 | 52 | /* Define to the version of this package. */ 53 | #define PACKAGE_VERSION "@PROJECT_VERSION@" 54 | 55 | /* printf format that works with size_t values */ 56 | #cmakedefine SIZE_FORMAT "@SIZE_FORMAT@" 57 | 58 | /* Define to 1 if your system has a working POSIX `fnmatch' function. */ 59 | #cmakedefine HAVE_FNMATCH 1 60 | 61 | /* Define to 1 if your system has a working POSIX `iconv' function. */ 62 | #cmakedefine HAVE_ICONV 1 63 | 64 | /* Defined if we should use our own MD5 routines. */ 65 | #cmakedefine01 USE_OUR_OWN_MD5 66 | 67 | /* Version number of package */ 68 | #define VERSION "@PROJECT_VERSION@" 69 | 70 | /* Enable GNU style printf formatters */ 71 | #define __USE_MINGW_ANSI_STDIO 1 72 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{matrix.platform.os}} 12 | 13 | defaults: 14 | run: 15 | shell: ${{ matrix.platform.shell }} 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | platform: 21 | - { name: macOS, os: macos-latest, shell: sh } 22 | - { name: Ubuntu, os: ubuntu-latest, shell: sh } 23 | - { name: Windows - mingw32, os: windows-latest, shell: 'msys2 {0}', msystem: mingw32, msys-env: mingw-w64-i686 } 24 | - { name: Windows - mingw64, os: windows-latest, shell: 'msys2 {0}', msystem: mingw64, msys-env: mingw-w64-x86_64 } 25 | build-type: [Release] 26 | build-static: [ON] 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - name: Setup GNU/Linux dependencies 31 | if: runner.os == 'Linux' 32 | run: | 33 | sudo apt-get update 34 | sudo apt-get install \ 35 | libssl-dev ninja-build zlib1g-dev 36 | 37 | - name: Setup macOS dependencies 38 | if: runner.os == 'macOS' 39 | run: | 40 | brew install \ 41 | ninja zlib 42 | echo "CMAKE_PREFIX_PATH=$(brew --prefix openssl):$(brew --prefix zlib)" >> $GITHUB_ENV 43 | 44 | 45 | - name: Setup Windows dependencies 46 | if: runner.os == 'Windows' 47 | uses: msys2/setup-msys2@v2 48 | with: 49 | msystem: ${{matrix.platform.msystem}} 50 | install: >- 51 | ${{matrix.platform.msys-env}}-gcc 52 | ${{matrix.platform.msys-env}}-cmake 53 | ${{matrix.platform.msys-env}}-ninja 54 | ${{matrix.platform.msys-env}}-openssl 55 | ${{matrix.platform.msys-env}}-zlib 56 | 57 | - name: Configure CMake 58 | run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DBUILD_STATIC=${{matrix.build-static}} -DBUILD_TESTING=ON 59 | 60 | - name: Build 61 | run: cmake --build build 62 | 63 | - name: Test 64 | run: ctest --test-dir build --output-on-failure 65 | 66 | - name: Install 67 | run: cmake --install build --strip --prefix install 68 | 69 | - name: Upload 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: unshield-${{github.sha}} (${{matrix.platform.name}}) 73 | path: install 74 | if-no-files-found: error 75 | -------------------------------------------------------------------------------- /lib/cabfile.h: -------------------------------------------------------------------------------- 1 | #ifndef __cabfile_h__ 2 | #define __cabfile_h__ 3 | 4 | #define OFFSET_COUNT 0x47 5 | #define CAB_SIGNATURE 0x28635349 6 | 7 | #define MSCF_SIGNATURE 0x4643534d 8 | 9 | #define COMMON_HEADER_SIZE 20 10 | #define VOLUME_HEADER_SIZE_V5 40 11 | #define VOLUME_HEADER_SIZE_V6 64 12 | 13 | #define MAX_FILE_GROUP_COUNT 71 14 | #define MAX_COMPONENT_COUNT 71 15 | 16 | typedef struct 17 | { 18 | uint32_t signature; /* 00 */ 19 | uint32_t version; 20 | uint32_t volume_info; 21 | uint32_t cab_descriptor_offset; 22 | uint32_t cab_descriptor_size; /* 10 */ 23 | } CommonHeader; 24 | 25 | 26 | typedef struct 27 | { 28 | uint32_t data_offset; 29 | uint32_t data_offset_high; 30 | uint32_t first_file_index; 31 | uint32_t last_file_index; 32 | uint32_t first_file_offset; 33 | uint32_t first_file_offset_high; 34 | uint32_t first_file_size_expanded; 35 | uint32_t first_file_size_expanded_high; 36 | uint32_t first_file_size_compressed; 37 | uint32_t first_file_size_compressed_high; 38 | uint32_t last_file_offset; 39 | uint32_t last_file_offset_high; 40 | uint32_t last_file_size_expanded; 41 | uint32_t last_file_size_expanded_high; 42 | uint32_t last_file_size_compressed; 43 | uint32_t last_file_size_compressed_high; 44 | } VolumeHeader; 45 | 46 | 47 | typedef struct 48 | { 49 | uint32_t file_table_offset; /* c */ 50 | uint32_t file_table_size; /* 14 */ 51 | uint32_t file_table_size2; /* 18 */ 52 | uint32_t directory_count; /* 1c */ 53 | uint32_t file_count; /* 28 */ 54 | uint32_t file_table_offset2; /* 2c */ 55 | 56 | uint32_t file_group_offsets[MAX_FILE_GROUP_COUNT]; /* 0x3e */ 57 | uint32_t component_offsets [MAX_COMPONENT_COUNT]; /* 0x15a */ 58 | } CabDescriptor; 59 | 60 | #define FILE_SPLIT 1U 61 | #define FILE_OBFUSCATED 2U 62 | #define FILE_COMPRESSED 4U 63 | #define FILE_INVALID 8U 64 | 65 | #define LINK_NONE 0 66 | #define LINK_PREV 1 67 | #define LINK_NEXT 2 68 | #define LINK_BOTH 3 69 | 70 | typedef struct 71 | { 72 | uint32_t name_offset; 73 | uint32_t directory_index; 74 | uint16_t flags; 75 | uint64_t expanded_size; 76 | uint64_t compressed_size; 77 | uint64_t data_offset; 78 | uint8_t md5[16]; 79 | uint16_t volume; 80 | uint32_t link_previous; 81 | uint32_t link_next; 82 | uint8_t link_flags; 83 | } FileDescriptor; 84 | 85 | typedef struct 86 | { 87 | uint32_t name_offset; 88 | uint32_t descriptor_offset; 89 | uint32_t next_offset; 90 | } OffsetList; 91 | 92 | #endif 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Unshield 2 | ======== 3 | 4 | [![Packaging status](https://repology.org/badge/tiny-repos/unshield.svg)](https://repology.org/project/unshield/versions) 5 | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/unshield.svg)](https://repology.org/project/unshield/versions) 6 | 7 | 8 | Support Unshield development 9 | ---------------------------- 10 | 11 | - [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SQ7PEFMJK36AU) 12 | 13 | 14 | Dictionary 15 | ---------- 16 | 17 | InstallShield (IS): see www.installshield.com 18 | 19 | InstallShield Cabinet File (ISCF): A .cab file used by IS. 20 | 21 | Microsoft Cabinet File (MSCF): A .cab file used by Microsoft. 22 | 23 | 24 | About Unshield 25 | -------------- 26 | 27 | To install a Pocket PC application remotely, an installable 28 | Microsoft Cabinet File is copied to the /Windows/AppMgr/Install 29 | directory on the PDA and then the wceload.exe is executed to 30 | perform the actual install. That is a very simple procedure. 31 | 32 | Unfortunately, many applications for Pocket PC are distributed as 33 | InstallShield installers for Microsoft Windows, and not as 34 | individual Microsoft Cabinet Files. That is very impractical for 35 | users of other operating systems, such as Linux or FreeBSD. 36 | 37 | An installer created by the InstallShield software stores the 38 | files it will install inside of InstallShield Cabinet Files. It 39 | would thus be desirable to be able to extract the Microsoft 40 | Cabinet Files from the InstallShield Cabinet Files in order to be 41 | able to install the applications without access to Microsoft 42 | Windows. 43 | 44 | The format of InstallShield Cabinet Files is not officially 45 | documented but there are two tools available for Microsoft 46 | Windows that extracts files from InstallShield installers, and 47 | they are distributed with source code included. These tools are 48 | named "i5comp" and "i6comp" and can be downloaded from the 49 | Internet. 50 | 51 | One major drawback with these tools are that for the actual 52 | decompression of the files stored in the InstallShield Cabinet 53 | Files they require the use of code written by InstallShield that 54 | is not available as source code. Luckily, by examining this code 55 | with the 'strings' tool, I discovered that they were using the 56 | open source zlib library (www.gzip.org/zlib) for decompression. 57 | 58 | I could have modified i5comp and i6comp to run on other operating 59 | systems than Microsoft Windows, but I preferred to use them as a 60 | reference for this implementation. The goals of this 61 | implementation are: 62 | 63 | - Use a well known open source license (MIT) 64 | 65 | - Work on both little-endian and big-endian systems 66 | 67 | - Separate the implementation in a tool and a library 68 | 69 | - Support InstallShield versions 5 and later 70 | 71 | - Be able to list contents of InstallShield Cabinet Files 72 | 73 | - Be able to extract files from InstallShield Cabinet Files 74 | 75 | 76 | License 77 | ------- 78 | 79 | Unshield uses the MIT license. The short version is "do as you 80 | like, but don't blame me if anything goes wrong". 81 | 82 | See the file LICENSE for details. 83 | 84 | 85 | Build From Source 86 | ----------------- 87 | 88 | Just use the standard CMake build process: 89 | 90 | ``` sh 91 | cmake . 92 | make 93 | make install 94 | ``` 95 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(unshield C) 3 | 4 | # Mimic CMP0048 which is avaliable only for cmake 3.0 and later 5 | set(PROJECT_VERSION_MAJOR 1) 6 | set(PROJECT_VERSION_MINOR 6) 7 | set(PROJECT_VERSION_PATCH 2) 8 | set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") 9 | 10 | option(BUILD_STATIC "Build static version of libunshield" OFF) 11 | 12 | include(CheckIncludeFiles) 13 | include(CheckSymbolExists) 14 | include(CheckCSourceCompiles) 15 | if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test") 16 | include(CTest) 17 | endif() 18 | include(GNUInstallDirs) 19 | 20 | check_include_files(dlfcn.h HAVE_DLFCN_H) 21 | check_include_files(inttypes.h HAVE_INTTYPES_H) 22 | check_include_files(memory.h HAVE_MEMORY_H) 23 | check_include_files(stdbool.h HAVE_STDBOOL_H) 24 | check_include_files(stdint.h HAVE_STDINT_H) 25 | check_include_files(stdlib.h HAVE_STDLIB_H) 26 | check_include_files(strings.h HAVE_STRINGS_H) 27 | check_include_files(string.h HAVE_STRING_H) 28 | check_include_files(sys/stat.h HAVE_SYS_STAT_H) 29 | check_include_files(sys/types.h HAVE_SYS_TYPES_H) 30 | check_include_files(unistd.h HAVE_UNISTD_H) 31 | check_symbol_exists(fnmatch fnmatch.h HAVE_FNMATCH) 32 | check_symbol_exists(iconv iconv.h HAVE_ICONV) 33 | 34 | set(SIZE_FORMAT "zi") 35 | check_c_source_compiles("#include \nint main(int argc, char **argv) { size_t value = 0; printf(\"%${SIZE_FORMAT}\", value); return 0; }" SIZE_FORMAT_ZI) 36 | if(NOT SIZE_FORMAT_ZI) 37 | set(SIZE_FORMAT "i") 38 | check_c_source_compiles("#include \nint main(int argc, char **argv) { size_t value = 0; printf(\"%${SIZE_FORMAT}\", value); return 0; }" SIZE_FORMAT_I) 39 | if(NOT SIZE_FORMAT_I) 40 | set(SIZE_FORMAT "li") 41 | check_c_source_compiles("#include \nint main(int argc, char **argv) { size_t value = 0; printf(\"%${SIZE_FORMAT}\", value); return 0; }" SIZE_FORMAT_LI) 42 | if(NOT SIZE_FORMAT_LI) 43 | message(FATAL_ERROR "You must be using a really weird platform!") 44 | endif() 45 | endif() 46 | endif() 47 | 48 | find_package(ZLIB REQUIRED) 49 | find_package(OpenSSL) 50 | 51 | if(${OPENSSL_FOUND}) 52 | option(USE_OUR_OWN_MD5 "Build using own md5 implementation" OFF) 53 | else() 54 | option(USE_OUR_OWN_MD5 "Build using own md5 implementation" ON) 55 | endif() 56 | 57 | message(STATUS "OPENSSL_FOUND: ${OPENSSL_FOUND}") 58 | message(STATUS "USE_OUR_OWN_MD5: ${USE_OUR_OWN_MD5}") 59 | message(STATUS "BUILD_STATIC: ${BUILD_STATIC}") 60 | if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test") 61 | message(STATUS "BUILD_TESTING: ${BUILD_TESTING}") 62 | else() 63 | set(BUILD_TESTING OFF) 64 | endif() 65 | 66 | add_definitions(-DHAVE_CONFIG_H) 67 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/lib/unshield_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/lib/unshield_config.h) 68 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libunshield.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libunshield.pc @ONLY) 69 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 70 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/lib) 71 | 72 | # Function to change MSVC runtime linkage to static 73 | function(set_msvc_runtime_static) 74 | foreach(flag_var 75 | CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE 76 | CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) 77 | if(${flag_var} MATCHES "/MD") 78 | string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") 79 | endif() 80 | endforeach() 81 | endfunction() 82 | 83 | if (MSVC) 84 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 85 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/win32_msvc) 86 | endif () 87 | 88 | add_subdirectory(lib) 89 | add_subdirectory(src) 90 | 91 | install(FILES man/unshield.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) 92 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libunshield.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 93 | install(EXPORT unshieldConfig NAMESPACE unshield:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/unshield) 94 | 95 | if(BUILD_TESTING) 96 | add_subdirectory(test) 97 | endif() 98 | -------------------------------------------------------------------------------- /lib/libunshield.h: -------------------------------------------------------------------------------- 1 | #ifndef __unshield_h__ 2 | #define __unshield_h__ 3 | 4 | #include 5 | #include 6 | 7 | #define UNSHIELD_LOG_LEVEL_LOWEST 0 8 | 9 | #define UNSHIELD_LOG_LEVEL_ERROR 1 10 | #define UNSHIELD_LOG_LEVEL_WARNING 2 11 | #define UNSHIELD_LOG_LEVEL_TRACE 3 12 | 13 | #define UNSHIELD_LOG_LEVEL_HIGHEST 4 14 | 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #if defined(_WIN32) && defined(UNSHIELD_DYNAMIC_LIBRARY) 21 | # if defined(UNSHIELD_EXPORTS) 22 | # define UNSHIELD_API __declspec(dllexport) 23 | # else 24 | # define UNSHIELD_API __declspec(dllimport) 25 | # endif 26 | #else 27 | # define UNSHIELD_API 28 | #endif 29 | 30 | typedef struct _Unshield Unshield; 31 | 32 | 33 | /* 34 | Logging 35 | */ 36 | 37 | UNSHIELD_API void unshield_set_log_level(int level); 38 | 39 | 40 | /* 41 | Open/close functions 42 | */ 43 | 44 | UNSHIELD_API Unshield* unshield_open(const char* filename); 45 | UNSHIELD_API Unshield* unshield_open_force_version(const char* filename, int version); 46 | UNSHIELD_API void unshield_close(Unshield* unshield); 47 | 48 | typedef struct 49 | { 50 | void *(*fopen)(const char *filename, const char *modes, void *userdata); 51 | int (*fseek)(void *file, long int offset, int whence, void *userdata); 52 | long int (*ftell)(void *file, void *userdata); 53 | size_t (*fread)(void *ptr, size_t size, size_t n, void *file, void *userdata); 54 | size_t (*fwrite)(const void *ptr, size_t size, size_t n, void *file, void *userdata); 55 | int (*fclose)(void *file, void *userdata); 56 | void *(*opendir)(const char *name, void *userdata); 57 | int (*closedir)(void *dir, void *userdata); 58 | struct dirent* (*readdir)(void *dir, void *userdata); 59 | } UnshieldIoCallbacks; 60 | 61 | UNSHIELD_API Unshield* unshield_open2(const char* filename, const UnshieldIoCallbacks* callbacks, void* userdata); 62 | UNSHIELD_API Unshield* unshield_open2_force_version(const char* filename, int version, const UnshieldIoCallbacks* callbacks, void* userdata); 63 | 64 | /* 65 | Component functions 66 | */ 67 | 68 | typedef struct 69 | { 70 | const char* name; 71 | unsigned file_group_count; 72 | const char** file_group_names; 73 | } UnshieldComponent; 74 | 75 | UNSHIELD_API int unshield_component_count (Unshield* unshield); 76 | UNSHIELD_API const char* unshield_component_name (Unshield* unshield, int index); 77 | 78 | /* 79 | File group functions 80 | */ 81 | 82 | typedef struct 83 | { 84 | const char* name; 85 | int first_file; 86 | int last_file; 87 | } UnshieldFileGroup; 88 | 89 | UNSHIELD_API int unshield_file_group_count (Unshield* unshield); 90 | UNSHIELD_API UnshieldFileGroup* unshield_file_group_get (Unshield* unshield, int index); 91 | UNSHIELD_API UnshieldFileGroup* unshield_file_group_find (Unshield* unshield, const char* name); 92 | UNSHIELD_API const char* unshield_file_group_name (Unshield* unshield, int index); 93 | 94 | /* 95 | Directory functions 96 | */ 97 | 98 | UNSHIELD_API int unshield_directory_count (Unshield* unshield); 99 | UNSHIELD_API const char* unshield_directory_name (Unshield* unshield, int index); 100 | 101 | /* 102 | File functions 103 | */ 104 | 105 | UNSHIELD_API int unshield_file_count (Unshield* unshield); 106 | UNSHIELD_API const char* unshield_file_name (Unshield* unshield, int index); 107 | UNSHIELD_API bool unshield_file_is_valid (Unshield* unshield, int index); 108 | UNSHIELD_API bool unshield_file_save (Unshield* unshield, int index, const char* filename); 109 | UNSHIELD_API int unshield_file_directory (Unshield* unshield, int index); 110 | UNSHIELD_API size_t unshield_file_size (Unshield* unshield, int index); 111 | 112 | /** For investigation of compressed data */ 113 | UNSHIELD_API bool unshield_file_save_raw(Unshield* unshield, int index, const char* filename); 114 | 115 | /** Maybe it's just gzip without size? */ 116 | UNSHIELD_API bool unshield_file_save_old(Unshield* unshield, int index, const char* filename); 117 | 118 | /** Deobfuscate a buffer. Seed is 0 at file start */ 119 | UNSHIELD_API void unshield_deobfuscate(unsigned char* buffer, size_t size, unsigned* seed); 120 | 121 | /** Is the archive Unicode-capable? */ 122 | UNSHIELD_API bool unshield_is_unicode(Unshield* unshield); 123 | 124 | #ifdef __cplusplus 125 | } 126 | #endif 127 | 128 | 129 | #endif 130 | 131 | -------------------------------------------------------------------------------- /lib/internal.h: -------------------------------------------------------------------------------- 1 | #ifndef __internal_h__ 2 | #define __internal_h__ 3 | 4 | #include "libunshield.h" 5 | #include "lib/unshield_config.h" 6 | 7 | #if HAVE_STDINT_H 8 | #include 9 | #elif HAVE_INTTYPES_H 10 | #include 11 | #endif 12 | 13 | #include 14 | #include /* for FILE */ 15 | 16 | #include "cabfile.h" 17 | 18 | typedef struct _StringBuffer StringBuffer; 19 | 20 | struct _StringBuffer 21 | { 22 | StringBuffer* next; 23 | char* string; 24 | }; 25 | 26 | typedef struct _Header Header; 27 | 28 | struct _Header 29 | { 30 | Header* next; 31 | int index; 32 | uint8_t* data; 33 | size_t size; 34 | int major_version; 35 | 36 | /* shortcuts */ 37 | CommonHeader common; 38 | CabDescriptor cab; 39 | uint32_t* file_table; 40 | FileDescriptor** file_descriptors; 41 | 42 | int component_count; 43 | UnshieldComponent** components; 44 | 45 | int file_group_count; 46 | UnshieldFileGroup** file_groups; 47 | 48 | StringBuffer* string_buffer; 49 | }; 50 | 51 | struct _Unshield 52 | { 53 | Header* header_list; 54 | char* filename_pattern; 55 | const UnshieldIoCallbacks* io_callbacks; 56 | void* io_userdata; 57 | }; 58 | 59 | /* 60 | Internal component functions 61 | */ 62 | 63 | UnshieldComponent* unshield_component_new(Header* header, uint32_t offset); 64 | void unshield_component_destroy(UnshieldComponent* self); 65 | 66 | 67 | /* 68 | Internal file group functions 69 | */ 70 | 71 | UnshieldFileGroup* unshield_file_group_new(Header* header, uint32_t offset); 72 | void unshield_file_group_destroy(UnshieldFileGroup* self); 73 | 74 | 75 | /* 76 | Helpers 77 | */ 78 | 79 | char *unshield_get_base_directory_name(Unshield *unshield); 80 | long int unshield_get_path_max(Unshield* unshield); 81 | FILE* unshield_fopen_for_reading(Unshield* unshield, int index, const char* suffix); 82 | long long unshield_fsize(Unshield* unshield, FILE* file); 83 | bool unshield_read_common_header(uint8_t** buffer, CommonHeader* common); 84 | 85 | const char* unshield_get_utf8_string(Header* header, const void* buffer); 86 | const char* unshield_header_get_string(Header* header, uint32_t offset); 87 | uint8_t* unshield_header_get_buffer(Header* header, uint32_t offset); 88 | char *unshield_get_last_path_separator(char *path); 89 | 90 | static inline void* unshield_fopen(Unshield* unshield, const char *filename, const char *modes) 91 | { 92 | return unshield->io_callbacks->fopen(filename, modes, unshield->io_userdata); 93 | } 94 | 95 | static inline int unshield_fseek(Unshield* unshield, void *file, long int offset, int whence) 96 | { 97 | return unshield->io_callbacks->fseek(file, offset, whence, unshield->io_userdata); 98 | } 99 | 100 | static inline long int unshield_ftell(Unshield* unshield, void *file) 101 | { 102 | return unshield->io_callbacks->ftell(file, unshield->io_userdata); 103 | } 104 | 105 | static inline size_t unshield_fread(Unshield* unshield, void *ptr, size_t size, size_t n, void *file) 106 | { 107 | return unshield->io_callbacks->fread(ptr, size, n, file, unshield->io_userdata); 108 | } 109 | 110 | static inline size_t unshield_fwrite(Unshield* unshield, const void *ptr, size_t size, size_t n, void *file) 111 | { 112 | return unshield->io_callbacks->fwrite(ptr, size, n, file, unshield->io_userdata); 113 | } 114 | 115 | static inline int unshield_fclose(Unshield* unshield, void *ptr) 116 | { 117 | return unshield->io_callbacks->fclose(ptr, unshield->io_userdata); 118 | } 119 | 120 | static inline void * unshield_opendir(Unshield* unshield, const char *name) 121 | { 122 | return unshield->io_callbacks->opendir(name, unshield->io_userdata); 123 | } 124 | 125 | static inline int unshield_closedir(Unshield* unshield, void *dir) 126 | { 127 | return unshield->io_callbacks->closedir(dir, unshield->io_userdata); 128 | } 129 | 130 | static inline struct dirent* unshield_readdir(Unshield* unshield, void *dir) 131 | { 132 | return unshield->io_callbacks->readdir(dir, unshield->io_userdata); 133 | } 134 | 135 | /* 136 | Constants 137 | */ 138 | 139 | #define HEADER_SUFFIX "hdr" 140 | #define CABINET_SUFFIX "cab" 141 | 142 | /* 143 | Macros for safer development 144 | */ 145 | 146 | #define FREE(ptr) { if (ptr) { free(ptr); (ptr) = NULL; } } 147 | #define STRDUP(str) ((str) ? strdup(str) : NULL) 148 | #define NEW(type, count) ((type*)calloc(count, sizeof(type))) 149 | #define NEW1(type) ((type*)calloc(1, sizeof(type))) 150 | #define FCLOSE(unshield, file) if (file) { unshield_fclose(unshield, file); (file) = NULL; } 151 | #define FSIZE(unshield, file) ((file) ? unshield_fsize(unshield, file) : 0) 152 | #define STREQ(s1,s2) (0 == strcmp(s1,s2)) 153 | 154 | static inline uint16_t get_unaligned_le16(const uint8_t *p) 155 | { 156 | return p[0] | p[1] << 8; 157 | } 158 | 159 | static inline uint32_t get_unaligned_le32(const uint8_t *p) 160 | { 161 | return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24; 162 | } 163 | 164 | static inline uint64_t get_unaligned_le64(const uint8_t *p) 165 | { 166 | return (uint64_t)get_unaligned_le32(p + 4) << 32 | get_unaligned_le32(p); 167 | } 168 | 169 | #define READ_UINT16(p) get_unaligned_le16(p) 170 | #define READ_UINT32(p) get_unaligned_le32(p) 171 | #define READ_UINT64(p) get_unaligned_le64(p) 172 | 173 | #define READ_INT16(p) ((int16_t)READ_UINT16(p)) 174 | #define READ_INT32(p) ((int32_t)READ_UINT32(p)) 175 | 176 | 177 | #endif 178 | 179 | -------------------------------------------------------------------------------- /lib/helper.c: -------------------------------------------------------------------------------- 1 | #define _BSD_SOURCE 1 2 | #define _DEFAULT_SOURCE 1 3 | #include "internal.h" 4 | #include "converter.h" 5 | #include "log.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef _WIN32 14 | #define realpath(N,R) _fullpath((R),(N),_MAX_PATH) 15 | #include 16 | #ifndef PATH_MAX 17 | #define PATH_MAX _MAX_PATH 18 | #endif 19 | #else 20 | #include 21 | #endif 22 | 23 | #define VERBOSE 0 24 | 25 | #if defined(_MSC_VER) 26 | #define snprintf _snprintf 27 | #define vsnprintf _vsnprintf 28 | #define strcasecmp _stricmp 29 | #define strncasecmp _strnicmp 30 | #endif 31 | 32 | long int unshield_get_path_max(Unshield* unshield) 33 | { 34 | #ifdef PATH_MAX 35 | return PATH_MAX; 36 | #else 37 | long int path_max = pathconf(unshield->filename_pattern, _PC_PATH_MAX); 38 | if (path_max <= 0) 39 | path_max = 4096; 40 | return path_max; 41 | #endif 42 | } 43 | 44 | char *unshield_get_base_directory_name(Unshield *unshield) { 45 | long int path_max = unshield_get_path_max(unshield); 46 | char *p = unshield_get_last_path_separator(unshield->filename_pattern); 47 | char *dirname = malloc(path_max); 48 | 49 | if (p) { 50 | strncpy(dirname, unshield->filename_pattern, path_max); 51 | if ((unsigned int) (p - unshield->filename_pattern) > path_max) { 52 | dirname[path_max - 1] = 0; 53 | } else 54 | dirname[(p - unshield->filename_pattern)] = 0; 55 | } else 56 | strcpy(dirname, "."); 57 | 58 | return dirname; 59 | } 60 | 61 | 62 | static char* get_filename(Unshield* unshield, int index, const char* suffix) { 63 | if (unshield && unshield->filename_pattern) 64 | { 65 | long path_max = unshield_get_path_max(unshield); 66 | char* filename = malloc(path_max); 67 | 68 | if (filename == NULL) { 69 | unshield_error("Unable to allocate memory.\n"); 70 | goto exit; 71 | } 72 | 73 | if (snprintf(filename, path_max, unshield->filename_pattern, index, suffix) >= path_max) { 74 | unshield_error("Pathname exceeds system limits.\n"); 75 | goto exit; 76 | } 77 | 78 | exit: 79 | return filename; 80 | } 81 | 82 | return NULL; 83 | } 84 | 85 | 86 | FILE* unshield_fopen_for_reading(Unshield* unshield, int index, const char* suffix) 87 | { 88 | if (unshield && unshield->filename_pattern) 89 | { 90 | FILE* result = NULL; 91 | char* filename = get_filename(unshield, index, suffix); 92 | char* dirname = unshield_get_base_directory_name(unshield); 93 | const char *q; 94 | struct dirent *dent = NULL; 95 | DIR *sourcedir = NULL; 96 | long int path_max = unshield_get_path_max(unshield); 97 | 98 | q=unshield_get_last_path_separator(filename); 99 | if (q) 100 | q++; 101 | else 102 | q=filename; 103 | 104 | sourcedir = unshield_opendir(unshield, dirname); 105 | /* Search for the File case independent */ 106 | if (sourcedir) 107 | { 108 | for (dent=unshield_readdir(unshield, sourcedir);dent; 109 | dent=unshield_readdir(unshield, sourcedir)) 110 | { 111 | if (!(strcasecmp(q, dent->d_name))) 112 | { 113 | /*unshield_trace("Found match %s\n",dent->d_name);*/ 114 | break; 115 | } 116 | } 117 | 118 | if (dent == NULL) 119 | { 120 | unshield_trace("File %s not found even case insensitive\n",filename); 121 | goto exit; 122 | } 123 | else 124 | if(snprintf(filename, path_max, "%s/%s", dirname, dent->d_name)>=path_max) 125 | { 126 | unshield_error("Pathname exceeds system limits.\n"); 127 | goto exit; 128 | } 129 | } 130 | else 131 | unshield_trace("Could not open directory %s error %s\n", dirname, strerror(errno)); 132 | 133 | #if VERBOSE 134 | unshield_trace("Opening file '%s'", filename); 135 | #endif 136 | result = unshield_fopen(unshield, filename, "rb"); 137 | 138 | exit: 139 | if (sourcedir) 140 | unshield_closedir(unshield, sourcedir); 141 | free(filename); 142 | free(dirname); 143 | return result; 144 | } 145 | 146 | return NULL; 147 | } 148 | 149 | long long unshield_fsize(Unshield* unshield, FILE* file) 150 | { 151 | long long result; 152 | long long previous = unshield_ftell(unshield, file); 153 | unshield_fseek(unshield, file, 0L, SEEK_END); 154 | result = unshield_ftell(unshield, file); 155 | unshield_fseek(unshield, file, previous, SEEK_SET); 156 | return result; 157 | } 158 | 159 | bool unshield_read_common_header(uint8_t** buffer, CommonHeader* common) 160 | { 161 | uint8_t* p = *buffer; 162 | common->signature = READ_UINT32(p); p += 4; 163 | 164 | if (CAB_SIGNATURE != common->signature) 165 | { 166 | unshield_error("Invalid file signature"); 167 | 168 | if (MSCF_SIGNATURE == common->signature) 169 | unshield_warning("Found Microsoft Cabinet header. Use cabextract (https://www.cabextract.org.uk/) to unpack this file."); 170 | 171 | return false; 172 | } 173 | 174 | common->version = READ_UINT32(p); p += 4; 175 | common->volume_info = READ_UINT32(p); p += 4; 176 | common->cab_descriptor_offset = READ_UINT32(p); p += 4; 177 | common->cab_descriptor_size = READ_UINT32(p); p += 4; 178 | 179 | #if VERBOSE 180 | unshield_trace("Common header: %08x %08x %08x %08x", 181 | common->version, 182 | common->volume_info, 183 | common->cab_descriptor_offset, 184 | common->cab_descriptor_size); 185 | #endif 186 | 187 | *buffer = p; 188 | return true; 189 | } 190 | 191 | /** 192 | Get pointer at cab descriptor + offset 193 | */ 194 | uint8_t* unshield_header_get_buffer(Header* header, uint32_t offset) 195 | { 196 | if (offset) 197 | return 198 | header->data + 199 | header->common.cab_descriptor_offset + 200 | offset; 201 | else 202 | return NULL; 203 | } 204 | 205 | /** 206 | Returns the last path separator in a filesystem path 207 | */ 208 | char *unshield_get_last_path_separator(char *path) 209 | { 210 | char *p = strrchr(path, '/'); 211 | 212 | #ifdef WIN32 213 | char *pbs = strrchr(path, '\\'); 214 | 215 | if (NULL != pbs && (NULL == p || pbs > p)) 216 | return pbs; 217 | #endif 218 | 219 | return p; 220 | } 221 | 222 | static int unshield_strlen_utf16(const uint16_t* utf16) 223 | { 224 | const uint16_t* current = utf16; 225 | while (*current++) 226 | ; 227 | return current - utf16; 228 | } 229 | 230 | 231 | static StringBuffer* unshield_add_string_buffer(Header* header) 232 | { 233 | StringBuffer* result = NEW1(StringBuffer); 234 | result->next = header->string_buffer; 235 | return header->string_buffer = result; 236 | } 237 | 238 | 239 | static const char* unshield_utf16_to_utf8(Header* header, const uint16_t* utf16) 240 | { 241 | StringBuffer* string_buffer = unshield_add_string_buffer(header); 242 | int length = unshield_strlen_utf16(utf16); 243 | int buffer_size = 3 * length + 1; 244 | char* target = string_buffer->string = NEW(char, buffer_size); 245 | size_t result = utf16_to_utf8( 246 | utf16, length + 1, 247 | target, buffer_size); 248 | return string_buffer->string; 249 | } 250 | 251 | const char* unshield_get_utf8_string(Header* header, const void* buffer) 252 | { 253 | if (header->major_version >= 17 && buffer != NULL) 254 | { 255 | return unshield_utf16_to_utf8(header, (const uint16_t*)buffer); 256 | } 257 | else 258 | { 259 | return (const char*)buffer; 260 | } 261 | } 262 | 263 | /** 264 | Get string at cab descriptor offset + string offset 265 | */ 266 | const char* unshield_header_get_string(Header* header, uint32_t offset) 267 | { 268 | return unshield_get_utf8_string(header, unshield_header_get_buffer(header, offset)); 269 | } 270 | 271 | 272 | -------------------------------------------------------------------------------- /lib/md5/md5c.c: -------------------------------------------------------------------------------- 1 | /* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm 2 | */ 3 | 4 | /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 5 | rights reserved. 6 | 7 | License to copy and use this software is granted provided that it 8 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 9 | Algorithm" in all material mentioning or referencing this software 10 | or this function. 11 | 12 | License is also granted to make and use derivative works provided 13 | that such works are identified as "derived from the RSA Data 14 | Security, Inc. MD5 Message-Digest Algorithm" in all material 15 | mentioning or referencing the derived work. 16 | 17 | RSA Data Security, Inc. makes no representations concerning either 18 | the merchantability of this software or the suitability of this 19 | software for any particular purpose. It is provided "as is" 20 | without express or implied warranty of any kind. 21 | 22 | These notices must be retained in any copies of any part of this 23 | documentation and/or software. 24 | */ 25 | 26 | #include "global.h" 27 | #include "md5.h" 28 | 29 | /* Constants for MD5Transform routine. 30 | */ 31 | #define S11 7 32 | #define S12 12 33 | #define S13 17 34 | #define S14 22 35 | #define S21 5 36 | #define S22 9 37 | #define S23 14 38 | #define S24 20 39 | #define S31 4 40 | #define S32 11 41 | #define S33 16 42 | #define S34 23 43 | #define S41 6 44 | #define S42 10 45 | #define S43 15 46 | #define S44 21 47 | 48 | static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64])); 49 | static void Encode PROTO_LIST 50 | ((unsigned char *, UINT4 *, unsigned int)); 51 | static void Decode PROTO_LIST 52 | ((UINT4 *, unsigned char *, unsigned int)); 53 | static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int)); 54 | static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int)); 55 | 56 | static unsigned char PADDING[64] = { 57 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 60 | }; 61 | 62 | /* F, G, H and I are basic MD5 functions. 63 | */ 64 | #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) 65 | #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) 66 | #define H(x, y, z) ((x) ^ (y) ^ (z)) 67 | #define I(x, y, z) ((y) ^ ((x) | (~z))) 68 | 69 | /* ROTATE_LEFT rotates x left n bits. 70 | */ 71 | #define ROTATE_LEFT(x, n) ((((x) << (n)) & 0xffffffffU) | ((x) >> (32-(n)))) 72 | 73 | /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. 74 | Rotation is separate from addition to prevent recomputation. 75 | */ 76 | #define FF(a, b, c, d, x, s, ac) { \ 77 | (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ 78 | (a) = ROTATE_LEFT ((a), (s)); \ 79 | (a) += (b); \ 80 | } 81 | #define GG(a, b, c, d, x, s, ac) { \ 82 | (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ 83 | (a) = ROTATE_LEFT ((a), (s)); \ 84 | (a) += (b); \ 85 | } 86 | #define HH(a, b, c, d, x, s, ac) { \ 87 | (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ 88 | (a) = ROTATE_LEFT ((a), (s)); \ 89 | (a) += (b); \ 90 | } 91 | #define II(a, b, c, d, x, s, ac) { \ 92 | (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ 93 | (a) = ROTATE_LEFT ((a), (s)); \ 94 | (a) += (b); \ 95 | } 96 | 97 | /* MD5 initialization. Begins an MD5 operation, writing a new context. 98 | */ 99 | void MD5Init(MD5_CTX *context) 100 | /* context: context */ 101 | { 102 | context->count[0] = context->count[1] = 0; 103 | /* Load magic initialization constants. 104 | */ 105 | context->state[0] = 0x67452301; 106 | context->state[1] = 0xefcdab89; 107 | context->state[2] = 0x98badcfe; 108 | context->state[3] = 0x10325476; 109 | } 110 | 111 | /* MD5 block update operation. Continues an MD5 message-digest 112 | operation, processing another message block, and updating the 113 | context. 114 | */ 115 | void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen) 116 | /* context: context */ 117 | /* input: input block */ 118 | /* inputlen: length of input block */ 119 | { 120 | unsigned int i, index, partLen; 121 | 122 | /* Compute number of bytes mod 64 */ 123 | index = (unsigned int)((context->count[0] >> 3) & 0x3F); 124 | 125 | /* Update number of bits */ 126 | if ((context->count[0] += ((UINT4)inputLen << 3)) 127 | < ((UINT4)inputLen << 3)) 128 | context->count[1]++; 129 | context->count[1] += ((UINT4)inputLen >> 29); 130 | 131 | partLen = 64 - index; 132 | 133 | /* Transform as many times as possible. 134 | */ 135 | if (inputLen >= partLen) { 136 | MD5_memcpy 137 | ((POINTER)&context->buffer[index], (POINTER)input, partLen); 138 | MD5Transform (context->state, context->buffer); 139 | 140 | for (i = partLen; i + 63 < inputLen; i += 64) 141 | MD5Transform (context->state, &input[i]); 142 | 143 | index = 0; 144 | } 145 | else 146 | i = 0; 147 | 148 | /* Buffer remaining input */ 149 | MD5_memcpy 150 | ((POINTER)&context->buffer[index], (POINTER)&input[i], 151 | inputLen-i); 152 | } 153 | 154 | /* MD5 finalization. Ends an MD5 message-digest operation, writing the 155 | the message digest and zeroizing the context. 156 | */ 157 | void MD5Final(unsigned char digest[16], MD5_CTX *context) 158 | /* digest: message digest */ 159 | /* context: context */ 160 | { 161 | unsigned char bits[8]; 162 | unsigned int index, padLen; 163 | 164 | /* Save number of bits */ 165 | Encode (bits, context->count, 8); 166 | 167 | /* Pad out to 56 mod 64. 168 | */ 169 | index = (unsigned int)((context->count[0] >> 3) & 0x3f); 170 | padLen = (index < 56) ? (56 - index) : (120 - index); 171 | MD5Update (context, PADDING, padLen); 172 | 173 | /* Append length (before padding) */ 174 | MD5Update (context, bits, 8); 175 | /* Store state in digest */ 176 | Encode (digest, context->state, 16); 177 | 178 | /* Zeroize sensitive information. 179 | */ 180 | MD5_memset ((POINTER)context, 0, sizeof (*context)); 181 | } 182 | 183 | /* MD5 basic transformation. Transforms state based on block. 184 | */ 185 | static void MD5Transform (UINT4 state[4], unsigned char block[64]) 186 | { 187 | UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; 188 | 189 | Decode (x, block, 64); 190 | 191 | /* Round 1 */ 192 | FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ 193 | FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ 194 | FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ 195 | FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ 196 | FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ 197 | FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ 198 | FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ 199 | FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ 200 | FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ 201 | FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ 202 | FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ 203 | FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ 204 | FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ 205 | FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ 206 | FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ 207 | FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ 208 | 209 | /* Round 2 */ 210 | GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ 211 | GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ 212 | GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ 213 | GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ 214 | GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ 215 | GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ 216 | GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ 217 | GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ 218 | GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ 219 | GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ 220 | GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ 221 | GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ 222 | GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ 223 | GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ 224 | GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ 225 | GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ 226 | 227 | /* Round 3 */ 228 | HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ 229 | HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ 230 | HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ 231 | HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ 232 | HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ 233 | HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ 234 | HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ 235 | HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ 236 | HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ 237 | HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ 238 | HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ 239 | HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ 240 | HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ 241 | HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ 242 | HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ 243 | HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ 244 | 245 | /* Round 4 */ 246 | II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ 247 | II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ 248 | II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ 249 | II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ 250 | II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ 251 | II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ 252 | II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ 253 | II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ 254 | II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ 255 | II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ 256 | II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ 257 | II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ 258 | II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ 259 | II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ 260 | II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ 261 | II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ 262 | 263 | state[0] += a; 264 | state[1] += b; 265 | state[2] += c; 266 | state[3] += d; 267 | 268 | /* Zeroize sensitive information. 269 | */ 270 | MD5_memset ((POINTER)x, 0, sizeof (x)); 271 | } 272 | 273 | /* Encodes input (UINT4) into output (unsigned char). Assumes len is 274 | a multiple of 4. 275 | */ 276 | static void Encode(unsigned char *output, UINT4 *input, unsigned int len) 277 | { 278 | unsigned int i, j; 279 | 280 | for (i = 0, j = 0; j < len; i++, j += 4) { 281 | output[j] = (unsigned char)(input[i] & 0xff); 282 | output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); 283 | output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); 284 | output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); 285 | } 286 | } 287 | 288 | /* Decodes input (unsigned char) into output (UINT4). Assumes len is 289 | a multiple of 4. 290 | */ 291 | static void Decode(UINT4 *output, unsigned char *input, unsigned int len) 292 | { 293 | unsigned int i, j; 294 | 295 | for (i = 0, j = 0; j < len; i++, j += 4) 296 | output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | 297 | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); 298 | } 299 | 300 | /* Note: Replace "for loop" with standard memcpy if possible. 301 | */ 302 | 303 | static void MD5_memcpy(POINTER output, POINTER input, unsigned int len) 304 | { 305 | unsigned int i; 306 | 307 | for (i = 0; i < len; i++) 308 | output[i] = input[i]; 309 | } 310 | 311 | /* Note: Replace "for loop" with standard memset if possible. 312 | */ 313 | static void MD5_memset(POINTER output, int value, unsigned int len) 314 | { 315 | unsigned int i; 316 | 317 | for (i = 0; i < len; i++) 318 | ((char *)output)[i] = (char)value; 319 | } 320 | -------------------------------------------------------------------------------- /lib/converter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // The type of a single Unicode codepoint 5 | typedef uint32_t codepoint_t; 6 | 7 | // The last codepoint of the Basic Multilingual Plane, which is the part of Unicode that 8 | // UTF-16 can encode without surrogates 9 | #define BMP_END 0xFFFF 10 | 11 | // The highest valid Unicode codepoint 12 | #define UNICODE_MAX 0x10FFFF 13 | 14 | // The codepoint that is used to replace invalid encodings 15 | #define INVALID_CODEPOINT 0xFFFD 16 | 17 | // If a character, masked with GENERIC_SURROGATE_MASK, matches this value, it is a surrogate. 18 | #define GENERIC_SURROGATE_VALUE 0xD800 19 | // The mask to apply to a character before testing it against GENERIC_SURROGATE_VALUE 20 | #define GENERIC_SURROGATE_MASK 0xF800 21 | 22 | // If a character, masked with SURROGATE_MASK, matches this value, it is a high surrogate. 23 | #define HIGH_SURROGATE_VALUE 0xD800 24 | // If a character, masked with SURROGATE_MASK, matches this value, it is a low surrogate. 25 | #define LOW_SURROGATE_VALUE 0xDC00 26 | // The mask to apply to a character before testing it against HIGH_SURROGATE_VALUE or LOW_SURROGATE_VALUE 27 | #define SURROGATE_MASK 0xFC00 28 | 29 | // The value that is subtracted from a codepoint before encoding it in a surrogate pair 30 | #define SURROGATE_CODEPOINT_OFFSET 0x10000 31 | // A mask that can be applied to a surrogate to extract the codepoint value contained in it 32 | #define SURROGATE_CODEPOINT_MASK 0x03FF 33 | // The number of bits of SURROGATE_CODEPOINT_MASK 34 | #define SURROGATE_CODEPOINT_BITS 10 35 | 36 | 37 | // The highest codepoint that can be encoded with 1 byte in UTF-8 38 | #define UTF8_1_MAX 0x7F 39 | // The highest codepoint that can be encoded with 2 bytes in UTF-8 40 | #define UTF8_2_MAX 0x7FF 41 | // The highest codepoint that can be encoded with 3 bytes in UTF-8 42 | #define UTF8_3_MAX 0xFFFF 43 | // The highest codepoint that can be encoded with 4 bytes in UTF-8 44 | #define UTF8_4_MAX 0x10FFFF 45 | 46 | // If a character, masked with UTF8_CONTINUATION_MASK, matches this value, it is a UTF-8 continuation byte 47 | #define UTF8_CONTINUATION_VALUE 0x80 48 | // The mask to a apply to a character before testing it against UTF8_CONTINUATION_VALUE 49 | #define UTF8_CONTINUATION_MASK 0xC0 50 | // The number of bits of a codepoint that are contained in a UTF-8 continuation byte 51 | #define UTF8_CONTINUATION_CODEPOINT_BITS 6 52 | 53 | // Represents a UTF-8 bit pattern that can be set or verified 54 | typedef struct 55 | { 56 | // The mask that should be applied to the character before testing it 57 | utf8_t mask; 58 | // The value that the character should be tested against after applying the mask 59 | utf8_t value; 60 | } utf8_pattern; 61 | 62 | // The patterns for leading bytes of a UTF-8 codepoint encoding 63 | // Each pattern represents the leading byte for a character encoded with N UTF-8 bytes, 64 | // where N is the index + 1 65 | static const utf8_pattern utf8_leading_bytes[] = 66 | { 67 | { 0x80, 0x00 }, // 0xxxxxxx 68 | { 0xE0, 0xC0 }, // 110xxxxx 69 | { 0xF0, 0xE0 }, // 1110xxxx 70 | { 0xF8, 0xF0 } // 11110xxx 71 | }; 72 | 73 | // The number of elements in utf8_leading_bytes 74 | #define UTF8_LEADING_BYTES_LEN 4 75 | 76 | 77 | // Gets a codepoint from a UTF-16 string 78 | // utf16: The UTF-16 string 79 | // len: The length of the UTF-16 string, in UTF-16 characters 80 | // index: 81 | // A pointer to the current index on the string. 82 | // When the function returns, this will be left at the index of the last character 83 | // that composes the returned codepoint. 84 | // For surrogate pairs, this means the index will be left at the low surrogate. 85 | static codepoint_t decode_utf16(utf16_t const* utf16, size_t len, size_t* index) 86 | { 87 | utf16_t high = utf16[*index]; 88 | 89 | // BMP character 90 | if ((high & GENERIC_SURROGATE_MASK) != GENERIC_SURROGATE_VALUE) 91 | return high; 92 | 93 | // Unmatched low surrogate, invalid 94 | if ((high & SURROGATE_MASK) != HIGH_SURROGATE_VALUE) 95 | return INVALID_CODEPOINT; 96 | 97 | // String ended with an unmatched high surrogate, invalid 98 | if (*index == len - 1) 99 | return INVALID_CODEPOINT; 100 | 101 | utf16_t low = utf16[*index + 1]; 102 | 103 | // Unmatched high surrogate, invalid 104 | if ((low & SURROGATE_MASK) != LOW_SURROGATE_VALUE) 105 | return INVALID_CODEPOINT; 106 | 107 | // Two correctly matched surrogates, increase index to indicate we've consumed 108 | // two characters 109 | (*index)++; 110 | 111 | // The high bits of the codepoint are the value bits of the high surrogate 112 | // The low bits of the codepoint are the value bits of the low surrogate 113 | codepoint_t result = high & SURROGATE_CODEPOINT_MASK; 114 | result <<= SURROGATE_CODEPOINT_BITS; 115 | result |= low & SURROGATE_CODEPOINT_MASK; 116 | result += SURROGATE_CODEPOINT_OFFSET; 117 | 118 | // And if all else fails, it's valid 119 | return result; 120 | } 121 | 122 | // Calculates the number of UTF-8 characters it would take to encode a codepoint 123 | // The codepoint won't be checked for validity, that should be done beforehand. 124 | static int calculate_utf8_len(codepoint_t codepoint) 125 | { 126 | // An array with the max values would be more elegant, but a bit too heavy 127 | // for this common function 128 | 129 | if (codepoint <= UTF8_1_MAX) 130 | return 1; 131 | 132 | if (codepoint <= UTF8_2_MAX) 133 | return 2; 134 | 135 | if (codepoint <= UTF8_3_MAX) 136 | return 3; 137 | 138 | return 4; 139 | } 140 | 141 | // Encodes a codepoint in a UTF-8 string. 142 | // The codepoint won't be checked for validity, that should be done beforehand. 143 | // 144 | // codepoint: The codepoint to be encoded. 145 | // utf8: The UTF-8 string 146 | // len: The length of the UTF-8 string, in UTF-8 characters 147 | // index: The first empty index on the string. 148 | // 149 | // return: The number of characters written to the string. 150 | static size_t encode_utf8(codepoint_t codepoint, utf8_t* utf8, size_t len, size_t index) 151 | { 152 | int size = calculate_utf8_len(codepoint); 153 | 154 | // Not enough space left on the string 155 | if (index + size > len) 156 | return 0; 157 | 158 | // Write the continuation bytes in reverse order first 159 | for (int cont_index = size - 1; cont_index > 0; cont_index--) 160 | { 161 | utf8_t cont = codepoint & ~UTF8_CONTINUATION_MASK; 162 | cont |= UTF8_CONTINUATION_VALUE; 163 | 164 | utf8[index + cont_index] = cont; 165 | codepoint >>= UTF8_CONTINUATION_CODEPOINT_BITS; 166 | } 167 | 168 | // Write the leading byte 169 | utf8_pattern pattern = utf8_leading_bytes[size - 1]; 170 | 171 | utf8_t lead = codepoint & ~(pattern.mask); 172 | lead |= pattern.value; 173 | 174 | utf8[index] = lead; 175 | 176 | return size; 177 | } 178 | 179 | size_t utf16_to_utf8(utf16_t const* utf16, size_t utf16_len, utf8_t* utf8, size_t utf8_len) 180 | { 181 | // The next codepoint that will be written in the UTF-8 string 182 | // or the size of the required buffer if utf8 is NULL 183 | size_t utf8_index = 0; 184 | 185 | for (size_t utf16_index = 0; utf16_index < utf16_len; utf16_index++) 186 | { 187 | codepoint_t codepoint = decode_utf16(utf16, utf16_len, &utf16_index); 188 | 189 | if (utf8 == NULL) 190 | utf8_index += calculate_utf8_len(codepoint); 191 | else 192 | utf8_index += encode_utf8(codepoint, utf8, utf8_len, utf8_index); 193 | } 194 | 195 | return utf8_index; 196 | } 197 | 198 | // Gets a codepoint from a UTF-8 string 199 | // utf8: The UTF-8 string 200 | // len: The length of the UTF-8 string, in UTF-8 characters 201 | // index: 202 | // A pointer to the current index on the string. 203 | // When the function returns, this will be left at the index of the last character 204 | // that composes the returned codepoint. 205 | // For example, for a 3-byte codepoint, the index will be left at the third character. 206 | static codepoint_t decode_utf8(utf8_t const* utf8, size_t len, size_t* index) 207 | { 208 | utf8_t leading = utf8[*index]; 209 | 210 | // The number of bytes that are used to encode the codepoint 211 | int encoding_len = 0; 212 | // The pattern of the leading byte 213 | utf8_pattern leading_pattern; 214 | // If the leading byte matches the current leading pattern 215 | bool matches = false; 216 | 217 | do 218 | { 219 | encoding_len++; 220 | leading_pattern = utf8_leading_bytes[encoding_len - 1]; 221 | 222 | matches = (leading & leading_pattern.mask) == leading_pattern.value; 223 | 224 | } while (!matches && encoding_len < UTF8_LEADING_BYTES_LEN); 225 | 226 | // Leading byte doesn't match any known pattern, consider it invalid 227 | if (!matches) 228 | return INVALID_CODEPOINT; 229 | 230 | codepoint_t codepoint = leading & ~leading_pattern.mask; 231 | 232 | for (int i = 0; i < encoding_len - 1; i++) 233 | { 234 | // String ended before all continuation bytes were found 235 | // Invalid encoding 236 | if (*index + 1 >= len) 237 | return INVALID_CODEPOINT; 238 | 239 | utf8_t continuation = utf8[*index + 1]; 240 | 241 | // Number of continuation bytes not the same as advertised on the leading byte 242 | // Invalid encoding 243 | if ((continuation & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_VALUE) 244 | return INVALID_CODEPOINT; 245 | 246 | codepoint <<= UTF8_CONTINUATION_CODEPOINT_BITS; 247 | codepoint |= continuation & ~UTF8_CONTINUATION_MASK; 248 | 249 | (*index)++; 250 | } 251 | 252 | int proper_len = calculate_utf8_len(codepoint); 253 | 254 | // Overlong encoding: too many bytes were used to encode a short codepoint 255 | // Invalid encoding 256 | if (proper_len != encoding_len) 257 | return INVALID_CODEPOINT; 258 | 259 | // Surrogates are invalid Unicode codepoints, and should only be used in UTF-16 260 | // Invalid encoding 261 | if (codepoint < BMP_END && (codepoint & GENERIC_SURROGATE_MASK) == GENERIC_SURROGATE_VALUE) 262 | return INVALID_CODEPOINT; 263 | 264 | // UTF-8 can encode codepoints larger than the Unicode standard allows 265 | // Invalid encoding 266 | if (codepoint > UNICODE_MAX) 267 | return INVALID_CODEPOINT; 268 | 269 | return codepoint; 270 | } 271 | 272 | // Calculates the number of UTF-16 characters it would take to encode a codepoint 273 | // The codepoint won't be checked for validity, that should be done beforehand. 274 | static int calculate_utf16_len(codepoint_t codepoint) 275 | { 276 | if (codepoint <= BMP_END) 277 | return 1; 278 | 279 | return 2; 280 | } 281 | 282 | // Encodes a codepoint in a UTF-16 string. 283 | // The codepoint won't be checked for validity, that should be done beforehand. 284 | // 285 | // codepoint: The codepoint to be encoded. 286 | // utf16: The UTF-16 string 287 | // len: The length of the UTF-16 string, in UTF-16 characters 288 | // index: The first empty index on the string. 289 | // 290 | // return: The number of characters written to the string. 291 | static size_t encode_utf16(codepoint_t codepoint, utf16_t* utf16, size_t len, size_t index) 292 | { 293 | // Not enough space on the string 294 | if (index >= len) 295 | return 0; 296 | 297 | if (codepoint <= BMP_END) 298 | { 299 | utf16[index] = codepoint; 300 | return 1; 301 | } 302 | 303 | // Not enough space on the string for two surrogates 304 | if (index + 1 >= len) 305 | return 0; 306 | 307 | codepoint -= SURROGATE_CODEPOINT_OFFSET; 308 | 309 | utf16_t low = LOW_SURROGATE_VALUE; 310 | low |= codepoint & SURROGATE_CODEPOINT_MASK; 311 | 312 | codepoint >>= SURROGATE_CODEPOINT_BITS; 313 | 314 | utf16_t high = HIGH_SURROGATE_VALUE; 315 | high |= codepoint & SURROGATE_CODEPOINT_MASK; 316 | 317 | utf16[index] = high; 318 | utf16[index + 1] = low; 319 | 320 | return 2; 321 | } 322 | 323 | 324 | size_t utf8_to_utf16(utf8_t const* utf8, size_t utf8_len, utf16_t* utf16, size_t utf16_len) 325 | { 326 | // The next codepoint that will be written in the UTF-16 string 327 | // or the size of the required buffer if utf16 is NULL 328 | size_t utf16_index = 0; 329 | 330 | for (size_t utf8_index = 0; utf8_index < utf8_len; utf8_index++) 331 | { 332 | codepoint_t codepoint = decode_utf8(utf8, utf8_len, &utf8_index); 333 | 334 | if (utf16 == NULL) 335 | utf16_index += calculate_utf16_len(codepoint); 336 | else 337 | utf16_index += encode_utf16(codepoint, utf16, utf16_len, utf16_index); 338 | } 339 | 340 | return utf16_index; 341 | } 342 | -------------------------------------------------------------------------------- /win32_msvc/getopt.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * ya_getopt - Yet another getopt 4 | * https://github.com/kubo/ya_getopt 5 | * 6 | * Copyright 2015 Kubo Takehiro 7 | * 8 | * Redistribution and use in source and binary forms, with or without modification, are 9 | * permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this list of 12 | * conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 15 | * of conditions and the following disclaimer in the documentation and/or other materials 16 | * provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED 19 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those of the 29 | * authors and should not be interpreted as representing official policies, either expressed 30 | * or implied, of the authors. 31 | * 32 | */ 33 | 34 | 35 | #ifndef YA_GETOPT_H 36 | #define YA_GETOPT_H 1 37 | 38 | #if defined(__cplusplus) 39 | extern "C" { 40 | #endif 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #define ya_no_argument 0 48 | #define ya_required_argument 1 49 | #define ya_optional_argument 2 50 | 51 | struct option { 52 | const char *name; 53 | int has_arg; 54 | int *flag; 55 | int val; 56 | }; 57 | 58 | // error: ‘ya_getopt’ defined but not used [-Werror=unused-function] 59 | ///static int ya_getopt(int argc, char * const argv[], const char *optstring); 60 | static int ya_getopt_long(int argc, char * const argv[], const char *optstring, 61 | const struct option *longopts, int *longindex); 62 | ///static int ya_getopt_long_only(int argc, char * const argv[], const char *optstring, 63 | /// const struct option *longopts, int *longindex); 64 | 65 | #ifndef YA_GETOPT_NO_COMPAT_MACRO 66 | ///#define getopt ya_getopt 67 | #define getopt_long ya_getopt_long 68 | ///#define getopt_long_only ya_getopt_long_only 69 | #define optarg ya_optarg 70 | #define optind ya_optind 71 | #define opterr ya_opterr 72 | #define optopt ya_optopt 73 | #define no_argument ya_no_argument 74 | #define required_argument ya_required_argument 75 | #define optional_argument ya_optional_argument 76 | #endif 77 | 78 | 79 | // =============================================================================== 80 | 81 | char *ya_optarg = NULL; 82 | int ya_optind = 1; 83 | int ya_opterr = 1; 84 | int ya_optopt = '?'; 85 | 86 | static char *ya_optnext = NULL; 87 | static int posixly_correct = -1; 88 | static int handle_nonopt_argv = 0; 89 | 90 | static void ya_getopt_error(const char *optstring, const char *format, ...); 91 | static void check_gnu_extension(const char *optstring); 92 | static int ya_getopt_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int long_only); 93 | static int ya_getopt_shortopts(int argc, char * const argv[], const char *optstring, int long_only); 94 | static int ya_getopt_longopts(int argc, char * const argv[], char *arg, const char *optstring, const struct option *longopts, int *longindex, int *long_only_flag); 95 | 96 | static void ya_getopt_error(const char *optstring, const char *format, ...) 97 | { 98 | if (ya_opterr && optstring[0] != ':') { 99 | va_list ap; 100 | va_start(ap, format); 101 | vfprintf(stderr, format, ap); 102 | va_end(ap); 103 | } 104 | } 105 | 106 | static void check_gnu_extension(const char *optstring) 107 | { 108 | if (optstring[0] == '+' || getenv("POSIXLY_CORRECT") != NULL) { 109 | posixly_correct = 1; 110 | } else { 111 | posixly_correct = 0; 112 | } 113 | if (optstring[0] == '-') { 114 | handle_nonopt_argv = 1; 115 | } else { 116 | handle_nonopt_argv = 0; 117 | } 118 | } 119 | 120 | static int is_option(const char *arg) 121 | { 122 | return arg[0] == '-' && arg[1] != '\0'; 123 | } 124 | 125 | // ================================================================== 126 | 127 | ///static int ya_getopt(int argc, char * const argv[], const char *optstring) 128 | ///{ 129 | /// return ya_getopt_internal(argc, argv, optstring, NULL, NULL, 0); 130 | ///} 131 | 132 | static int ya_getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) 133 | { 134 | return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 0); 135 | } 136 | 137 | ///static int ya_getopt_long_only(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) 138 | ///{ 139 | /// return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 1); 140 | ///} 141 | 142 | // ================================================================== 143 | 144 | static int ya_getopt_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int long_only) 145 | { 146 | static int start, end; 147 | 148 | if (ya_optopt == '?') { 149 | ya_optopt = 0; 150 | } 151 | 152 | if (posixly_correct == -1) { 153 | check_gnu_extension(optstring); 154 | } 155 | 156 | if (ya_optind == 0) { 157 | check_gnu_extension(optstring); 158 | ya_optind = 1; 159 | ya_optnext = NULL; 160 | } 161 | 162 | switch (optstring[0]) { 163 | case '+': 164 | case '-': 165 | optstring++; 166 | } 167 | 168 | if (ya_optnext == NULL && start != 0) { 169 | int last_pos = ya_optind - 1; 170 | 171 | ya_optind -= end - start; 172 | if (ya_optind <= 0) { 173 | ya_optind = 1; 174 | } 175 | while (start < end--) { 176 | int i; 177 | char *arg = argv[end]; 178 | 179 | for (i = end; i < last_pos; i++) { 180 | ((char **)argv)[i] = argv[i + 1]; 181 | } 182 | ((char const **)argv)[i] = arg; 183 | last_pos--; 184 | } 185 | start = 0; 186 | } 187 | 188 | if (ya_optind >= argc) { 189 | ya_optarg = NULL; 190 | return -1; 191 | } 192 | if (ya_optnext == NULL) { 193 | const char *arg = argv[ya_optind]; 194 | if (!is_option(arg)) { 195 | if (handle_nonopt_argv) { 196 | ya_optarg = argv[ya_optind++]; 197 | start = 0; 198 | return 1; 199 | } else if (posixly_correct) { 200 | ya_optarg = NULL; 201 | return -1; 202 | } else { 203 | int i; 204 | 205 | start = ya_optind; 206 | for (i = ya_optind + 1; i < argc; i++) { 207 | if (is_option(argv[i])) { 208 | end = i; 209 | break; 210 | } 211 | } 212 | if (i == argc) { 213 | ya_optarg = NULL; 214 | return -1; 215 | } 216 | ya_optind = i; 217 | arg = argv[ya_optind]; 218 | } 219 | } 220 | if (strcmp(arg, "--") == 0) { 221 | ya_optind++; 222 | return -1; 223 | } 224 | if (longopts != NULL && arg[1] == '-') { 225 | return ya_getopt_longopts(argc, argv, argv[ya_optind] + 2, optstring, longopts, longindex, NULL); 226 | } 227 | } 228 | 229 | if (ya_optnext == NULL) { 230 | ya_optnext = argv[ya_optind] + 1; 231 | } 232 | if (long_only) { 233 | int long_only_flag = 0; 234 | int rv = ya_getopt_longopts(argc, argv, ya_optnext, optstring, longopts, longindex, &long_only_flag); 235 | if (!long_only_flag) { 236 | ya_optnext = NULL; 237 | return rv; 238 | } 239 | } 240 | 241 | return ya_getopt_shortopts(argc, argv, optstring, long_only); 242 | } 243 | 244 | static int ya_getopt_shortopts(int argc, char * const argv[], const char *optstring, int long_only) 245 | { 246 | int opt = *ya_optnext; 247 | const char *os = strchr(optstring, opt); 248 | 249 | if (os == NULL) { 250 | ya_optarg = NULL; 251 | if (long_only) { 252 | ya_getopt_error(optstring, "%s: unrecognized option '-%s'\n", argv[0], ya_optnext); 253 | ya_optind++; 254 | ya_optnext = NULL; 255 | } else { 256 | ya_optopt = opt; 257 | ya_getopt_error(optstring, "%s: invalid option -- '%c'\n", argv[0], opt); 258 | if (*(++ya_optnext) == 0) { 259 | ya_optind++; 260 | ya_optnext = NULL; 261 | } 262 | } 263 | return '?'; 264 | } 265 | if (os[1] == ':') { 266 | if (ya_optnext[1] == 0) { 267 | ya_optind++; 268 | ya_optnext = NULL; 269 | if (os[2] == ':') { 270 | /* optional argument */ 271 | ya_optarg = NULL; 272 | } else { 273 | if (ya_optind == argc) { 274 | ya_optarg = NULL; 275 | ya_optopt = opt; 276 | ya_getopt_error(optstring, "%s: option requires an argument -- '%c'\n", argv[0], opt); 277 | if (optstring[0] == ':') { 278 | return ':'; 279 | } else { 280 | return '?'; 281 | } 282 | } 283 | ya_optarg = argv[ya_optind]; 284 | ya_optind++; 285 | } 286 | } else { 287 | ya_optarg = ya_optnext + 1; 288 | ya_optind++; 289 | } 290 | ya_optnext = NULL; 291 | } else { 292 | ya_optarg = NULL; 293 | if (ya_optnext[1] == 0) { 294 | ya_optnext = NULL; 295 | ya_optind++; 296 | } else { 297 | ya_optnext++; 298 | } 299 | } 300 | return opt; 301 | } 302 | 303 | static int ya_getopt_longopts(int argc, char * const argv[], char *arg, const char *optstring, const struct option *longopts, int *longindex, int *long_only_flag) 304 | { 305 | char *val = NULL; 306 | const struct option *opt; 307 | size_t namelen; 308 | int idx; 309 | 310 | for (idx = 0; longopts[idx].name != NULL; idx++) { 311 | opt = &longopts[idx]; 312 | namelen = strlen(opt->name); 313 | if (strncmp(arg, opt->name, namelen) == 0) { 314 | switch (arg[namelen]) { 315 | case '\0': 316 | switch (opt->has_arg) { 317 | case ya_required_argument: 318 | ya_optind++; 319 | if (ya_optind == argc) { 320 | ya_optarg = NULL; 321 | ya_optopt = opt->val; 322 | ya_getopt_error(optstring, "%s: option '--%s' requires an argument\n", argv[0], opt->name); 323 | if (optstring[0] == ':') { 324 | return ':'; 325 | } else { 326 | return '?'; 327 | } 328 | } 329 | val = argv[ya_optind]; 330 | break; 331 | } 332 | goto found; 333 | case '=': 334 | if (opt->has_arg == ya_no_argument) { 335 | const char *hyphens = (argv[ya_optind][1] == '-') ? "--" : "-"; 336 | 337 | ya_optind++; 338 | ya_optarg = NULL; 339 | ya_optopt = opt->val; 340 | ya_getopt_error(optstring, "%s: option '%s%s' doesn't allow an argument\n", argv[0], hyphens, opt->name); 341 | return '?'; 342 | } 343 | val = arg + namelen + 1; 344 | goto found; 345 | } 346 | } 347 | } 348 | if (long_only_flag) { 349 | *long_only_flag = 1; 350 | } else { 351 | ya_getopt_error(optstring, "%s: unrecognized option '%s'\n", argv[0], argv[ya_optind]); 352 | ya_optind++; 353 | } 354 | return '?'; 355 | found: 356 | ya_optarg = val; 357 | ya_optind++; 358 | if (opt->flag) { 359 | *opt->flag = opt->val; 360 | } 361 | if (longindex) { 362 | *longindex = idx; 363 | } 364 | return opt->flag ? 0 : opt->val; 365 | } 366 | 367 | 368 | #if defined(__cplusplus) 369 | } 370 | #endif 371 | 372 | #endif 373 | -------------------------------------------------------------------------------- /lib/libunshield.c: -------------------------------------------------------------------------------- 1 | #define _BSD_SOURCE 1 2 | #define _DEFAULT_SOURCE 1 3 | #include "internal.h" 4 | #include "log.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef _WIN32 12 | #define unshield_native_fseek _fseeki64 13 | #define unshield_native_ftell _ftelli64 14 | #else 15 | #define unshield_native_fseek fseek 16 | #define unshield_native_ftell ftell 17 | #endif 18 | 19 | 20 | void *unshield_default_fopen(const char *filename, const char *modes, void *userdata) 21 | { 22 | (void)userdata; 23 | return fopen(filename, modes); 24 | } 25 | 26 | int unshield_default_fseek(void *file, long int offset, int whence, void *userdata) 27 | { 28 | (void)userdata; 29 | return unshield_native_fseek(file, offset, whence); 30 | } 31 | 32 | long int unshield_default_ftell(void *file, void *userdata) 33 | { 34 | (void)userdata; 35 | return unshield_native_ftell(file); 36 | } 37 | 38 | size_t unshield_default_fread(void *ptr, size_t size, size_t n, void *file, void *userdata) 39 | { 40 | (void)userdata; 41 | return fread(ptr, size, n, file); 42 | } 43 | 44 | size_t unshield_default_fwrite(const void *ptr, size_t size, size_t n, void *file, void *userdata) 45 | { 46 | (void)userdata; 47 | return fwrite(ptr, size, n, file); 48 | } 49 | 50 | int unshield_default_fclose(void *file, void *userdata) 51 | { 52 | (void)userdata; 53 | return fclose(file); 54 | } 55 | 56 | void *unshield_default_opendir(const char *name, void *userdata) 57 | { 58 | (void)userdata; 59 | return opendir(name); 60 | } 61 | 62 | int unshield_default_closedir(void *dir, void *userdata) 63 | { 64 | (void)userdata; 65 | return closedir(dir); 66 | } 67 | 68 | struct dirent* unshield_default_readdir(void *dir, void *userdata) 69 | { 70 | (void)userdata; 71 | return readdir(dir); 72 | } 73 | 74 | static UnshieldIoCallbacks unshield_default_io_callbacks = { 75 | .fopen = unshield_default_fopen, 76 | .fseek = unshield_default_fseek, 77 | .ftell = unshield_default_ftell, 78 | .fread = unshield_default_fread, 79 | .fwrite = unshield_default_fwrite, 80 | .fclose = unshield_default_fclose, 81 | .opendir = unshield_default_opendir, 82 | .closedir = unshield_default_closedir, 83 | .readdir = unshield_default_readdir, 84 | }; 85 | 86 | /** 87 | Create filename pattern used by unshield_fopen_for_reading() 88 | */ 89 | static bool unshield_create_filename_pattern(Unshield* unshield, const char* filename)/*{{{*/ 90 | { 91 | /* 92 | TODO 93 | Correct his function so that it handles filenames with more than one dot ('.')! 94 | */ 95 | 96 | if (unshield && filename) 97 | { 98 | char pattern[256]; 99 | char* prefix = strdup(filename); 100 | char* p = unshield_get_last_path_separator(prefix); 101 | if (!p) 102 | p = prefix; 103 | 104 | for (; *p != '\0'; p++) 105 | { 106 | if ('.' == *p || isdigit(*p)) 107 | { 108 | *p = '\0'; 109 | break; 110 | } 111 | } 112 | 113 | snprintf(pattern, sizeof(pattern), "%s%%i.%%s", prefix); 114 | free(prefix); 115 | 116 | FREE(unshield->filename_pattern); 117 | unshield->filename_pattern = strdup(pattern); 118 | return true; 119 | } 120 | else 121 | return false; 122 | }/*}}}*/ 123 | 124 | static bool unshield_get_common_header(Header* header) 125 | { 126 | uint8_t* p = header->data; 127 | return unshield_read_common_header(&p, &header->common); 128 | } 129 | 130 | static bool unshield_get_cab_descriptor(Header* header) 131 | { 132 | if (header->common.cab_descriptor_size) 133 | { 134 | uint8_t* p = header->data + header->common.cab_descriptor_offset; 135 | int i; 136 | 137 | p += 0xc; 138 | header->cab.file_table_offset = READ_UINT32(p); p += 4; 139 | p += 4; 140 | header->cab.file_table_size = READ_UINT32(p); p += 4; 141 | header->cab.file_table_size2 = READ_UINT32(p); p += 4; 142 | header->cab.directory_count = READ_UINT32(p); p += 4; 143 | p += 8; 144 | header->cab.file_count = READ_UINT32(p); p += 4; 145 | header->cab.file_table_offset2 = READ_UINT32(p); p += 4; 146 | 147 | assert((p - (header->data + header->common.cab_descriptor_offset)) == 0x30); 148 | 149 | if (header->cab.file_table_size != header->cab.file_table_size2) 150 | unshield_warning("File table sizes do not match"); 151 | 152 | unshield_trace("Cabinet descriptor: %08x %08x %08x %08x", 153 | header->cab.file_table_offset, 154 | header->cab.file_table_size, 155 | header->cab.file_table_size2, 156 | header->cab.file_table_offset2 157 | ); 158 | 159 | unshield_trace("Directory count: %i", header->cab.directory_count); 160 | unshield_trace("File count: %i", header->cab.file_count); 161 | 162 | p += 0xe; 163 | 164 | for (i = 0; i < MAX_FILE_GROUP_COUNT; i++) 165 | { 166 | header->cab.file_group_offsets[i] = READ_UINT32(p); p += 4; 167 | } 168 | 169 | for (i = 0; i < MAX_COMPONENT_COUNT; i++) 170 | { 171 | header->cab.component_offsets[i] = READ_UINT32(p); p += 4; 172 | } 173 | 174 | return true; 175 | } 176 | else 177 | { 178 | unshield_error("No CAB descriptor available!"); 179 | return false; 180 | } 181 | } 182 | 183 | static bool unshield_get_file_table(Header* header) 184 | { 185 | uint8_t* p = header->data + 186 | header->common.cab_descriptor_offset + 187 | header->cab.file_table_offset; 188 | int count = header->cab.directory_count + header->cab.file_count; 189 | int i; 190 | 191 | header->file_table = calloc(count, sizeof(uint32_t)); 192 | 193 | for (i = 0; i < count; i++) 194 | { 195 | header->file_table[i] = READ_UINT32(p); p += 4; 196 | } 197 | 198 | return true; 199 | } 200 | 201 | static bool unshield_header_get_components(Header* header)/*{{{*/ 202 | { 203 | int count = 0; 204 | int i; 205 | int available = 16; 206 | 207 | header->components = malloc(available * sizeof(UnshieldComponent*)); 208 | 209 | for (i = 0; i < MAX_COMPONENT_COUNT; i++) 210 | { 211 | if (header->cab.component_offsets[i]) 212 | { 213 | OffsetList list; 214 | 215 | list.next_offset = header->cab.component_offsets[i]; 216 | 217 | while (list.next_offset) 218 | { 219 | uint8_t* p = unshield_header_get_buffer(header, list.next_offset); 220 | 221 | list.name_offset = READ_UINT32(p); p += 4; 222 | list.descriptor_offset = READ_UINT32(p); p += 4; 223 | list.next_offset = READ_UINT32(p); p += 4; 224 | 225 | if (count == available) 226 | { 227 | available <<= 1; 228 | header->components = realloc(header->components, available * sizeof(UnshieldComponent*)); 229 | } 230 | 231 | header->components[count++] = unshield_component_new(header, list.descriptor_offset); 232 | } 233 | } 234 | } 235 | 236 | header->component_count = count; 237 | 238 | return true; 239 | } /*}}}*/ 240 | 241 | static bool unshield_header_get_file_groups(Header* header)/*{{{*/ 242 | { 243 | int count = 0; 244 | int i; 245 | int available = 16; 246 | 247 | header->file_groups = malloc(available * sizeof(UnshieldFileGroup*)); 248 | 249 | for (i = 0; i < MAX_FILE_GROUP_COUNT; i++) 250 | { 251 | if (header->cab.file_group_offsets[i]) 252 | { 253 | OffsetList list; 254 | 255 | list.next_offset = header->cab.file_group_offsets[i]; 256 | 257 | while (list.next_offset) 258 | { 259 | uint8_t* p = unshield_header_get_buffer(header, list.next_offset); 260 | 261 | list.name_offset = READ_UINT32(p); p += 4; 262 | list.descriptor_offset = READ_UINT32(p); p += 4; 263 | list.next_offset = READ_UINT32(p); p += 4; 264 | 265 | if (count == available) 266 | { 267 | available <<= 1; 268 | header->file_groups = realloc(header->file_groups, available * sizeof(UnshieldFileGroup*)); 269 | } 270 | 271 | header->file_groups[count++] = unshield_file_group_new(header, list.descriptor_offset); 272 | } 273 | } 274 | } 275 | 276 | header->file_group_count = count; 277 | 278 | return true; 279 | } /*}}}*/ 280 | 281 | /** 282 | Read all header files 283 | */ 284 | static bool unshield_read_headers(Unshield* unshield, int version)/*{{{*/ 285 | { 286 | int i; 287 | bool iterate = true; 288 | Header* previous = NULL; 289 | 290 | if (unshield->header_list) 291 | { 292 | unshield_warning("Already have a header list"); 293 | return true; 294 | } 295 | 296 | for (i = 1; iterate; i++) 297 | { 298 | FILE* file = unshield_fopen_for_reading(unshield, i, HEADER_SUFFIX); 299 | 300 | if (file) 301 | { 302 | unshield_trace("Reading header from .hdr file %i.", i); 303 | iterate = false; 304 | } 305 | else 306 | { 307 | unshield_trace("Could not open .hdr file %i. Reading header from .cab file %i instead.", 308 | i, i); 309 | file = unshield_fopen_for_reading(unshield, i, CABINET_SUFFIX); 310 | } 311 | 312 | if (file) 313 | { 314 | size_t bytes_read; 315 | Header* header = NEW1(Header); 316 | header->index = i; 317 | 318 | header->size = FSIZE(unshield, file); 319 | if (header->size < 4) 320 | { 321 | unshield_error("Header file %i too small", i); 322 | FCLOSE(unshield, file); 323 | goto error; 324 | } 325 | 326 | header->data = malloc(header->size); 327 | if (!header->data) 328 | { 329 | unshield_error("Failed to allocate memory for header file %i", i); 330 | FCLOSE(unshield, file); 331 | goto error; 332 | } 333 | 334 | bytes_read = unshield_fread(unshield, header->data, 1, header->size, file); 335 | FCLOSE(unshield, file); 336 | 337 | if (bytes_read != header->size) 338 | { 339 | unshield_error("Failed to read from header file %i. Expected = %i, read = %i", 340 | i, header->size, bytes_read); 341 | goto error; 342 | } 343 | 344 | if (!unshield_get_common_header(header)) 345 | { 346 | unshield_error("Failed to read common header from header file %i", i); 347 | goto error; 348 | } 349 | 350 | if (version != -1) 351 | { 352 | header->major_version = version; 353 | } 354 | else if (header->common.version >> 24 == 1) 355 | { 356 | header->major_version = (header->common.version >> 12) & 0xf; 357 | } 358 | else if (header->common.version >> 24 == 2 359 | || header->common.version >> 24 == 4) 360 | { 361 | header->major_version = (header->common.version & 0xffff); 362 | if (header->major_version != 0) 363 | header->major_version = header->major_version / 100; 364 | } 365 | 366 | #if 0 367 | if (header->major_version < 5) 368 | header->major_version = 5; 369 | #endif 370 | 371 | unshield_trace("Version 0x%08x handled as major version %i", 372 | header->common.version, 373 | header->major_version); 374 | 375 | if (!unshield_get_cab_descriptor(header)) 376 | { 377 | unshield_error("Failed to read CAB descriptor from header file %i", i); 378 | goto error; 379 | } 380 | 381 | if (!unshield_get_file_table(header)) 382 | { 383 | unshield_error("Failed to read file table from header file %i", i); 384 | goto error; 385 | } 386 | 387 | if (!unshield_header_get_components(header)) 388 | { 389 | unshield_error("Failed to read components from header file %i", i); 390 | goto error; 391 | } 392 | 393 | if (!unshield_header_get_file_groups(header)) 394 | { 395 | unshield_error("Failed to read file groups from header file %i", i); 396 | goto error; 397 | } 398 | 399 | if (previous) 400 | previous->next = header; 401 | else 402 | previous = unshield->header_list = header; 403 | 404 | continue; 405 | 406 | error: 407 | if (header) 408 | FREE(header->data); 409 | FREE(header); 410 | iterate = false; 411 | } 412 | else 413 | iterate = false; 414 | } 415 | 416 | return (unshield->header_list != NULL); 417 | }/*}}}*/ 418 | 419 | Unshield* unshield_open(const char* filename)/*{{{*/ 420 | { 421 | return unshield_open_force_version(filename, -1); 422 | }/*}}}*/ 423 | 424 | Unshield* unshield_open_force_version(const char* filename, int version)/*{{{*/ 425 | { 426 | return unshield_open2_force_version(filename, version, NULL, NULL); 427 | }/*}}}*/ 428 | 429 | Unshield* unshield_open2(const char* filename, const UnshieldIoCallbacks* callbacks, void* userdata)/*{{{*/ 430 | { 431 | return unshield_open2_force_version(filename, -1, callbacks, userdata); 432 | }/*}}}*/ 433 | 434 | Unshield* unshield_open2_force_version(const char* filename, int version, const UnshieldIoCallbacks* callbacks, void* userdata)/*{{{*/ 435 | { 436 | Unshield* unshield = NEW1(Unshield); 437 | if (!unshield) 438 | { 439 | unshield_error("Failed to allocate memory for Unshield structure"); 440 | goto error; 441 | } 442 | 443 | unshield->io_callbacks = callbacks == NULL ? &unshield_default_io_callbacks : callbacks; 444 | unshield->io_userdata = userdata; 445 | 446 | if (!unshield_create_filename_pattern(unshield, filename)) 447 | { 448 | unshield_error("Failed to create filename pattern"); 449 | goto error; 450 | } 451 | 452 | if (!unshield_read_headers(unshield, version)) 453 | { 454 | unshield_error("Failed to read header files"); 455 | goto error; 456 | } 457 | 458 | return unshield; 459 | 460 | error: 461 | unshield_close(unshield); 462 | return NULL; 463 | }/*}}}*/ 464 | 465 | 466 | static void unshield_free_string_buffers(Header* header) 467 | { 468 | StringBuffer* current = header->string_buffer; 469 | header->string_buffer = NULL; 470 | 471 | while (current != NULL) 472 | { 473 | StringBuffer* next = current->next; 474 | FREE(current->string); 475 | FREE(current); 476 | current = next; 477 | } 478 | } 479 | 480 | void unshield_close(Unshield* unshield)/*{{{*/ 481 | { 482 | if (unshield) 483 | { 484 | Header* header; 485 | 486 | for(header = unshield->header_list; header; ) 487 | { 488 | Header* next = header->next; 489 | int i; 490 | 491 | unshield_free_string_buffers(header); 492 | 493 | if (header->components) 494 | { 495 | for (i = 0; i < header->component_count; i++) 496 | unshield_component_destroy(header->components[i]); 497 | free(header->components); 498 | } 499 | 500 | if (header->file_groups) 501 | { 502 | for (i = 0; i < header->file_group_count; i++) 503 | unshield_file_group_destroy(header->file_groups[i]); 504 | free(header->file_groups); 505 | } 506 | 507 | if (header->file_descriptors) 508 | { 509 | for (i = 0; i < (int)header->cab.file_count; i++) 510 | FREE(header->file_descriptors[i]); 511 | free(header->file_descriptors); 512 | } 513 | 514 | FREE(header->file_table); 515 | 516 | FREE(header->data); 517 | FREE(header); 518 | 519 | header = next; 520 | } 521 | 522 | FREE(unshield->filename_pattern); 523 | free(unshield); 524 | } 525 | }/*}}}*/ 526 | 527 | bool unshield_is_unicode(Unshield* unshield) 528 | { 529 | if (unshield) 530 | { 531 | Header* header = unshield->header_list; 532 | 533 | return header->major_version >= 17; 534 | } 535 | else 536 | return false; 537 | } 538 | -------------------------------------------------------------------------------- /src/unshield.c: -------------------------------------------------------------------------------- 1 | #ifdef __linux__ 2 | #define _BSD_SOURCE 1 3 | #define _DEFAULT_SOURCE 1 4 | #define _POSIX_C_SOURCE 2 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "../lib/libunshield.h" 16 | #ifdef HAVE_CONFIG_H 17 | #include "lib/unshield_config.h" 18 | #endif 19 | #if HAVE_FNMATCH 20 | #include 21 | #endif 22 | #ifdef HAVE_ICONV 23 | #include 24 | #include 25 | #endif 26 | 27 | #ifndef VERSION 28 | #define VERSION "Unknown" 29 | #endif 30 | 31 | #define FREE(ptr) { if (ptr) { free(ptr); ptr = NULL; } } 32 | 33 | #ifdef _WIN32 34 | #define realpath(N,R) _fullpath((R),(N),_MAX_PATH) 35 | #include 36 | #ifndef PATH_MAX 37 | #define PATH_MAX _MAX_PATH 38 | #endif 39 | #else 40 | #include 41 | #endif 42 | 43 | #ifndef NAME_MAX 44 | #define NAME_MAX FILENAME_MAX 45 | #endif 46 | 47 | typedef enum 48 | { 49 | ACTION_EXTRACT, 50 | ACTION_LIST_COMPONENTS, 51 | ACTION_LIST_FILE_GROUPS, 52 | ACTION_LIST_FILES, 53 | ACTION_TEST 54 | } ACTION; 55 | 56 | typedef enum 57 | { 58 | FORMAT_NEW, 59 | FORMAT_OLD, 60 | FORMAT_RAW 61 | } FORMAT; 62 | 63 | #define DEFAULT_OUTPUT_DIRECTORY "." 64 | 65 | static const char* output_directory = DEFAULT_OUTPUT_DIRECTORY; 66 | static const char* file_group_name = NULL; 67 | static const char* component_name = NULL; 68 | static bool junk_paths = false; 69 | static bool make_lowercase = false; 70 | static bool raw_filename = false; 71 | static ACTION action = ACTION_EXTRACT; 72 | static int log_level = UNSHIELD_LOG_LEVEL_LOWEST; 73 | static int exit_status = 0; 74 | static FORMAT format = FORMAT_NEW; 75 | static int is_version = -1; 76 | static const char* cab_file_name = NULL; 77 | static char* const* path_names = NULL; 78 | static int path_name_count = 0; 79 | #ifdef HAVE_ICONV 80 | static const char* encoding = NULL; 81 | iconv_t encoding_descriptor = (iconv_t)-1; 82 | #endif 83 | 84 | static bool make_sure_directory_exists(const char* directory)/*{{{*/ 85 | { 86 | struct stat dir_stat; 87 | const char* p = directory; 88 | bool success = false; 89 | char* current = NULL; 90 | 91 | while (p && *p) 92 | { 93 | if ('/' == *p) 94 | p++; 95 | else if (0 == strncmp(p, "./", 2)) 96 | p+=2; 97 | else if (0 == strncmp(p, "../", 3)) 98 | p+=3; 99 | #ifdef WIN32 100 | if ('\\' == *p) 101 | p++; 102 | else if (0 == strncmp(p, ".\\", 2)) 103 | p += 2; 104 | else if (0 == strncmp(p, "..\\", 3)) 105 | p += 3; 106 | #endif 107 | else 108 | { 109 | int is_win_root = 0; 110 | const char* slash = strchr(p, '/'); 111 | #ifdef WIN32 112 | const char* backslash = strchr(p, '\\'); 113 | if (NULL != backslash && (NULL == slash || backslash < slash)) 114 | slash = backslash; 115 | #endif 116 | 117 | current = strdup(directory); 118 | 119 | if (slash) 120 | current[slash-directory] = '\0'; 121 | 122 | #ifdef WIN32 123 | if (slash - directory == 2 && current[1] == ':') 124 | is_win_root = 1; 125 | #endif 126 | 127 | if (!is_win_root && stat(current, &dir_stat) < 0) 128 | { 129 | #if defined (__MINGW32__) || defined (_WIN32) 130 | if (_mkdir(current) < 0) 131 | #else 132 | if (mkdir(current, 0700) < 0) 133 | #endif 134 | { 135 | fprintf(stderr, "Failed to create directory %s\n", directory); 136 | if(strlen(directory)>NAME_MAX) 137 | fprintf(stderr, "Directory name must be less than %i characters\n", NAME_MAX+1); 138 | goto exit; 139 | } 140 | } 141 | 142 | p = slash; 143 | FREE(current); 144 | } 145 | } 146 | 147 | success = true; 148 | 149 | exit: 150 | FREE(current); 151 | return success; 152 | }/*}}}*/ 153 | 154 | #ifdef HAVE_ICONV 155 | static bool convert_encoding(char *buffer, size_t size) 156 | { 157 | bool success = false; 158 | char *newbuf, *inbuf, *outbuf; 159 | size_t inbytesleft, outbytesleft, newsize; 160 | 161 | if (encoding_descriptor == (iconv_t)-1) 162 | return true; 163 | 164 | inbuf = buffer; 165 | inbytesleft = strlen(buffer); 166 | newbuf = outbuf = malloc(size); 167 | outbytesleft = size - 1; 168 | 169 | if (iconv(encoding_descriptor, 170 | &inbuf, &inbytesleft, 171 | &outbuf, &outbytesleft) == (size_t)-1) 172 | { 173 | fprintf(stderr, "Could not encode text to '%s' error %s\n", 174 | encoding, strerror(errno)); 175 | goto exit; 176 | } 177 | 178 | newsize = (size_t)(outbuf - newbuf); 179 | memcpy(buffer, newbuf, newsize); 180 | buffer[newsize] = '\0'; 181 | 182 | success = true; 183 | 184 | exit: 185 | free(newbuf); 186 | return success; 187 | } 188 | #endif 189 | 190 | static void show_usage(const char* name) 191 | { 192 | fprintf(stderr, 193 | "Syntax:\n" 194 | "\n" 195 | "\t%s [-c COMPONENT] [-d DIRECTORY] [-D LEVEL] [-g GROUP] [-h] [-i VERSION] [-e ENCODING] [-j] [-L] [-O] [-r] [-R] [-V] c|g|l|t|x CABFILE [FILENAME...]\n" 196 | "\n" 197 | "Options:\n" 198 | "\t-c COMPONENT Only list/extract this component\n" 199 | "\t-d DIRECTORY Extract files to DIRECTORY\n" 200 | "\t-D LEVEL Set debug log level\n" 201 | "\t 0 - No logging (default)\n" 202 | "\t 1 - Errors only\n" 203 | "\t 2 - Errors and warnings\n" 204 | "\t 3 - Errors, warnings and debug messages\n" 205 | "\t-g GROUP Only list/extract this file group\n" 206 | "\t-h Show this help message\n" 207 | "\t-i VERSION Force InstallShield version number (don't autodetect)\n" 208 | "\t-e ENCODING Convert filename character encoding to local codepage from ENCODING (implicitly sets -R)\n" 209 | "\t-j Junk paths (do not make directories)\n" 210 | "\t-L Make file and directory names lowercase\n" 211 | "\t-O Use old compression\n" 212 | "\t-r Save raw data (do not decompress)\n" 213 | "\t-R Don't do any conversion to file and directory names when extracting\n" 214 | "\t-V --version Print copyright and version information\n" 215 | "\n" 216 | "Commands:\n" 217 | "\tc List components\n" 218 | "\tg List file groups\n" 219 | "\tl List files\n" 220 | "\tt Test files\n" 221 | "\tx Extract files\n" 222 | "\n" 223 | "Other:\n" 224 | "\tCABFILE The file to list or extract contents of\n" 225 | "\tFILENAME... Optionally specify names of specific files to extract" 226 | #if HAVE_FNMATCH 227 | " (wildcards are supported)" 228 | #endif 229 | "\n" 230 | , 231 | name); 232 | } 233 | 234 | static bool handle_parameters( 235 | int argc, 236 | char* const argv[]) 237 | { 238 | int c; 239 | 240 | static struct option long_options[] = 241 | { 242 | { "version", no_argument, NULL, 'V' }, 243 | { NULL, 0, NULL, 0 } 244 | }; 245 | 246 | while ((c = getopt_long(argc, argv, "c:d:D:g:hi:e:jLOrRV", long_options, NULL)) != -1) 247 | { 248 | switch (c) 249 | { 250 | case 'c': 251 | component_name = optarg; 252 | break; 253 | 254 | case 'd': 255 | output_directory = optarg; 256 | break; 257 | 258 | case 'D': 259 | log_level = atoi(optarg); 260 | break; 261 | 262 | case 'g': 263 | file_group_name = optarg; 264 | break; 265 | 266 | case 'i': 267 | is_version = atoi(optarg); 268 | break; 269 | 270 | case 'e': 271 | #ifdef HAVE_ICONV 272 | encoding = optarg; 273 | raw_filename = true; 274 | #else 275 | fprintf(stderr, "This version of Unshield is not built with encoding support.\n"); 276 | return false; 277 | #endif 278 | break; 279 | 280 | case 'j': 281 | junk_paths = true; 282 | break; 283 | 284 | case 'L': 285 | make_lowercase = true; 286 | break; 287 | 288 | case 'R': 289 | raw_filename = true; 290 | break; 291 | 292 | case 'O': 293 | format = FORMAT_OLD; 294 | break; 295 | 296 | case 'r': 297 | format = FORMAT_RAW; 298 | break; 299 | 300 | case 'V': 301 | printf("Unshield version " VERSION ". MIT License. (C) 2003-2023 David Eriksson.\n"); 302 | exit(0); 303 | break; 304 | 305 | case 'h': 306 | default: 307 | show_usage(argv[0]); 308 | return false; 309 | } 310 | } 311 | 312 | unshield_set_log_level(log_level); 313 | 314 | if (optind == argc || !argv[optind]) 315 | { 316 | fprintf(stderr, "No action provided on command line.\n\n"); 317 | show_usage(argv[0]); 318 | return false; 319 | } 320 | 321 | char action_char = argv[optind++][0]; 322 | switch (action_char) 323 | { 324 | case 'c': 325 | action = ACTION_LIST_COMPONENTS; 326 | break; 327 | 328 | case 'g': 329 | action = ACTION_LIST_FILE_GROUPS; 330 | break; 331 | 332 | case 'l': 333 | action = ACTION_LIST_FILES; 334 | break; 335 | 336 | case 't': 337 | action = ACTION_TEST; 338 | break; 339 | 340 | case 'x': 341 | action = ACTION_EXTRACT; 342 | break; 343 | 344 | default: 345 | fprintf(stderr, "Unknown action '%c' on command line.\n\n", action_char); 346 | show_usage(argv[0]); 347 | return false; 348 | } 349 | 350 | cab_file_name = argv[optind++]; 351 | 352 | if (cab_file_name == NULL) 353 | { 354 | fprintf(stderr, "No InstallShield Cabinet File name provided on command line.\n\n"); 355 | show_usage(argv[0]); 356 | return false; 357 | } 358 | 359 | path_name_count = argc - optind; 360 | path_names = &argv[optind]; 361 | 362 | return true; 363 | } 364 | 365 | static bool extract_file(Unshield* unshield, const char* prefix, int index) 366 | { 367 | bool success; 368 | char* dirname; 369 | char* filename; 370 | char* p; 371 | int directory = unshield_file_directory(unshield, index); 372 | long int path_max; 373 | char* real_output_directory; 374 | char* real_filename; 375 | 376 | #ifdef PATH_MAX 377 | path_max = PATH_MAX; 378 | #else 379 | path_max = pathconf(prefix, _PC_PATH_MAX); 380 | if (path_max <= 0) 381 | path_max = 4096; 382 | #endif 383 | 384 | real_output_directory = malloc(path_max); 385 | real_filename = malloc(path_max); 386 | dirname = malloc(path_max); 387 | filename = malloc(path_max); 388 | if (real_output_directory == NULL || real_filename == NULL) 389 | { 390 | fprintf(stderr,"Unable to allocate memory."); 391 | success=false; 392 | goto exit; 393 | } 394 | 395 | 396 | if(strlen(output_directory) < path_max-1) 397 | { 398 | strncpy(dirname, output_directory,path_max-1); 399 | if (path_max > 0) 400 | dirname[path_max - 1]= '\0'; 401 | strcat(dirname, "/"); 402 | } 403 | else 404 | { 405 | fprintf(stderr, "\nOutput directory exceeds maximum path length.\n"); 406 | success = false; 407 | goto exit; 408 | } 409 | 410 | 411 | if (prefix && prefix[0]) 412 | { 413 | if(strlen(dirname)+strlen(prefix) < path_max-1) 414 | { 415 | strcat(dirname, prefix); 416 | strcat(dirname, "/"); 417 | } 418 | else 419 | { 420 | fprintf(stderr, "\nOutput directory exceeds maximum path length.\n"); 421 | success = false; 422 | goto exit; 423 | } 424 | } 425 | 426 | if (!junk_paths && directory >= 0) 427 | { 428 | const char* tmp = unshield_directory_name(unshield, directory); 429 | if (tmp && tmp[0]) 430 | { 431 | if(strlen(dirname)+strlen(tmp) < path_max-1) 432 | { 433 | strcat(dirname, tmp); 434 | strcat(dirname, "/"); 435 | } 436 | else 437 | { 438 | fprintf(stderr, "\nOutput directory exceeds maximum path length.\n"); 439 | success = false; 440 | goto exit; 441 | } 442 | } 443 | } 444 | 445 | for (p = dirname + strlen(output_directory); *p != '\0'; p++) 446 | { 447 | switch (*p) 448 | { 449 | case '\\': 450 | *p = '/'; 451 | break; 452 | 453 | case ' ': 454 | case '<': 455 | case '>': 456 | case '[': 457 | case ']': 458 | *p = '_'; 459 | break; 460 | 461 | default: 462 | if (!raw_filename) 463 | { 464 | if (!isprint(*p)) 465 | *p = '_'; 466 | else if (make_lowercase) 467 | *p = tolower(*p); 468 | } 469 | break;; 470 | } 471 | } 472 | 473 | #ifdef HAVE_ICONV 474 | if (!convert_encoding(dirname, path_max)) 475 | { 476 | success = false; 477 | goto exit; 478 | } 479 | #endif 480 | 481 | 482 | #if 0 483 | if (dirname[strlen(dirname)-1] != '/') 484 | strcat(dirname, "/"); 485 | #endif 486 | 487 | make_sure_directory_exists(dirname); 488 | 489 | snprintf(filename, path_max, "%s%s", 490 | dirname, unshield_file_name(unshield, index)); 491 | 492 | for (p = filename + strlen(dirname); *p != '\0'; p++) 493 | { 494 | if (!raw_filename) 495 | { 496 | if (!isprint(*p)) 497 | *p = '_'; 498 | else if (make_lowercase) 499 | *p = tolower(*p); 500 | } 501 | } 502 | 503 | #ifdef HAVE_ICONV 504 | if (!convert_encoding(filename + strlen(dirname), 505 | path_max - strlen(dirname))) 506 | { 507 | success = false; 508 | goto exit; 509 | } 510 | #endif 511 | 512 | #ifdef __GLIBC__ 513 | /* use GNU extension to return non-existing files to real_output_directory */ 514 | realpath(output_directory, real_output_directory); 515 | realpath(filename, real_filename); 516 | if (real_filename == NULL || strncmp(real_filename, 517 | real_output_directory, 518 | strlen(real_output_directory)) != 0) 519 | { 520 | fprintf(stderr, "\n\nExtraction failed.\n"); 521 | fprintf(stderr, "Error: %s (%d).\n", strerror(errno), errno); 522 | fprintf(stderr, "Possible directory traversal attack for: %s\n", filename); 523 | fprintf(stderr, "To be placed at: %s\n\n", real_filename); 524 | success = false; 525 | goto exit; 526 | } 527 | #endif 528 | 529 | printf(" extracting: %s\n", filename); 530 | switch (format) 531 | { 532 | case FORMAT_NEW: 533 | success = unshield_file_save(unshield, index, filename); 534 | break; 535 | case FORMAT_OLD: 536 | success = unshield_file_save_old(unshield, index, filename); 537 | break; 538 | case FORMAT_RAW: 539 | success = unshield_file_save_raw(unshield, index, filename); 540 | break; 541 | } 542 | 543 | exit: 544 | if (!success) 545 | { 546 | fprintf(stderr, "Failed to extract file '%s'.%s\n", 547 | unshield_file_name(unshield, index), 548 | (log_level < 3) ? "Run unshield again with -D 3 for more information." : ""); 549 | unlink(filename); 550 | exit_status = 1; 551 | } 552 | free(real_filename); 553 | free(real_output_directory); 554 | free(dirname); 555 | free(filename); 556 | return success; 557 | } 558 | 559 | static bool should_process_file(Unshield* unshield, int index) 560 | { 561 | int i; 562 | 563 | if (path_name_count == 0) 564 | return true; 565 | 566 | for (i = 0; i < path_name_count; i++) 567 | { 568 | #if HAVE_FNMATCH 569 | if (fnmatch(path_names[i], unshield_file_name(unshield, index), 0) == 0) 570 | return true; 571 | #else 572 | if (strcmp(path_names[i], unshield_file_name(unshield, index)) == 0) 573 | return true; 574 | #endif 575 | } 576 | 577 | return false; 578 | } 579 | 580 | static int extract_helper(Unshield* unshield, const char* prefix, int first, int last)/*{{{*/ 581 | { 582 | int i; 583 | int count = 0; 584 | 585 | for (i = first; i <= last; i++) 586 | { 587 | if (unshield_file_is_valid(unshield, i) 588 | && should_process_file(unshield, i) 589 | && extract_file(unshield, prefix, i)) 590 | count++; 591 | } 592 | 593 | return count; 594 | }/*}}}*/ 595 | 596 | static bool test_file(Unshield* unshield, int index) 597 | { 598 | bool success; 599 | 600 | printf(" testing: %s\n", unshield_file_name(unshield, index)); 601 | 602 | switch (format) 603 | { 604 | case FORMAT_NEW: 605 | success = unshield_file_save(unshield, index, NULL); 606 | break; 607 | case FORMAT_OLD: 608 | success = unshield_file_save_old(unshield, index, NULL); 609 | break; 610 | case FORMAT_RAW: 611 | success = unshield_file_save_raw(unshield, index, NULL); 612 | break; 613 | } 614 | 615 | if (!success) 616 | { 617 | fprintf(stderr, "Failed to extract file '%s'.%s\n", 618 | unshield_file_name(unshield, index), 619 | (log_level < 3) ? "Run unshield again with -D 3 for more information." : ""); 620 | exit_status = 1; 621 | } 622 | 623 | return success; 624 | } 625 | 626 | static int test_helper(Unshield* unshield, const char* prefix, int first, int last)/*{{{*/ 627 | { 628 | int i; 629 | int count = 0; 630 | 631 | for (i = first; i <= last; i++) 632 | { 633 | if (unshield_file_is_valid(unshield, i) && test_file(unshield, i)) 634 | count++; 635 | } 636 | 637 | return count; 638 | }/*}}}*/ 639 | 640 | static bool list_components(Unshield* unshield) 641 | { 642 | int i; 643 | int count = unshield_component_count(unshield); 644 | 645 | if (count < 0) 646 | return false; 647 | 648 | for (i = 0; i < count; i++) 649 | { 650 | printf("%s\n", unshield_component_name(unshield, i)); 651 | } 652 | 653 | printf("-------\n%i components\n", count); 654 | 655 | 656 | return true; 657 | } 658 | 659 | static bool list_file_groups(Unshield* unshield) 660 | { 661 | int i; 662 | int count = unshield_file_group_count(unshield); 663 | 664 | if (count < 0) 665 | return false; 666 | 667 | for (i = 0; i < count; i++) 668 | { 669 | printf("%s\n", unshield_file_group_name(unshield, i)); 670 | } 671 | 672 | printf("-------\n%i file groups\n", count); 673 | 674 | 675 | return true; 676 | } 677 | 678 | static int list_files_helper(Unshield* unshield, const char* prefix, int first, int last)/*{{{*/ 679 | { 680 | int i; 681 | int valid_count = 0; 682 | 683 | for (i = first; i <= last; i++) 684 | { 685 | char dirname[4096]; 686 | 687 | if (unshield_file_is_valid(unshield, i) && should_process_file(unshield, i)) 688 | { 689 | valid_count++; 690 | 691 | if (prefix && prefix[0]) 692 | { 693 | strcpy(dirname, prefix); 694 | strcat(dirname, "\\"); 695 | } 696 | else 697 | dirname[0] = '\0'; 698 | 699 | strcat(dirname, 700 | unshield_directory_name(unshield, unshield_file_directory(unshield, i))); 701 | 702 | #if 0 703 | for (p = dirname + strlen(output_directory); *p != '\0'; p++) 704 | if ('\\' == *p) 705 | *p = '/'; 706 | #endif 707 | 708 | if (dirname[strlen(dirname)-1] != '\\') 709 | strcat(dirname, "\\"); 710 | 711 | printf(" %8" SIZE_FORMAT " %s%s\n", 712 | unshield_file_size(unshield, i), 713 | dirname, 714 | unshield_file_name(unshield, i)); 715 | } 716 | } 717 | 718 | return valid_count; 719 | }/*}}}*/ 720 | 721 | typedef int (*ActionHelper)(Unshield* unshield, const char* prefix, int first, int last); 722 | 723 | static bool do_action(Unshield* unshield, ActionHelper helper) 724 | { 725 | int count = 0; 726 | 727 | if (component_name) 728 | { 729 | fprintf(stderr, "This action is not implemented for components, sorry! Patch welcome!\n"); 730 | return false; 731 | } 732 | else if (file_group_name) 733 | { 734 | UnshieldFileGroup* file_group = unshield_file_group_find(unshield, file_group_name); 735 | printf("File group: %s\n", file_group_name); 736 | if (file_group) 737 | count = helper(unshield, file_group_name, file_group->first_file, file_group->last_file); 738 | } 739 | else 740 | { 741 | int i; 742 | 743 | for (i = 0; i < unshield_file_group_count(unshield); i++) 744 | { 745 | UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i); 746 | if (file_group && file_group->first_file != -1) 747 | count += helper(unshield, file_group->name, file_group->first_file, file_group->last_file); 748 | } 749 | } 750 | 751 | printf(" -------- -------\n %i files\n", count); 752 | 753 | return true; 754 | } 755 | 756 | int main(int argc, char* const argv[]) 757 | { 758 | bool success = false; 759 | Unshield* unshield = NULL; 760 | 761 | setlocale(LC_ALL, ""); 762 | 763 | if (!handle_parameters(argc, argv)) 764 | goto exit; 765 | 766 | unshield = unshield_open_force_version(cab_file_name, is_version); 767 | if (!unshield) 768 | { 769 | fprintf(stderr, "Failed to open %s as an InstallShield Cabinet File\n", cab_file_name); 770 | goto exit; 771 | } 772 | 773 | #ifdef HAVE_ICONV 774 | if (!unshield_is_unicode(unshield) && encoding != NULL) 775 | { 776 | if ((encoding_descriptor = iconv_open("", encoding)) == (iconv_t)-1) 777 | { 778 | fprintf(stderr, "Cannot use encoding '%s' error %s\n", 779 | encoding, strerror(errno)); 780 | goto exit; 781 | } 782 | } 783 | #endif 784 | 785 | printf("Cabinet: %s\n", cab_file_name); 786 | 787 | switch (action) 788 | { 789 | case ACTION_EXTRACT: 790 | success = do_action(unshield, extract_helper); 791 | break; 792 | 793 | case ACTION_LIST_COMPONENTS: 794 | success = list_components(unshield); 795 | break; 796 | 797 | case ACTION_LIST_FILE_GROUPS: 798 | success = list_file_groups(unshield); 799 | break; 800 | 801 | case ACTION_LIST_FILES: 802 | success = do_action(unshield, list_files_helper); 803 | break; 804 | 805 | case ACTION_TEST: 806 | if (strcmp(output_directory, DEFAULT_OUTPUT_DIRECTORY) != 0) 807 | fprintf(stderr, "Output directory (-d) option has no effect with test (t) command.\n"); 808 | if (make_lowercase) 809 | fprintf(stderr, "Make lowercase (-L) option has no effect with test (t) command.\n"); 810 | success = do_action(unshield, test_helper); 811 | break; 812 | } 813 | 814 | exit: 815 | unshield_close(unshield); 816 | #ifdef HAVE_ICONV 817 | if (encoding_descriptor != (iconv_t)-1) 818 | iconv_close(encoding_descriptor); 819 | #endif 820 | if (!success) 821 | exit_status = 1; 822 | return exit_status; 823 | } 824 | 825 | -------------------------------------------------------------------------------- /win32_msvc/dirent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Dirent interface for Microsoft Visual Studio 3 | * Version 1.21 4 | * 5 | * Copyright (C) 2006-2012 Toni Ronkko 6 | * This file is part of dirent. Dirent may be freely distributed 7 | * under the MIT license. For all details and documentation, see 8 | * https://github.com/tronkko/dirent 9 | */ 10 | #ifndef DIRENT_H 11 | #define DIRENT_H 12 | 13 | /* 14 | * Include windows.h without Windows Sockets 1.1 to prevent conflicts with 15 | * Windows Sockets 2.0. 16 | */ 17 | #ifndef WIN32_LEAN_AND_MEAN 18 | # define WIN32_LEAN_AND_MEAN 19 | #endif 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* Indicates that d_type field is available in dirent structure */ 33 | #define _DIRENT_HAVE_D_TYPE 34 | 35 | /* Indicates that d_namlen field is available in dirent structure */ 36 | #define _DIRENT_HAVE_D_NAMLEN 37 | 38 | /* Entries missing from MSVC 6.0 */ 39 | #if !defined(FILE_ATTRIBUTE_DEVICE) 40 | # define FILE_ATTRIBUTE_DEVICE 0x40 41 | #endif 42 | 43 | /* File type and permission flags for stat(), general mask */ 44 | #if !defined(S_IFMT) 45 | # define S_IFMT _S_IFMT 46 | #endif 47 | 48 | /* Directory bit */ 49 | #if !defined(S_IFDIR) 50 | # define S_IFDIR _S_IFDIR 51 | #endif 52 | 53 | /* Character device bit */ 54 | #if !defined(S_IFCHR) 55 | # define S_IFCHR _S_IFCHR 56 | #endif 57 | 58 | /* Pipe bit */ 59 | #if !defined(S_IFFIFO) 60 | # define S_IFFIFO _S_IFFIFO 61 | #endif 62 | 63 | /* Regular file bit */ 64 | #if !defined(S_IFREG) 65 | # define S_IFREG _S_IFREG 66 | #endif 67 | 68 | /* Read permission */ 69 | #if !defined(S_IREAD) 70 | # define S_IREAD _S_IREAD 71 | #endif 72 | 73 | /* Write permission */ 74 | #if !defined(S_IWRITE) 75 | # define S_IWRITE _S_IWRITE 76 | #endif 77 | 78 | /* Execute permission */ 79 | #if !defined(S_IEXEC) 80 | # define S_IEXEC _S_IEXEC 81 | #endif 82 | 83 | /* Pipe */ 84 | #if !defined(S_IFIFO) 85 | # define S_IFIFO _S_IFIFO 86 | #endif 87 | 88 | /* Block device */ 89 | #if !defined(S_IFBLK) 90 | # define S_IFBLK 0 91 | #endif 92 | 93 | /* Link */ 94 | #if !defined(S_IFLNK) 95 | # define S_IFLNK 0 96 | #endif 97 | 98 | /* Socket */ 99 | #if !defined(S_IFSOCK) 100 | # define S_IFSOCK 0 101 | #endif 102 | 103 | /* Read user permission */ 104 | #if !defined(S_IRUSR) 105 | # define S_IRUSR S_IREAD 106 | #endif 107 | 108 | /* Write user permission */ 109 | #if !defined(S_IWUSR) 110 | # define S_IWUSR S_IWRITE 111 | #endif 112 | 113 | /* Execute user permission */ 114 | #if !defined(S_IXUSR) 115 | # define S_IXUSR 0 116 | #endif 117 | 118 | /* Read group permission */ 119 | #if !defined(S_IRGRP) 120 | # define S_IRGRP 0 121 | #endif 122 | 123 | /* Write group permission */ 124 | #if !defined(S_IWGRP) 125 | # define S_IWGRP 0 126 | #endif 127 | 128 | /* Execute group permission */ 129 | #if !defined(S_IXGRP) 130 | # define S_IXGRP 0 131 | #endif 132 | 133 | /* Read others permission */ 134 | #if !defined(S_IROTH) 135 | # define S_IROTH 0 136 | #endif 137 | 138 | /* Write others permission */ 139 | #if !defined(S_IWOTH) 140 | # define S_IWOTH 0 141 | #endif 142 | 143 | /* Execute others permission */ 144 | #if !defined(S_IXOTH) 145 | # define S_IXOTH 0 146 | #endif 147 | 148 | /* Maximum length of file name */ 149 | #if !defined(PATH_MAX) 150 | # define PATH_MAX MAX_PATH 151 | #endif 152 | #if !defined(FILENAME_MAX) 153 | # define FILENAME_MAX MAX_PATH 154 | #endif 155 | #if !defined(NAME_MAX) 156 | # define NAME_MAX FILENAME_MAX 157 | #endif 158 | 159 | /* File type flags for d_type */ 160 | #define DT_UNKNOWN 0 161 | #define DT_REG S_IFREG 162 | #define DT_DIR S_IFDIR 163 | #define DT_FIFO S_IFIFO 164 | #define DT_SOCK S_IFSOCK 165 | #define DT_CHR S_IFCHR 166 | #define DT_BLK S_IFBLK 167 | #define DT_LNK S_IFLNK 168 | 169 | /* Macros for converting between st_mode and d_type */ 170 | #define IFTODT(mode) ((mode) & S_IFMT) 171 | #define DTTOIF(type) (type) 172 | 173 | /* 174 | * File type macros. Note that block devices, sockets and links cannot be 175 | * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are 176 | * only defined for compatibility. These macros should always return false 177 | * on Windows. 178 | */ 179 | #if !defined(S_ISFIFO) 180 | # define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) 181 | #endif 182 | #if !defined(S_ISDIR) 183 | # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) 184 | #endif 185 | #if !defined(S_ISREG) 186 | # define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) 187 | #endif 188 | #if !defined(S_ISLNK) 189 | # define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) 190 | #endif 191 | #if !defined(S_ISSOCK) 192 | # define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) 193 | #endif 194 | #if !defined(S_ISCHR) 195 | # define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) 196 | #endif 197 | #if !defined(S_ISBLK) 198 | # define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) 199 | #endif 200 | 201 | /* Return the exact length of d_namlen without zero terminator */ 202 | #define _D_EXACT_NAMLEN(p) ((p)->d_namlen) 203 | 204 | /* Return number of bytes needed to store d_namlen */ 205 | #define _D_ALLOC_NAMLEN(p) (PATH_MAX) 206 | 207 | 208 | #ifdef __cplusplus 209 | extern "C" { 210 | #endif 211 | 212 | 213 | /* Wide-character version */ 214 | struct _wdirent { 215 | /* Always zero */ 216 | long d_ino; 217 | 218 | /* Structure size */ 219 | unsigned short d_reclen; 220 | 221 | /* Length of name without \0 */ 222 | size_t d_namlen; 223 | 224 | /* File type */ 225 | int d_type; 226 | 227 | /* File name */ 228 | wchar_t d_name[PATH_MAX]; 229 | }; 230 | typedef struct _wdirent _wdirent; 231 | 232 | struct _WDIR { 233 | /* Current directory entry */ 234 | struct _wdirent ent; 235 | 236 | /* Private file data */ 237 | WIN32_FIND_DATAW data; 238 | 239 | /* True if data is valid */ 240 | int cached; 241 | 242 | /* Win32 search handle */ 243 | HANDLE handle; 244 | 245 | /* Initial directory name */ 246 | wchar_t *patt; 247 | }; 248 | typedef struct _WDIR _WDIR; 249 | 250 | static _WDIR *_wopendir (const wchar_t *dirname); 251 | static struct _wdirent *_wreaddir (_WDIR *dirp); 252 | static int _wclosedir (_WDIR *dirp); 253 | static void _wrewinddir (_WDIR* dirp); 254 | 255 | 256 | /* For compatibility with Symbian */ 257 | #define wdirent _wdirent 258 | #define WDIR _WDIR 259 | #define wopendir _wopendir 260 | #define wreaddir _wreaddir 261 | #define wclosedir _wclosedir 262 | #define wrewinddir _wrewinddir 263 | 264 | 265 | /* Multi-byte character versions */ 266 | struct dirent { 267 | /* Always zero */ 268 | long d_ino; 269 | 270 | /* Structure size */ 271 | unsigned short d_reclen; 272 | 273 | /* Length of name without \0 */ 274 | size_t d_namlen; 275 | 276 | /* File type */ 277 | int d_type; 278 | 279 | /* File name */ 280 | char d_name[PATH_MAX]; 281 | }; 282 | typedef struct dirent dirent; 283 | 284 | struct DIR { 285 | struct dirent ent; 286 | struct _WDIR *wdirp; 287 | }; 288 | typedef struct DIR DIR; 289 | 290 | static DIR *opendir (const char *dirname); 291 | static struct dirent *readdir (DIR *dirp); 292 | static int closedir (DIR *dirp); 293 | static void rewinddir (DIR* dirp); 294 | 295 | 296 | /* Internal utility functions */ 297 | static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); 298 | static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); 299 | 300 | static int dirent_mbstowcs_s( 301 | size_t *pReturnValue, 302 | wchar_t *wcstr, 303 | size_t sizeInWords, 304 | const char *mbstr, 305 | size_t count); 306 | 307 | static int dirent_wcstombs_s( 308 | size_t *pReturnValue, 309 | char *mbstr, 310 | size_t sizeInBytes, 311 | const wchar_t *wcstr, 312 | size_t count); 313 | 314 | static void dirent_set_errno (int error); 315 | 316 | /* 317 | * Open directory stream DIRNAME for read and return a pointer to the 318 | * internal working area that is used to retrieve individual directory 319 | * entries. 320 | */ 321 | static _WDIR* 322 | _wopendir( 323 | const wchar_t *dirname) 324 | { 325 | _WDIR *dirp = NULL; 326 | int error; 327 | 328 | /* Must have directory name */ 329 | if (dirname == NULL || dirname[0] == '\0') { 330 | dirent_set_errno (ENOENT); 331 | return NULL; 332 | } 333 | 334 | /* Allocate new _WDIR structure */ 335 | dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); 336 | if (dirp != NULL) { 337 | DWORD n; 338 | 339 | /* Reset _WDIR structure */ 340 | dirp->handle = INVALID_HANDLE_VALUE; 341 | dirp->patt = NULL; 342 | dirp->cached = 0; 343 | 344 | /* Compute the length of full path plus zero terminator 345 | * 346 | * Note that on WinRT there's no way to convert relative paths 347 | * into absolute paths, so just assume its an absolute path. 348 | */ 349 | # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) 350 | n = wcslen(dirname); 351 | # else 352 | n = GetFullPathNameW (dirname, 0, NULL, NULL); 353 | # endif 354 | 355 | /* Allocate room for absolute directory name and search pattern */ 356 | dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); 357 | if (dirp->patt) { 358 | 359 | /* 360 | * Convert relative directory name to an absolute one. This 361 | * allows rewinddir() to function correctly even when current 362 | * working directory is changed between opendir() and rewinddir(). 363 | * 364 | * Note that on WinRT there's no way to convert relative paths 365 | * into absolute paths, so just assume its an absolute path. 366 | */ 367 | # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) 368 | wcsncpy_s(dirp->patt, n+1, dirname, n); 369 | # else 370 | n = GetFullPathNameW (dirname, n, dirp->patt, NULL); 371 | # endif 372 | if (n > 0) { 373 | wchar_t *p; 374 | 375 | /* Append search pattern \* to the directory name */ 376 | p = dirp->patt + n; 377 | if (dirp->patt < p) { 378 | switch (p[-1]) { 379 | case '\\': 380 | case '/': 381 | case ':': 382 | /* Directory ends in path separator, e.g. c:\temp\ */ 383 | /*NOP*/; 384 | break; 385 | 386 | default: 387 | /* Directory name doesn't end in path separator */ 388 | *p++ = '\\'; 389 | } 390 | } 391 | *p++ = '*'; 392 | *p = '\0'; 393 | 394 | /* Open directory stream and retrieve the first entry */ 395 | if (dirent_first (dirp)) { 396 | /* Directory stream opened successfully */ 397 | error = 0; 398 | } else { 399 | /* Cannot retrieve first entry */ 400 | error = 1; 401 | dirent_set_errno (ENOENT); 402 | } 403 | 404 | } else { 405 | /* Cannot retrieve full path name */ 406 | dirent_set_errno (ENOENT); 407 | error = 1; 408 | } 409 | 410 | } else { 411 | /* Cannot allocate memory for search pattern */ 412 | error = 1; 413 | } 414 | 415 | } else { 416 | /* Cannot allocate _WDIR structure */ 417 | error = 1; 418 | } 419 | 420 | /* Clean up in case of error */ 421 | if (error && dirp) { 422 | _wclosedir (dirp); 423 | dirp = NULL; 424 | } 425 | 426 | return dirp; 427 | } 428 | 429 | /* 430 | * Read next directory entry. The directory entry is returned in dirent 431 | * structure in the d_name field. Individual directory entries returned by 432 | * this function include regular files, sub-directories, pseudo-directories 433 | * "." and ".." as well as volume labels, hidden files and system files. 434 | */ 435 | static struct _wdirent* 436 | _wreaddir( 437 | _WDIR *dirp) 438 | { 439 | WIN32_FIND_DATAW *datap; 440 | struct _wdirent *entp; 441 | 442 | /* Read next directory entry */ 443 | datap = dirent_next (dirp); 444 | if (datap) { 445 | size_t n; 446 | DWORD attr; 447 | 448 | /* Pointer to directory entry to return */ 449 | entp = &dirp->ent; 450 | 451 | /* 452 | * Copy file name as wide-character string. If the file name is too 453 | * long to fit in to the destination buffer, then truncate file name 454 | * to PATH_MAX characters and zero-terminate the buffer. 455 | */ 456 | n = 0; 457 | while (n + 1 < PATH_MAX && datap->cFileName[n] != 0) { 458 | entp->d_name[n] = datap->cFileName[n]; 459 | n++; 460 | } 461 | dirp->ent.d_name[n] = 0; 462 | 463 | /* Length of file name excluding zero terminator */ 464 | entp->d_namlen = n; 465 | 466 | /* File type */ 467 | attr = datap->dwFileAttributes; 468 | if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { 469 | entp->d_type = DT_CHR; 470 | } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { 471 | entp->d_type = DT_DIR; 472 | } else { 473 | entp->d_type = DT_REG; 474 | } 475 | 476 | /* Reset dummy fields */ 477 | entp->d_ino = 0; 478 | entp->d_reclen = sizeof (struct _wdirent); 479 | 480 | } else { 481 | 482 | /* Last directory entry read */ 483 | entp = NULL; 484 | 485 | } 486 | 487 | return entp; 488 | } 489 | 490 | /* 491 | * Close directory stream opened by opendir() function. This invalidates the 492 | * DIR structure as well as any directory entry read previously by 493 | * _wreaddir(). 494 | */ 495 | static int 496 | _wclosedir( 497 | _WDIR *dirp) 498 | { 499 | int ok; 500 | if (dirp) { 501 | 502 | /* Release search handle */ 503 | if (dirp->handle != INVALID_HANDLE_VALUE) { 504 | FindClose (dirp->handle); 505 | dirp->handle = INVALID_HANDLE_VALUE; 506 | } 507 | 508 | /* Release search pattern */ 509 | if (dirp->patt) { 510 | free (dirp->patt); 511 | dirp->patt = NULL; 512 | } 513 | 514 | /* Release directory structure */ 515 | free (dirp); 516 | ok = /*success*/0; 517 | 518 | } else { 519 | /* Invalid directory stream */ 520 | dirent_set_errno (EBADF); 521 | ok = /*failure*/-1; 522 | } 523 | return ok; 524 | } 525 | 526 | /* 527 | * Rewind directory stream such that _wreaddir() returns the very first 528 | * file name again. 529 | */ 530 | static void 531 | _wrewinddir( 532 | _WDIR* dirp) 533 | { 534 | if (dirp) { 535 | /* Release existing search handle */ 536 | if (dirp->handle != INVALID_HANDLE_VALUE) { 537 | FindClose (dirp->handle); 538 | } 539 | 540 | /* Open new search handle */ 541 | dirent_first (dirp); 542 | } 543 | } 544 | 545 | /* Get first directory entry (internal) */ 546 | static WIN32_FIND_DATAW* 547 | dirent_first( 548 | _WDIR *dirp) 549 | { 550 | WIN32_FIND_DATAW *datap; 551 | 552 | /* Open directory and retrieve the first entry */ 553 | dirp->handle = FindFirstFileExW( 554 | dirp->patt, FindExInfoStandard, &dirp->data, 555 | FindExSearchNameMatch, NULL, 0); 556 | if (dirp->handle != INVALID_HANDLE_VALUE) { 557 | 558 | /* a directory entry is now waiting in memory */ 559 | datap = &dirp->data; 560 | dirp->cached = 1; 561 | 562 | } else { 563 | 564 | /* Failed to re-open directory: no directory entry in memory */ 565 | dirp->cached = 0; 566 | datap = NULL; 567 | 568 | } 569 | return datap; 570 | } 571 | 572 | /* Get next directory entry (internal) */ 573 | static WIN32_FIND_DATAW* 574 | dirent_next( 575 | _WDIR *dirp) 576 | { 577 | WIN32_FIND_DATAW *p; 578 | 579 | /* Get next directory entry */ 580 | if (dirp->cached != 0) { 581 | 582 | /* A valid directory entry already in memory */ 583 | p = &dirp->data; 584 | dirp->cached = 0; 585 | 586 | } else if (dirp->handle != INVALID_HANDLE_VALUE) { 587 | 588 | /* Get the next directory entry from stream */ 589 | if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { 590 | /* Got a file */ 591 | p = &dirp->data; 592 | } else { 593 | /* The very last entry has been processed or an error occured */ 594 | FindClose (dirp->handle); 595 | dirp->handle = INVALID_HANDLE_VALUE; 596 | p = NULL; 597 | } 598 | 599 | } else { 600 | 601 | /* End of directory stream reached */ 602 | p = NULL; 603 | 604 | } 605 | 606 | return p; 607 | } 608 | 609 | /* 610 | * Open directory stream using plain old C-string. 611 | */ 612 | static DIR* 613 | opendir( 614 | const char *dirname) 615 | { 616 | struct DIR *dirp; 617 | int error; 618 | 619 | /* Must have directory name */ 620 | if (dirname == NULL || dirname[0] == '\0') { 621 | dirent_set_errno (ENOENT); 622 | return NULL; 623 | } 624 | 625 | /* Allocate memory for DIR structure */ 626 | dirp = (DIR*) malloc (sizeof (struct DIR)); 627 | if (dirp) { 628 | wchar_t wname[PATH_MAX]; 629 | size_t n; 630 | 631 | /* Convert directory name to wide-character string */ 632 | error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX); 633 | if (!error) { 634 | 635 | /* Open directory stream using wide-character name */ 636 | dirp->wdirp = _wopendir (wname); 637 | if (dirp->wdirp) { 638 | /* Directory stream opened */ 639 | error = 0; 640 | } else { 641 | /* Failed to open directory stream */ 642 | error = 1; 643 | } 644 | 645 | } else { 646 | /* 647 | * Cannot convert file name to wide-character string. This 648 | * occurs if the string contains invalid multi-byte sequences or 649 | * the output buffer is too small to contain the resulting 650 | * string. 651 | */ 652 | error = 1; 653 | } 654 | 655 | } else { 656 | /* Cannot allocate DIR structure */ 657 | error = 1; 658 | } 659 | 660 | /* Clean up in case of error */ 661 | if (error && dirp) { 662 | free (dirp); 663 | dirp = NULL; 664 | } 665 | 666 | return dirp; 667 | } 668 | 669 | /* 670 | * Read next directory entry. 671 | * 672 | * When working with text consoles, please note that file names returned by 673 | * readdir() are represented in the default ANSI code page while any output to 674 | * console is typically formatted on another code page. Thus, non-ASCII 675 | * characters in file names will not usually display correctly on console. The 676 | * problem can be fixed in two ways: (1) change the character set of console 677 | * to 1252 using chcp utility and use Lucida Console font, or (2) use 678 | * _cprintf function when writing to console. The _cprinf() will re-encode 679 | * ANSI strings to the console code page so many non-ASCII characters will 680 | * display correcly. 681 | */ 682 | static struct dirent* 683 | readdir( 684 | DIR *dirp) 685 | { 686 | WIN32_FIND_DATAW *datap; 687 | struct dirent *entp; 688 | 689 | /* Read next directory entry */ 690 | datap = dirent_next (dirp->wdirp); 691 | if (datap) { 692 | size_t n; 693 | int error; 694 | 695 | /* Attempt to convert file name to multi-byte string */ 696 | error = dirent_wcstombs_s( 697 | &n, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX); 698 | 699 | /* 700 | * If the file name cannot be represented by a multi-byte string, 701 | * then attempt to use old 8+3 file name. This allows traditional 702 | * Unix-code to access some file names despite of unicode 703 | * characters, although file names may seem unfamiliar to the user. 704 | * 705 | * Be ware that the code below cannot come up with a short file 706 | * name unless the file system provides one. At least 707 | * VirtualBox shared folders fail to do this. 708 | */ 709 | if (error && datap->cAlternateFileName[0] != '\0') { 710 | error = dirent_wcstombs_s( 711 | &n, dirp->ent.d_name, PATH_MAX, 712 | datap->cAlternateFileName, PATH_MAX); 713 | } 714 | 715 | if (!error) { 716 | DWORD attr; 717 | 718 | /* Initialize directory entry for return */ 719 | entp = &dirp->ent; 720 | 721 | /* Length of file name excluding zero terminator */ 722 | entp->d_namlen = n - 1; 723 | 724 | /* File attributes */ 725 | attr = datap->dwFileAttributes; 726 | if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { 727 | entp->d_type = DT_CHR; 728 | } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { 729 | entp->d_type = DT_DIR; 730 | } else { 731 | entp->d_type = DT_REG; 732 | } 733 | 734 | /* Reset dummy fields */ 735 | entp->d_ino = 0; 736 | entp->d_reclen = sizeof (struct dirent); 737 | 738 | } else { 739 | /* 740 | * Cannot convert file name to multi-byte string so construct 741 | * an errornous directory entry and return that. Note that 742 | * we cannot return NULL as that would stop the processing 743 | * of directory entries completely. 744 | */ 745 | entp = &dirp->ent; 746 | entp->d_name[0] = '?'; 747 | entp->d_name[1] = '\0'; 748 | entp->d_namlen = 1; 749 | entp->d_type = DT_UNKNOWN; 750 | entp->d_ino = 0; 751 | entp->d_reclen = 0; 752 | } 753 | 754 | } else { 755 | /* No more directory entries */ 756 | entp = NULL; 757 | } 758 | 759 | return entp; 760 | } 761 | 762 | /* 763 | * Close directory stream. 764 | */ 765 | static int 766 | closedir( 767 | DIR *dirp) 768 | { 769 | int ok; 770 | if (dirp) { 771 | 772 | /* Close wide-character directory stream */ 773 | ok = _wclosedir (dirp->wdirp); 774 | dirp->wdirp = NULL; 775 | 776 | /* Release multi-byte character version */ 777 | free (dirp); 778 | 779 | } else { 780 | 781 | /* Invalid directory stream */ 782 | dirent_set_errno (EBADF); 783 | ok = /*failure*/-1; 784 | 785 | } 786 | return ok; 787 | } 788 | 789 | /* 790 | * Rewind directory stream to beginning. 791 | */ 792 | static void 793 | rewinddir( 794 | DIR* dirp) 795 | { 796 | /* Rewind wide-character string directory stream */ 797 | _wrewinddir (dirp->wdirp); 798 | } 799 | 800 | /* Convert multi-byte string to wide character string */ 801 | static int 802 | dirent_mbstowcs_s( 803 | size_t *pReturnValue, 804 | wchar_t *wcstr, 805 | size_t sizeInWords, 806 | const char *mbstr, 807 | size_t count) 808 | { 809 | int error; 810 | 811 | #if defined(_MSC_VER) && _MSC_VER >= 1400 812 | 813 | /* Microsoft Visual Studio 2005 or later */ 814 | error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); 815 | 816 | #else 817 | 818 | /* Older Visual Studio or non-Microsoft compiler */ 819 | size_t n; 820 | 821 | /* Convert to wide-character string (or count characters) */ 822 | n = mbstowcs (wcstr, mbstr, sizeInWords); 823 | if (!wcstr || n < count) { 824 | 825 | /* Zero-terminate output buffer */ 826 | if (wcstr && sizeInWords) { 827 | if (n >= sizeInWords) { 828 | n = sizeInWords - 1; 829 | } 830 | wcstr[n] = 0; 831 | } 832 | 833 | /* Length of resuting multi-byte string WITH zero terminator */ 834 | if (pReturnValue) { 835 | *pReturnValue = n + 1; 836 | } 837 | 838 | /* Success */ 839 | error = 0; 840 | 841 | } else { 842 | 843 | /* Could not convert string */ 844 | error = 1; 845 | 846 | } 847 | 848 | #endif 849 | 850 | return error; 851 | } 852 | 853 | /* Convert wide-character string to multi-byte string */ 854 | static int 855 | dirent_wcstombs_s( 856 | size_t *pReturnValue, 857 | char *mbstr, 858 | size_t sizeInBytes, /* max size of mbstr */ 859 | const wchar_t *wcstr, 860 | size_t count) 861 | { 862 | int error; 863 | 864 | #if defined(_MSC_VER) && _MSC_VER >= 1400 865 | 866 | /* Microsoft Visual Studio 2005 or later */ 867 | error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); 868 | 869 | #else 870 | 871 | /* Older Visual Studio or non-Microsoft compiler */ 872 | size_t n; 873 | 874 | /* Convert to multi-byte string (or count the number of bytes needed) */ 875 | n = wcstombs (mbstr, wcstr, sizeInBytes); 876 | if (!mbstr || n < count) { 877 | 878 | /* Zero-terminate output buffer */ 879 | if (mbstr && sizeInBytes) { 880 | if (n >= sizeInBytes) { 881 | n = sizeInBytes - 1; 882 | } 883 | mbstr[n] = '\0'; 884 | } 885 | 886 | /* Length of resulting multi-bytes string WITH zero-terminator */ 887 | if (pReturnValue) { 888 | *pReturnValue = n + 1; 889 | } 890 | 891 | /* Success */ 892 | error = 0; 893 | 894 | } else { 895 | 896 | /* Cannot convert string */ 897 | error = 1; 898 | 899 | } 900 | 901 | #endif 902 | 903 | return error; 904 | } 905 | 906 | /* Set errno variable */ 907 | static void 908 | dirent_set_errno( 909 | int error) 910 | { 911 | #if defined(_MSC_VER) && _MSC_VER >= 1400 912 | 913 | /* Microsoft Visual Studio 2005 and later */ 914 | _set_errno (error); 915 | 916 | #else 917 | 918 | /* Non-Microsoft compiler or older Microsoft compiler */ 919 | errno = error; 920 | 921 | #endif 922 | } 923 | 924 | 925 | #ifdef __cplusplus 926 | } 927 | #endif 928 | #endif /*DIRENT_H*/ 929 | 930 | -------------------------------------------------------------------------------- /lib/file.c: -------------------------------------------------------------------------------- 1 | #include "internal.h" 2 | #if USE_OUR_OWN_MD5 3 | #include "md5/global.h" 4 | #include "md5/md5.h" 5 | #else 6 | #include 7 | #endif 8 | #include "cabfile.h" 9 | #include "log.h" 10 | #include 11 | #include 12 | #include 13 | 14 | #if !defined(_MSC_VER) 15 | #include /* for MIN(a,b) */ 16 | #endif 17 | 18 | #ifdef _WIN32 19 | #define fseek _fseeki64 20 | #define ftell _ftelli64 21 | #endif 22 | 23 | #ifndef MIN /* missing in some platforms */ 24 | #define MIN(a,b) (((a)<(b))?(a):(b)) 25 | #endif 26 | 27 | #include 28 | 29 | #define VERBOSE 3 30 | 31 | #define ror8(x,n) (((x) >> ((int)(n))) | ((x) << (8 - (int)(n)))) 32 | #define rol8(x,n) (((x) << ((int)(n))) | ((x) >> (8 - (int)(n)))) 33 | 34 | static const uint8_t END_OF_CHUNK[4] = { 0x00, 0x00, 0xff, 0xff }; 35 | 36 | static FileDescriptor* unshield_read_file_descriptor(Unshield* unshield, int index) 37 | { 38 | /* XXX: multi-volume support... */ 39 | Header* header = unshield->header_list; 40 | uint8_t* p = NULL; 41 | uint8_t* saved_p = NULL; 42 | FileDescriptor* fd = NEW1(FileDescriptor); 43 | 44 | switch (header->major_version) 45 | { 46 | case 0: 47 | case 5: 48 | saved_p = p = header->data + 49 | header->common.cab_descriptor_offset + 50 | header->cab.file_table_offset + 51 | header->file_table[header->cab.directory_count + index]; 52 | 53 | #if VERBOSE 54 | unshield_trace("File descriptor offset %i: %08x", index, p - header->data); 55 | #endif 56 | 57 | fd->volume = header->index; 58 | 59 | fd->name_offset = READ_UINT32(p); p += 4; 60 | fd->directory_index = READ_UINT16(p); p += 2; 61 | assert(fd->directory_index < unshield_directory_count(unshield)); 62 | 63 | p += 2; 64 | fd->flags = READ_UINT16(p); p += 2; 65 | 66 | fd->expanded_size = READ_UINT32(p); p += 4; 67 | fd->compressed_size = READ_UINT32(p); p += 4; 68 | p += 0x14; 69 | fd->data_offset = READ_UINT32(p); p += 4; 70 | 71 | #if VERBOSE >= 2 72 | unshield_trace("Name offset: %08x", fd->name_offset); 73 | unshield_trace("Directory index: %08x", fd->directory_index); 74 | unshield_trace("Flags: %04x", fd->flags); 75 | unshield_trace("Expanded size: %08x", fd->expanded_size); 76 | unshield_trace("Compressed size: %08x", fd->compressed_size); 77 | unshield_trace("Data offset: %08x", fd->data_offset); 78 | #endif 79 | 80 | if (header->major_version == 5) 81 | { 82 | memcpy(fd->md5, p, 0x10); p += 0x10; 83 | assert((p - saved_p) == 0x3a); 84 | } 85 | 86 | break; 87 | 88 | case 6: 89 | case 7: 90 | case 8: 91 | case 9: 92 | case 10: 93 | case 11: 94 | case 12: 95 | case 13: 96 | default: 97 | saved_p = p = header->data + 98 | header->common.cab_descriptor_offset + 99 | header->cab.file_table_offset + 100 | header->cab.file_table_offset2 + 101 | index * 0x57; 102 | 103 | #if VERBOSE 104 | unshield_trace("File descriptor offset: %08x", p - header->data); 105 | #endif 106 | fd->flags = READ_UINT16(p); p += 2; 107 | fd->expanded_size = READ_UINT64(p); p += 8; 108 | fd->compressed_size = READ_UINT64(p); p += 8; 109 | fd->data_offset = READ_UINT64(p); p += 8; 110 | memcpy(fd->md5, p, 0x10); p += 0x10; 111 | p += 0x10; 112 | fd->name_offset = READ_UINT32(p); p += 4; 113 | fd->directory_index = READ_UINT16(p); p += 2; 114 | assert(fd->directory_index < unshield_directory_count(unshield)); 115 | 116 | assert((p - saved_p) == 0x40); 117 | 118 | p += 0xc; 119 | fd->link_previous = READ_UINT32(p); p += 4; 120 | fd->link_next = READ_UINT32(p); p += 4; 121 | fd->link_flags = *p; p ++; 122 | 123 | #if VERBOSE 124 | if (fd->link_flags != LINK_NONE) 125 | { 126 | unshield_trace("Link: previous=%i, next=%i, flags=%i", 127 | fd->link_previous, fd->link_next, fd->link_flags); 128 | } 129 | #endif 130 | 131 | fd->volume = READ_UINT16(p); p += 2; 132 | 133 | assert((p - saved_p) == 0x57); 134 | break; 135 | } 136 | 137 | if (!(fd->flags & FILE_COMPRESSED) && 138 | fd->compressed_size != fd->expanded_size) 139 | { 140 | unshield_warning("File is not compressed but compressed size is %08x and expanded size is %08x", 141 | fd->compressed_size, fd->expanded_size); 142 | } 143 | 144 | return fd; 145 | } 146 | 147 | static FileDescriptor* unshield_get_file_descriptor(Unshield* unshield, int index) 148 | { 149 | /* XXX: multi-volume support... */ 150 | Header* header = unshield->header_list; 151 | 152 | if (index < 0 || index >= (int)header->cab.file_count) 153 | { 154 | unshield_error("Invalid index"); 155 | return NULL; 156 | } 157 | 158 | if (!header->file_descriptors) 159 | header->file_descriptors = calloc(header->cab.file_count, sizeof(FileDescriptor*)); 160 | 161 | if (!header->file_descriptors[index]) 162 | header->file_descriptors[index] = unshield_read_file_descriptor(unshield, index); 163 | 164 | return header->file_descriptors[index]; 165 | } 166 | 167 | int unshield_file_count (Unshield* unshield)/*{{{*/ 168 | { 169 | if (unshield) 170 | { 171 | /* XXX: multi-volume support... */ 172 | Header* header = unshield->header_list; 173 | 174 | return header->cab.file_count; 175 | } 176 | else 177 | return -1; 178 | }/*}}}*/ 179 | 180 | const char* unshield_file_name (Unshield* unshield, int index)/*{{{*/ 181 | { 182 | FileDescriptor* fd = unshield_get_file_descriptor(unshield, index); 183 | 184 | if (fd) 185 | { 186 | /* XXX: multi-volume support... */ 187 | Header* header = unshield->header_list; 188 | 189 | return unshield_get_utf8_string(header, 190 | header->data + 191 | header->common.cab_descriptor_offset + 192 | header->cab.file_table_offset + 193 | fd->name_offset); 194 | } 195 | 196 | unshield_warning("Failed to get file descriptor %i", index); 197 | return NULL; 198 | }/*}}}*/ 199 | 200 | bool unshield_file_is_valid(Unshield* unshield, int index) 201 | { 202 | bool is_valid = false; 203 | FileDescriptor* fd; 204 | 205 | if (index < 0 || index >= unshield_file_count(unshield)) 206 | goto exit; 207 | 208 | if (!(fd = unshield_get_file_descriptor(unshield, index))) 209 | goto exit; 210 | 211 | if (fd->flags & FILE_INVALID) 212 | goto exit; 213 | 214 | if (!fd->name_offset) 215 | goto exit; 216 | 217 | if (!fd->data_offset) 218 | goto exit; 219 | 220 | is_valid = true; 221 | 222 | exit: 223 | return is_valid; 224 | } 225 | 226 | 227 | static int unshield_uncompress (Byte *dest, uLong* destLen, Byte *source, uLong *sourceLen)/*{{{*/ 228 | { 229 | z_stream stream; 230 | int err; 231 | 232 | stream.next_in = source; 233 | stream.avail_in = (uInt)*sourceLen; 234 | 235 | stream.next_out = dest; 236 | stream.avail_out = (uInt)*destLen; 237 | 238 | stream.zalloc = (alloc_func)0; 239 | stream.zfree = (free_func)0; 240 | 241 | /* make second parameter negative to disable checksum verification */ 242 | err = inflateInit2(&stream, -MAX_WBITS); 243 | if (err != Z_OK) return err; 244 | 245 | err = inflate(&stream, Z_FINISH); 246 | if (err != Z_STREAM_END) { 247 | inflateEnd(&stream); 248 | return err; 249 | } 250 | 251 | *destLen = stream.total_out; 252 | *sourceLen = stream.total_in; 253 | 254 | err = inflateEnd(&stream); 255 | return err; 256 | }/*}}}*/ 257 | 258 | static int unshield_uncompress_old(Byte *dest, uLong *destLen, Byte *source, uLong *sourceLen)/*{{{*/ 259 | { 260 | z_stream stream; 261 | int err; 262 | 263 | stream.next_in = source; 264 | stream.avail_in = (uInt)*sourceLen; 265 | 266 | stream.next_out = dest; 267 | stream.avail_out = (uInt)*destLen; 268 | 269 | stream.zalloc = (alloc_func)0; 270 | stream.zfree = (free_func)0; 271 | 272 | *destLen = 0; 273 | *sourceLen = 0; 274 | 275 | /* make second parameter negative to disable checksum verification */ 276 | err = inflateInit2(&stream, -MAX_WBITS); 277 | if (err != Z_OK) 278 | return err; 279 | 280 | while (stream.avail_in > 1) 281 | { 282 | err = inflate(&stream, Z_BLOCK); 283 | if (err != Z_OK) 284 | { 285 | inflateEnd(&stream); 286 | return err; 287 | } 288 | } 289 | 290 | *destLen = stream.total_out; 291 | *sourceLen = stream.total_in; 292 | 293 | err = inflateEnd(&stream); 294 | return err; 295 | }/*}}}*/ 296 | 297 | typedef struct 298 | { 299 | Unshield* unshield; 300 | unsigned index; 301 | FileDescriptor* file_descriptor; 302 | int volume; 303 | FILE* volume_file; 304 | VolumeHeader volume_header; 305 | unsigned volume_bytes_left; 306 | unsigned obfuscation_offset; 307 | } UnshieldReader; 308 | 309 | static bool unshield_reader_open_volume(UnshieldReader* reader, int volume)/*{{{*/ 310 | { 311 | bool success = false; 312 | uint64_t data_offset = 0; 313 | uint64_t volume_bytes_left_compressed; 314 | uint64_t volume_bytes_left_expanded; 315 | CommonHeader common_header; 316 | 317 | #if VERBOSE >= 2 318 | unshield_trace("Open volume %i", volume); 319 | #endif 320 | 321 | FCLOSE(reader->unshield, reader->volume_file); 322 | 323 | reader->volume_file = unshield_fopen_for_reading(reader->unshield, volume, CABINET_SUFFIX); 324 | if (!reader->volume_file) 325 | { 326 | unshield_error("Failed to open input cabinet file %i", volume); 327 | goto exit; 328 | } 329 | 330 | { 331 | uint8_t tmp[COMMON_HEADER_SIZE]; 332 | uint8_t* p = tmp; 333 | 334 | if (COMMON_HEADER_SIZE != unshield_fread(reader->unshield, &tmp, 1, COMMON_HEADER_SIZE, reader->volume_file)) 335 | goto exit; 336 | 337 | if (!unshield_read_common_header(&p, &common_header)) 338 | goto exit; 339 | } 340 | 341 | memset(&reader->volume_header, 0, sizeof(VolumeHeader)); 342 | 343 | switch (reader->unshield->header_list->major_version) 344 | { 345 | case 0: 346 | case 5: 347 | { 348 | uint8_t five_header[VOLUME_HEADER_SIZE_V5]; 349 | uint8_t* p = five_header; 350 | 351 | if (VOLUME_HEADER_SIZE_V5 != 352 | unshield_fread(reader->unshield, &five_header, 1, VOLUME_HEADER_SIZE_V5, reader->volume_file)) 353 | goto exit; 354 | 355 | reader->volume_header.data_offset = READ_UINT32(p); p += 4; 356 | #if VERBOSE 357 | if (READ_UINT32(p)) 358 | unshield_trace("Unknown = %08x", READ_UINT32(p)); 359 | #endif 360 | /* unknown */ p += 4; 361 | reader->volume_header.first_file_index = READ_UINT32(p); p += 4; 362 | reader->volume_header.last_file_index = READ_UINT32(p); p += 4; 363 | reader->volume_header.first_file_offset = READ_UINT32(p); p += 4; 364 | reader->volume_header.first_file_size_expanded = READ_UINT32(p); p += 4; 365 | reader->volume_header.first_file_size_compressed = READ_UINT32(p); p += 4; 366 | reader->volume_header.last_file_offset = READ_UINT32(p); p += 4; 367 | reader->volume_header.last_file_size_expanded = READ_UINT32(p); p += 4; 368 | reader->volume_header.last_file_size_compressed = READ_UINT32(p); p += 4; 369 | 370 | if (reader->volume_header.last_file_offset == 0) 371 | reader->volume_header.last_file_offset = INT32_MAX; 372 | } 373 | break; 374 | 375 | case 6: 376 | case 7: 377 | case 8: 378 | case 9: 379 | case 10: 380 | case 11: 381 | case 12: 382 | case 13: 383 | default: 384 | { 385 | uint8_t six_header[VOLUME_HEADER_SIZE_V6]; 386 | uint8_t* p = six_header; 387 | 388 | if (VOLUME_HEADER_SIZE_V6 != 389 | unshield_fread(reader->unshield, &six_header, 1, VOLUME_HEADER_SIZE_V6, reader->volume_file)) 390 | goto exit; 391 | 392 | reader->volume_header.data_offset = READ_UINT32(p); p += 4; 393 | reader->volume_header.data_offset_high = READ_UINT32(p); p += 4; 394 | reader->volume_header.first_file_index = READ_UINT32(p); p += 4; 395 | reader->volume_header.last_file_index = READ_UINT32(p); p += 4; 396 | reader->volume_header.first_file_offset = READ_UINT32(p); p += 4; 397 | reader->volume_header.first_file_offset_high = READ_UINT32(p); p += 4; 398 | reader->volume_header.first_file_size_expanded = READ_UINT32(p); p += 4; 399 | reader->volume_header.first_file_size_expanded_high = READ_UINT32(p); p += 4; 400 | reader->volume_header.first_file_size_compressed = READ_UINT32(p); p += 4; 401 | reader->volume_header.first_file_size_compressed_high = READ_UINT32(p); p += 4; 402 | reader->volume_header.last_file_offset = READ_UINT32(p); p += 4; 403 | reader->volume_header.last_file_offset_high = READ_UINT32(p); p += 4; 404 | reader->volume_header.last_file_size_expanded = READ_UINT32(p); p += 4; 405 | reader->volume_header.last_file_size_expanded_high = READ_UINT32(p); p += 4; 406 | reader->volume_header.last_file_size_compressed = READ_UINT32(p); p += 4; 407 | reader->volume_header.last_file_size_compressed_high = READ_UINT32(p); p += 4; 408 | } 409 | break; 410 | } 411 | 412 | #if VERBOSE >= 2 413 | unshield_trace("First file index = %i, last file index = %i", 414 | reader->volume_header.first_file_index, reader->volume_header.last_file_index); 415 | unshield_trace("First file offset = %08x, last file offset = %08x", 416 | reader->volume_header.first_file_offset, reader->volume_header.last_file_offset); 417 | #endif 418 | 419 | /* enable support for split archives for IS5 */ 420 | if (reader->unshield->header_list->major_version == 5) 421 | { 422 | if (reader->index < (reader->unshield->header_list->cab.file_count - 1) && 423 | reader->index == reader->volume_header.last_file_index && 424 | reader->volume_header.last_file_size_compressed != reader->file_descriptor->compressed_size) 425 | { 426 | unshield_trace("IS5 split file last in volume"); 427 | reader->file_descriptor->flags |= FILE_SPLIT; 428 | } 429 | else if (reader->index > 0 && 430 | reader->index == reader->volume_header.first_file_index && 431 | reader->volume_header.first_file_size_compressed != reader->file_descriptor->compressed_size) 432 | { 433 | unshield_trace("IS5 split file first in volume"); 434 | reader->file_descriptor->flags |= FILE_SPLIT; 435 | } 436 | } 437 | 438 | if (reader->file_descriptor->flags & FILE_SPLIT) 439 | { 440 | #if VERBOSE 441 | unshield_trace(/*"Total bytes left = 0x08%x, "*/"previous data offset = 0x08%x", 442 | /*total_bytes_left, */data_offset); 443 | #endif 444 | 445 | if (reader->index == reader->volume_header.last_file_index && reader->volume_header.last_file_offset != 0x7FFFFFFF) 446 | { 447 | /* can be first file too... */ 448 | #if VERBOSE 449 | unshield_trace("Index %i is last file in cabinet file %i", 450 | reader->index, volume); 451 | #endif 452 | 453 | data_offset = reader->volume_header.last_file_offset; 454 | volume_bytes_left_expanded = reader->volume_header.last_file_size_expanded; 455 | volume_bytes_left_compressed = reader->volume_header.last_file_size_compressed; 456 | } 457 | else if (reader->index == reader->volume_header.first_file_index) 458 | { 459 | #if VERBOSE 460 | unshield_trace("Index %i is first file in cabinet file %i", 461 | reader->index, volume); 462 | #endif 463 | 464 | data_offset = reader->volume_header.first_file_offset; 465 | volume_bytes_left_expanded = reader->volume_header.first_file_size_expanded; 466 | volume_bytes_left_compressed = reader->volume_header.first_file_size_compressed; 467 | } 468 | else 469 | { 470 | success = true; 471 | goto exit; 472 | } 473 | 474 | #if VERBOSE 475 | unshield_trace("Will read 0x%08x bytes from offset 0x%08x", 476 | volume_bytes_left_compressed, data_offset); 477 | #endif 478 | } 479 | else 480 | { 481 | data_offset = reader->file_descriptor->data_offset; 482 | volume_bytes_left_expanded = reader->file_descriptor->expanded_size; 483 | volume_bytes_left_compressed = reader->file_descriptor->compressed_size; 484 | } 485 | 486 | if (reader->file_descriptor->flags & FILE_COMPRESSED) 487 | reader->volume_bytes_left = volume_bytes_left_compressed; 488 | else 489 | reader->volume_bytes_left = volume_bytes_left_expanded; 490 | 491 | unshield_fseek(reader->unshield, reader->volume_file, data_offset, SEEK_SET); 492 | 493 | reader->volume = volume; 494 | success = true; 495 | 496 | exit: 497 | return success; 498 | }/*}}}*/ 499 | 500 | void unshield_deobfuscate(unsigned char* buffer, size_t size, unsigned* seed) 501 | { 502 | unsigned tmp_seed = *seed; 503 | 504 | for (; size > 0; size--, buffer++, tmp_seed++) 505 | { 506 | *buffer = ror8(*buffer ^ 0xd5, 2) - (tmp_seed % 0x47); 507 | } 508 | 509 | *seed = tmp_seed; 510 | } 511 | 512 | static void unshield_reader_deobfuscate(UnshieldReader* reader, uint8_t* buffer, size_t size) 513 | { 514 | unshield_deobfuscate(buffer, size, &reader->obfuscation_offset); 515 | } 516 | 517 | static bool unshield_reader_read(UnshieldReader* reader, void* buffer, size_t size)/*{{{*/ 518 | { 519 | bool success = false; 520 | uint8_t* p = buffer; 521 | size_t bytes_left = size; 522 | 523 | #if VERBOSE >= 3 524 | unshield_trace("unshield_reader_read start: bytes_left = 0x%x, volume_bytes_left = 0x%x", 525 | bytes_left, reader->volume_bytes_left); 526 | #endif 527 | 528 | for (;;) 529 | { 530 | /* 531 | Read as much as possible from this volume 532 | */ 533 | size_t bytes_to_read = MIN(bytes_left, reader->volume_bytes_left); 534 | 535 | #if VERBOSE >= 3 536 | unshield_trace("Trying to read 0x%x bytes from offset %08x in volume %i", 537 | bytes_to_read, unshield_ftell(reader->unshield, reader->volume_file), reader->volume); 538 | #endif 539 | if (bytes_to_read == 0) 540 | { 541 | unshield_error("bytes_to_read can't be zero"); 542 | goto exit; 543 | } 544 | 545 | if (bytes_to_read != unshield_fread(reader->unshield, p, 1, bytes_to_read, reader->volume_file)) 546 | { 547 | unshield_error("Failed to read 0x%08x bytes of file %i (%s) from volume %i. Current offset = 0x%08x", 548 | bytes_to_read, reader->index, 549 | unshield_file_name(reader->unshield, reader->index), reader->volume, 550 | unshield_ftell(reader->unshield, reader->volume_file)); 551 | goto exit; 552 | } 553 | 554 | bytes_left -= bytes_to_read; 555 | reader->volume_bytes_left -= bytes_to_read; 556 | 557 | #if VERBOSE >= 3 558 | unshield_trace("bytes_left = %i, volume_bytes_left = %i", 559 | bytes_left, reader->volume_bytes_left); 560 | #endif 561 | 562 | if (!bytes_left) 563 | break; 564 | 565 | p += bytes_to_read; 566 | 567 | /* 568 | Open next volume 569 | */ 570 | 571 | if (!unshield_reader_open_volume(reader, reader->volume + 1)) 572 | { 573 | unshield_error("Failed to open volume %i to read %i more bytes", 574 | reader->volume + 1, bytes_to_read); 575 | goto exit; 576 | } 577 | } 578 | 579 | if (reader->file_descriptor->flags & FILE_OBFUSCATED) 580 | unshield_reader_deobfuscate(reader, buffer, size); 581 | 582 | success = true; 583 | 584 | exit: 585 | return success; 586 | }/*}}}*/ 587 | 588 | int copy_file(Unshield* unshield, FILE* infile, FILE* outfile) { 589 | #define SIZE (1024*1024) 590 | 591 | char buffer[SIZE]; 592 | size_t bytes; 593 | 594 | while (0 < (bytes = unshield_fread(unshield, buffer, 1, sizeof(buffer), infile))) 595 | unshield_fwrite(unshield, buffer, 1, bytes, outfile); 596 | 597 | return 0; 598 | } 599 | 600 | 601 | static UnshieldReader* unshield_reader_create_external(/*{{{*/ 602 | Unshield* unshield, 603 | int index, 604 | FileDescriptor* file_descriptor) 605 | { 606 | bool success = false; 607 | const char* file_name = unshield_file_name(unshield, index); 608 | const char* directory_name = unshield_directory_name(unshield, file_descriptor->directory_index); 609 | char* base_directory_name = unshield_get_base_directory_name(unshield); 610 | long int path_max = unshield_get_path_max(unshield); 611 | char* directory_and_filename = malloc(path_max); 612 | 613 | UnshieldReader* reader = NEW1(UnshieldReader); 614 | if (!reader) 615 | goto exit; 616 | 617 | reader->unshield = unshield; 618 | reader->index = index; 619 | reader->file_descriptor = file_descriptor; 620 | 621 | snprintf(directory_and_filename, path_max, "%s/%s/%s", base_directory_name, directory_name, file_name); 622 | 623 | reader->volume_file = fopen(directory_and_filename, "rb"); 624 | if (!reader->volume_file) 625 | { 626 | unshield_error("Failed to open input file %s", directory_and_filename); 627 | goto exit; 628 | } 629 | 630 | if (file_descriptor->flags & FILE_COMPRESSED) { 631 | long file_size = FSIZE(unshield, reader->volume_file); 632 | FILE *temporary_file = NULL; 633 | 634 | /* 635 | * Normally the compressed data is nicely terminated with end of chunk marker 00 00 ff ff but not always 636 | * This seem to happen for small files where the compressed size and expanded size are almost the same. 637 | * Workaround: Create a temporary file with the correct end of chunk. 638 | */ 639 | 640 | long diff = file_descriptor->compressed_size - file_size; 641 | if (diff > 0) { 642 | diff = MIN(sizeof(END_OF_CHUNK), diff); 643 | temporary_file = tmpfile(); 644 | copy_file(reader->unshield, reader->volume_file, temporary_file); 645 | fwrite(END_OF_CHUNK + sizeof(END_OF_CHUNK) - diff, 1, diff, temporary_file); 646 | fseek(temporary_file, 0, SEEK_SET); 647 | 648 | fclose(reader->volume_file); 649 | reader->volume_file = temporary_file; 650 | 651 | reader->volume_bytes_left = file_size + diff; 652 | } 653 | else { 654 | reader->volume_bytes_left = file_descriptor->compressed_size; 655 | } 656 | } 657 | else { 658 | reader->volume_bytes_left = file_descriptor->expanded_size; 659 | } 660 | 661 | success = true; 662 | 663 | exit: 664 | FREE(base_directory_name); 665 | FREE(directory_and_filename); 666 | 667 | if (success) 668 | return reader; 669 | 670 | FREE(reader); 671 | return NULL; 672 | } 673 | 674 | static UnshieldReader* unshield_reader_create(/*{{{*/ 675 | Unshield* unshield, 676 | int index, 677 | FileDescriptor* file_descriptor) 678 | { 679 | bool success = false; 680 | 681 | UnshieldReader* reader = NEW1(UnshieldReader); 682 | if (!reader) 683 | return NULL; 684 | 685 | reader->unshield = unshield; 686 | reader->index = index; 687 | reader->file_descriptor = file_descriptor; 688 | 689 | for (;;) 690 | { 691 | if (!unshield_reader_open_volume(reader, file_descriptor->volume)) 692 | { 693 | unshield_error("Failed to open volume %i", 694 | file_descriptor->volume); 695 | goto exit; 696 | } 697 | 698 | /* Start with the correct volume for IS5 cabinets */ 699 | if (reader->unshield->header_list->major_version <= 5 && 700 | index > (int)reader->volume_header.last_file_index) 701 | { 702 | unshield_trace("Trying next volume..."); 703 | file_descriptor->volume++; 704 | continue; 705 | } 706 | 707 | break; 708 | }; 709 | 710 | success = true; 711 | 712 | exit: 713 | if (success) 714 | return reader; 715 | 716 | FREE(reader); 717 | return NULL; 718 | }/*}}}*/ 719 | 720 | static void unshield_reader_destroy(UnshieldReader* reader)/*{{{*/ 721 | { 722 | if (reader) 723 | { 724 | FCLOSE(reader->unshield, reader->volume_file); 725 | free(reader); 726 | } 727 | }/*}}}*/ 728 | 729 | #define BUFFER_SIZE (64*1024) 730 | 731 | /* 732 | * If filename is NULL, just throw away the result 733 | */ 734 | bool unshield_file_save (Unshield* unshield, int index, const char* filename)/*{{{*/ 735 | { 736 | bool success = false; 737 | FILE* output = NULL; 738 | unsigned char* input_buffer = (unsigned char*)malloc(BUFFER_SIZE+1); 739 | unsigned char* output_buffer = (unsigned char*)malloc(BUFFER_SIZE); 740 | unsigned int bytes_left; 741 | uLong total_written = 0; 742 | UnshieldReader* reader = NULL; 743 | FileDescriptor* file_descriptor; 744 | 745 | #if USE_OUR_OWN_MD5 746 | MD5_CTX md5; 747 | MD5Init(&md5); 748 | #else 749 | EVP_MD_CTX *md5 = EVP_MD_CTX_new(); 750 | EVP_MD_CTX_init(md5); 751 | EVP_DigestInit_ex(md5, EVP_md5(), NULL); 752 | #endif 753 | 754 | if (!unshield) 755 | goto exit; 756 | 757 | if (!(file_descriptor = unshield_get_file_descriptor(unshield, index))) 758 | { 759 | unshield_error("Failed to get file descriptor for file %i", index); 760 | goto exit; 761 | } 762 | 763 | if ((file_descriptor->flags & FILE_INVALID) || 0 == file_descriptor->data_offset) 764 | { 765 | /* invalid file */ 766 | goto exit; 767 | } 768 | 769 | if (file_descriptor->link_flags & LINK_PREV) 770 | { 771 | success = unshield_file_save(unshield, file_descriptor->link_previous, filename); 772 | goto exit; 773 | } 774 | 775 | reader = unshield_reader_create(unshield, index, file_descriptor); 776 | if (!reader) 777 | { 778 | unshield_error("Failed to create data reader for file %i", index); 779 | goto exit; 780 | } 781 | 782 | if (unshield_fsize(unshield, reader->volume_file) == (long)file_descriptor->data_offset) 783 | { 784 | unshield_error("File %i is not inside the cabinet.", index); 785 | goto exit; 786 | } 787 | 788 | if (filename) 789 | { 790 | output = unshield_fopen(unshield, filename, "wb"); 791 | if (!output) 792 | { 793 | unshield_error("Failed to open output file '%s'", filename); 794 | goto exit; 795 | } 796 | } 797 | 798 | if (file_descriptor->flags & FILE_COMPRESSED) 799 | bytes_left = file_descriptor->compressed_size; 800 | else 801 | bytes_left = file_descriptor->expanded_size; 802 | 803 | /*unshield_trace("Bytes to read: %i", bytes_left);*/ 804 | 805 | while (bytes_left > 0) 806 | { 807 | uLong bytes_to_write = BUFFER_SIZE; 808 | int result; 809 | 810 | if (file_descriptor->flags & FILE_COMPRESSED) 811 | { 812 | uLong read_bytes; 813 | uint16_t bytes_to_read = 0; 814 | uint8_t bytes_to_read_bytes[2]; 815 | 816 | if (!unshield_reader_read(reader, bytes_to_read_bytes, sizeof(bytes_to_read_bytes))) 817 | { 818 | unshield_error("Failed to read %i bytes of file %i (%s) from input cabinet file %i", 819 | sizeof(bytes_to_read_bytes), index, unshield_file_name(unshield, index), file_descriptor->volume); 820 | goto exit; 821 | } 822 | 823 | bytes_to_read = READ_UINT16(bytes_to_read_bytes); 824 | if (bytes_to_read == 0) 825 | { 826 | unshield_error("bytes_to_read can't be zero"); 827 | unshield_error("HINT: Try unshield_file_save_old() or -O command line parameter!"); 828 | goto exit; 829 | } 830 | 831 | if (!unshield_reader_read(reader, input_buffer, bytes_to_read)) 832 | { 833 | #if VERBOSE 834 | unshield_error("Failed to read %i bytes of file %i (%s) from input cabinet file %i", 835 | bytes_to_read, index, unshield_file_name(unshield, index), file_descriptor->volume); 836 | #endif 837 | goto exit; 838 | } 839 | 840 | /* add a null byte to make inflate happy */ 841 | input_buffer[bytes_to_read] = 0; 842 | read_bytes = bytes_to_read+1; 843 | result = unshield_uncompress(output_buffer, &bytes_to_write, input_buffer, &read_bytes); 844 | 845 | if (Z_OK != result) 846 | { 847 | unshield_error("Decompression failed with code %i. bytes_to_read=%i, volume_bytes_left=%i, volume=%i, read_bytes=%i", 848 | result, bytes_to_read, reader->volume_bytes_left, file_descriptor->volume, read_bytes); 849 | if (result == Z_DATA_ERROR) 850 | { 851 | unshield_error("HINT: Try unshield_file_save_old() or -O command line parameter!"); 852 | } 853 | goto exit; 854 | } 855 | 856 | #if VERBOSE >= 3 857 | unshield_trace("read_bytes = %i", 858 | read_bytes); 859 | #endif 860 | 861 | bytes_left -= 2; 862 | bytes_left -= bytes_to_read; 863 | } 864 | else 865 | { 866 | bytes_to_write = MIN(bytes_left, BUFFER_SIZE); 867 | 868 | if (!unshield_reader_read(reader, output_buffer, bytes_to_write)) 869 | { 870 | #if VERBOSE 871 | unshield_error("Failed to read %i bytes from input cabinet file %i", 872 | bytes_to_write, file_descriptor->volume); 873 | #endif 874 | goto exit; 875 | } 876 | 877 | bytes_left -= bytes_to_write; 878 | } 879 | 880 | #if USE_OUR_OWN_MD5 881 | MD5Update(&md5, output_buffer, bytes_to_write); 882 | #else 883 | EVP_DigestUpdate(md5, output_buffer, bytes_to_write); 884 | #endif 885 | 886 | if (output) 887 | { 888 | if (bytes_to_write != unshield_fwrite(unshield, output_buffer, 1, bytes_to_write, output)) 889 | { 890 | unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); 891 | goto exit; 892 | } 893 | } 894 | 895 | total_written += bytes_to_write; 896 | } 897 | 898 | if (file_descriptor->expanded_size != total_written) 899 | { 900 | unshield_error("Expanded size expected to be %i, but was %i", 901 | file_descriptor->expanded_size, total_written); 902 | goto exit; 903 | } 904 | 905 | if (unshield->header_list->major_version >= 6) 906 | { 907 | unsigned char md5result[16]; 908 | #if USE_OUR_OWN_MD5 909 | MD5Final(md5result, &md5); 910 | #else 911 | EVP_DigestFinal_ex(md5, md5result, NULL); 912 | #endif 913 | 914 | if (0 != memcmp(md5result, file_descriptor->md5, 16)) 915 | { 916 | unshield_error("MD5 checksum failure for file %i (%s)", 917 | index, unshield_file_name(unshield, index)); 918 | goto exit; 919 | } 920 | } 921 | 922 | success = true; 923 | 924 | exit: 925 | #ifndef USE_OUR_OWN_MD5 926 | EVP_MD_CTX_free(md5); 927 | md5 = NULL; 928 | #endif 929 | unshield_reader_destroy(reader); 930 | FCLOSE(unshield, output); 931 | FREE(input_buffer); 932 | FREE(output_buffer); 933 | return success; 934 | }/*}}}*/ 935 | 936 | int unshield_file_directory(Unshield* unshield, int index)/*{{{*/ 937 | { 938 | FileDescriptor* fd = unshield_get_file_descriptor(unshield, index); 939 | if (fd) 940 | { 941 | assert(fd->directory_index < unshield_directory_count(unshield)); 942 | return fd->directory_index; 943 | } 944 | else 945 | return -1; 946 | }/*}}}*/ 947 | 948 | size_t unshield_file_size(Unshield* unshield, int index)/*{{{*/ 949 | { 950 | FileDescriptor* fd = unshield_get_file_descriptor(unshield, index); 951 | if (fd) 952 | { 953 | return fd->expanded_size; 954 | } 955 | else 956 | return 0; 957 | }/*}}}*/ 958 | 959 | bool unshield_file_save_raw(Unshield* unshield, int index, const char* filename) 960 | { 961 | /* XXX: Thou Shalt Not Cut & Paste... */ 962 | bool success = false; 963 | FILE* output = NULL; 964 | unsigned char* input_buffer = (unsigned char*)malloc(BUFFER_SIZE); 965 | unsigned char* output_buffer = (unsigned char*)malloc(BUFFER_SIZE); 966 | unsigned int bytes_left; 967 | UnshieldReader* reader = NULL; 968 | FileDescriptor* file_descriptor; 969 | 970 | if (!unshield) 971 | goto exit; 972 | 973 | if (!(file_descriptor = unshield_get_file_descriptor(unshield, index))) 974 | { 975 | unshield_error("Failed to get file descriptor for file %i", index); 976 | goto exit; 977 | } 978 | 979 | if ((file_descriptor->flags & FILE_INVALID) || 0 == file_descriptor->data_offset) 980 | { 981 | /* invalid file */ 982 | goto exit; 983 | } 984 | 985 | if (file_descriptor->link_flags & LINK_PREV) 986 | { 987 | success = unshield_file_save_raw(unshield, file_descriptor->link_previous, filename); 988 | goto exit; 989 | } 990 | 991 | reader = unshield_reader_create(unshield, index, file_descriptor); 992 | if (!reader) 993 | { 994 | unshield_error("Failed to create data reader for file %i", index); 995 | goto exit; 996 | } 997 | 998 | if (unshield_fsize(unshield, reader->volume_file) == (long)file_descriptor->data_offset) 999 | { 1000 | unshield_error("File %i is not inside the cabinet.", index); 1001 | goto exit; 1002 | } 1003 | 1004 | if (filename) 1005 | { 1006 | output = unshield_fopen(unshield, filename, "wb"); 1007 | if (!output) 1008 | { 1009 | unshield_error("Failed to open output file '%s'", filename); 1010 | goto exit; 1011 | } 1012 | } 1013 | 1014 | if (file_descriptor->flags & FILE_COMPRESSED) 1015 | bytes_left = file_descriptor->compressed_size; 1016 | else 1017 | bytes_left = file_descriptor->expanded_size; 1018 | 1019 | /*unshield_trace("Bytes to read: %i", bytes_left);*/ 1020 | 1021 | while (bytes_left > 0) 1022 | { 1023 | uLong bytes_to_write = MIN(bytes_left, BUFFER_SIZE); 1024 | 1025 | if (!unshield_reader_read(reader, output_buffer, bytes_to_write)) 1026 | { 1027 | #if VERBOSE 1028 | unshield_error("Failed to read %i bytes from input cabinet file %i", 1029 | bytes_to_write, file_descriptor->volume); 1030 | #endif 1031 | goto exit; 1032 | } 1033 | 1034 | bytes_left -= bytes_to_write; 1035 | 1036 | if (output) { 1037 | if (bytes_to_write != unshield_fwrite(unshield, output_buffer, 1, bytes_to_write, output)) { 1038 | unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); 1039 | goto exit; 1040 | } 1041 | } 1042 | } 1043 | 1044 | success = true; 1045 | 1046 | exit: 1047 | unshield_reader_destroy(reader); 1048 | FCLOSE(unshield, output); 1049 | FREE(input_buffer); 1050 | FREE(output_buffer); 1051 | return success; 1052 | } 1053 | 1054 | static uint8_t* find_bytes( 1055 | const uint8_t* buffer, size_t bufferSize, 1056 | const uint8_t* pattern, size_t patternSize) 1057 | { 1058 | const unsigned char *p = buffer; 1059 | size_t buffer_left = bufferSize; 1060 | while ((p = memchr(p, pattern[0], buffer_left)) != NULL) 1061 | { 1062 | if (patternSize > buffer_left) 1063 | break; 1064 | 1065 | if (memcmp(p, pattern, patternSize) == 0) 1066 | return (uint8_t*)p; 1067 | 1068 | ++p; 1069 | --buffer_left; 1070 | } 1071 | 1072 | return NULL; 1073 | } 1074 | 1075 | bool unshield_file_save_old(Unshield* unshield, int index, const char* filename)/*{{{*/ 1076 | { 1077 | /* XXX: Thou Shalt Not Cut & Paste... */ 1078 | bool success = false; 1079 | FILE* output = NULL; 1080 | size_t input_buffer_size = BUFFER_SIZE; 1081 | unsigned char* input_buffer = (unsigned char*)malloc(BUFFER_SIZE); 1082 | unsigned char* output_buffer = (unsigned char*)malloc(BUFFER_SIZE); 1083 | unsigned int bytes_left; 1084 | uLong total_written = 0; 1085 | UnshieldReader* reader = NULL; 1086 | FileDescriptor* file_descriptor; 1087 | 1088 | if (!unshield) 1089 | goto exit; 1090 | 1091 | if (!(file_descriptor = unshield_get_file_descriptor(unshield, index))) 1092 | { 1093 | unshield_error("Failed to get file descriptor for file %i", index); 1094 | goto exit; 1095 | } 1096 | 1097 | if ((file_descriptor->flags & FILE_INVALID) || 0 == file_descriptor->data_offset) 1098 | { 1099 | /* invalid file */ 1100 | goto exit; 1101 | } 1102 | 1103 | if (file_descriptor->link_flags & LINK_PREV) 1104 | { 1105 | success = unshield_file_save_old(unshield, file_descriptor->link_previous, filename); 1106 | goto exit; 1107 | } 1108 | 1109 | reader = unshield_reader_create(unshield, index, file_descriptor); 1110 | if (!reader) 1111 | { 1112 | unshield_error("Failed to create data reader for file %i", index); 1113 | goto exit; 1114 | } 1115 | 1116 | if (unshield_fsize(unshield, reader->volume_file) == (long)file_descriptor->data_offset) 1117 | { 1118 | unshield_error("File %i is not inside the cabinet. Trying external file!", index); 1119 | unshield_reader_destroy(reader); 1120 | reader = unshield_reader_create_external(unshield, index, file_descriptor); 1121 | if (!reader) 1122 | { 1123 | unshield_error("Failed to create data reader for file %i", index); 1124 | goto exit; 1125 | } 1126 | } 1127 | 1128 | if (filename) 1129 | { 1130 | output = unshield_fopen(unshield, filename, "wb"); 1131 | if (!output) 1132 | { 1133 | unshield_error("Failed to open output file '%s'", filename); 1134 | goto exit; 1135 | } 1136 | } 1137 | 1138 | bytes_left = file_descriptor->expanded_size; 1139 | 1140 | #if VERBOSE >= 4 1141 | unshield_trace("Bytes to write: %i", bytes_left); 1142 | #endif 1143 | 1144 | while (bytes_left > 0) 1145 | { 1146 | uLong bytes_to_write = 0; 1147 | int result; 1148 | 1149 | if (reader->volume_bytes_left == 0 && !unshield_reader_open_volume(reader, reader->volume + 1)) 1150 | { 1151 | unshield_error("Failed to open volume %i to read %i more bytes", 1152 | reader->volume + 1, bytes_left); 1153 | goto exit; 1154 | } 1155 | 1156 | if (file_descriptor->flags & FILE_COMPRESSED) 1157 | { 1158 | uLong read_bytes; 1159 | size_t input_size = reader->volume_bytes_left; 1160 | uint8_t* chunk_buffer; 1161 | 1162 | while (input_size > input_buffer_size) 1163 | { 1164 | input_buffer_size *= 2; 1165 | #if VERBOSE >= 3 1166 | unshield_trace("increased input_buffer_size to 0x%x", input_buffer_size); 1167 | #endif 1168 | 1169 | input_buffer = realloc(input_buffer, input_buffer_size); 1170 | assert(input_buffer); 1171 | } 1172 | 1173 | if (!unshield_reader_read(reader, input_buffer, input_size)) 1174 | { 1175 | #if VERBOSE 1176 | unshield_error("Failed to read 0x%x bytes of file %i (%s) from input cabinet file %i", 1177 | input_size, index, unshield_file_name(unshield, index), file_descriptor->volume); 1178 | #endif 1179 | goto exit; 1180 | } 1181 | 1182 | for (chunk_buffer = input_buffer; input_size && bytes_left; ) 1183 | { 1184 | size_t chunk_size; 1185 | uint8_t* match = find_bytes(chunk_buffer, input_size, END_OF_CHUNK, sizeof(END_OF_CHUNK)); 1186 | if (!match) 1187 | { 1188 | unshield_error("Could not find end of chunk for file %i (%s) from input cabinet file %i", 1189 | index, unshield_file_name(unshield, index), file_descriptor->volume); 1190 | goto exit; 1191 | } 1192 | 1193 | chunk_size = match - chunk_buffer; 1194 | 1195 | /* 1196 | Detect when the chunk actually contains the end of chunk marker. 1197 | 1198 | Needed by Qtime.smk from "The Feeble Files - spanish version". 1199 | 1200 | The first bit of a compressed block is always zero, so we apply this 1201 | workaround if it's a one. 1202 | 1203 | A possibly more proper fix for this would be to have 1204 | unshield_uncompress_old eat compressed data and discard chunk 1205 | markers inbetween. 1206 | */ 1207 | while ((chunk_size + sizeof(END_OF_CHUNK)) < input_size && 1208 | chunk_buffer[chunk_size + sizeof(END_OF_CHUNK)] & 1) 1209 | { 1210 | unshield_warning("It seems like we have an end of chunk marker inside of a chunk."); 1211 | chunk_size += sizeof(END_OF_CHUNK); 1212 | match = find_bytes(chunk_buffer + chunk_size, input_size - chunk_size, END_OF_CHUNK, sizeof(END_OF_CHUNK)); 1213 | if (!match) 1214 | { 1215 | unshield_error("Could not find end of chunk for file %i (%s) from input cabinet file %i", 1216 | index, unshield_file_name(unshield, index), file_descriptor->volume); 1217 | goto exit; 1218 | } 1219 | chunk_size = match - chunk_buffer; 1220 | } 1221 | 1222 | #if VERBOSE >= 3 1223 | unshield_trace("chunk_size = 0x%x", chunk_size); 1224 | #endif 1225 | 1226 | /* add a null byte to make inflate happy */ 1227 | chunk_buffer[chunk_size] = 0; 1228 | 1229 | bytes_to_write = BUFFER_SIZE; 1230 | read_bytes = chunk_size; 1231 | result = unshield_uncompress_old(output_buffer, &bytes_to_write, chunk_buffer, &read_bytes); 1232 | 1233 | if (Z_OK != result) 1234 | { 1235 | unshield_error("Decompression failed with code %i. input_size=%i, volume_bytes_left=%i, volume=%i, read_bytes=%i", 1236 | result, input_size, reader->volume_bytes_left, file_descriptor->volume, read_bytes); 1237 | goto exit; 1238 | } 1239 | 1240 | #if VERBOSE >= 3 1241 | unshield_trace("read_bytes = 0x%x", read_bytes); 1242 | #endif 1243 | 1244 | chunk_buffer += chunk_size; 1245 | chunk_buffer += sizeof(END_OF_CHUNK); 1246 | 1247 | input_size -= chunk_size; 1248 | input_size -= sizeof(END_OF_CHUNK); 1249 | 1250 | bytes_left -= bytes_to_write; 1251 | 1252 | if (output) { 1253 | if (bytes_to_write != unshield_fwrite(unshield, output_buffer, 1, bytes_to_write, output)) { 1254 | unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); 1255 | goto exit; 1256 | } 1257 | } 1258 | 1259 | total_written += bytes_to_write; 1260 | } 1261 | } 1262 | else 1263 | { 1264 | bytes_to_write = MIN(bytes_left, BUFFER_SIZE); 1265 | 1266 | if (!unshield_reader_read(reader, output_buffer, bytes_to_write)) 1267 | { 1268 | #if VERBOSE 1269 | unshield_error("Failed to read %i bytes from input cabinet file %i", 1270 | bytes_to_write, file_descriptor->volume); 1271 | #endif 1272 | goto exit; 1273 | } 1274 | 1275 | bytes_left -= bytes_to_write; 1276 | 1277 | if (output) { 1278 | if (bytes_to_write != unshield_fwrite(unshield, output_buffer, 1, bytes_to_write, output)) 1279 | { 1280 | unshield_error("Failed to write %i bytes to file '%s'", bytes_to_write, filename); 1281 | goto exit; 1282 | } 1283 | } 1284 | 1285 | total_written += bytes_to_write; 1286 | 1287 | } 1288 | } 1289 | 1290 | if (file_descriptor->expanded_size != total_written) 1291 | { 1292 | unshield_error("Expanded size expected to be %i, but was %i", 1293 | file_descriptor->expanded_size, total_written); 1294 | goto exit; 1295 | } 1296 | 1297 | success = true; 1298 | 1299 | exit: 1300 | unshield_reader_destroy(reader); 1301 | FCLOSE(unshield, output); 1302 | FREE(input_buffer); 1303 | FREE(output_buffer); 1304 | return success; 1305 | }/*}}}*/ 1306 | 1307 | 1308 | --------------------------------------------------------------------------------