From 734cb2ff3172a0364522be8425610cf1fa0fd6f0 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Tue, 1 Dec 2020 11:02:40 +0000 Subject: [PATCH] (libretro-common) Add command line tool for creating/extracting RZIP files --- .gitignore | 3 + libretro-common/samples/streams/rzip/Makefile | 51 +++ libretro-common/samples/streams/rzip/rzip.c | 363 ++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 libretro-common/samples/streams/rzip/Makefile create mode 100644 libretro-common/samples/streams/rzip/rzip.c diff --git a/.gitignore b/.gitignore index 3eec7273c8..c721e5a0f8 100644 --- a/.gitignore +++ b/.gitignore @@ -207,5 +207,8 @@ gfx/common/wayland/xdg-decoration-unstable-v1.c gfx/common/wayland/xdg-shell.c gfx/common/wayland/xdg-shell.h +# libretro-common samples +libretro-common/samples/streams/rzip/rzip + # DINGUX *.opk diff --git a/libretro-common/samples/streams/rzip/Makefile b/libretro-common/samples/streams/rzip/Makefile new file mode 100644 index 0000000000..c7d2b3dd5b --- /dev/null +++ b/libretro-common/samples/streams/rzip/Makefile @@ -0,0 +1,51 @@ +TARGET := rzip + +LIBRETRO_COMM_DIR := ../../.. + +LDFLAGS += -lz + +SOURCES := \ + rzip.c \ + $(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \ + $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ + $(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \ + $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \ + $(LIBRETRO_COMM_DIR)/file/file_path.c \ + $(LIBRETRO_COMM_DIR)/file/file_path_io.c \ + $(LIBRETRO_COMM_DIR)/string/stdstring.c \ + $(LIBRETRO_COMM_DIR)/streams/file_stream.c \ + $(LIBRETRO_COMM_DIR)/streams/file_stream_transforms.c \ + $(LIBRETRO_COMM_DIR)/streams/interface_stream.c \ + $(LIBRETRO_COMM_DIR)/streams/memory_stream.c \ + $(LIBRETRO_COMM_DIR)/streams/rzip_stream.c \ + $(LIBRETRO_COMM_DIR)/streams/stdin_stream.c \ + $(LIBRETRO_COMM_DIR)/streams/trans_stream.c \ + $(LIBRETRO_COMM_DIR)/streams/trans_stream_pipe.c \ + $(LIBRETRO_COMM_DIR)/streams/trans_stream_zlib.c \ + $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c + +OBJS := $(SOURCES:.c=.o) + +CFLAGS += -DHAVE_ZLIB -Wall -pedantic -std=gnu99 -I$(LIBRETRO_COMM_DIR)/include + +ifeq ($(DEBUG), 1) + CFLAGS += -O0 -g -DDEBUG -D_DEBUG +else + CFLAGS += -O2 -DNDEBUG +endif + +all: $(TARGET) + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) + +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) + +clean: + rm -f $(TARGET) $(OBJS) + +.PHONY: clean diff --git a/libretro-common/samples/streams/rzip/rzip.c b/libretro-common/samples/streams/rzip/rzip.c new file mode 100644 index 0000000000..93904a77f4 --- /dev/null +++ b/libretro-common/samples/streams/rzip/rzip.c @@ -0,0 +1,363 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (config_file_test.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define FILE_TRANSFER_CHUNK_SIZE 4096 + +enum rzip_action_type +{ + RZIP_ACTION_QUERY = 0, + RZIP_ACTION_COMPRESS, + RZIP_ACTION_EXTRACT +}; + +static void rand_str(char *dst, size_t len) +{ + char charset[] = "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + while (len-- > 0) + { + size_t i = (double)rand() / RAND_MAX * (sizeof(charset) - 1); + *dst++ = charset[i]; + } + *dst = '\0'; +} + +int main(int argc, char *argv[]) +{ + char in_file_path[PATH_MAX_LENGTH]; + char out_file_path[PATH_MAX_LENGTH]; + enum rzip_action_type action = RZIP_ACTION_QUERY; + intfstream_t *in_file = NULL; + intfstream_t *out_file = NULL; + int64_t in_file_size = 0; + int64_t in_file_raw_size = 0; + int64_t out_file_size = 0; + int64_t file_size_diff = 0; + int64_t total_data_read = 0; + bool in_file_compressed = false; + bool valid_args = false; + bool in_place = false; + int ret = 1; + + in_file_path[0] = '\0'; + out_file_path[0] = '\0'; + + /* Parse arguments */ + if ((argc > 1) && !string_is_empty(argv[1])) + { + valid_args = true; + + if (string_is_equal(argv[1], "i")) + action = RZIP_ACTION_QUERY; + else if (string_is_equal(argv[1], "a")) + action = RZIP_ACTION_COMPRESS; + else if (string_is_equal(argv[1], "x")) + action = RZIP_ACTION_EXTRACT; + else + valid_args = false; + } + + /* Get input file path */ + if (valid_args && (argc > 2) && !string_is_empty(argv[2])) + { + strlcpy(in_file_path, argv[2], sizeof(in_file_path)); + path_resolve_realpath(in_file_path, sizeof(in_file_path), true); + valid_args = valid_args && !string_is_empty(in_file_path); + } + else + valid_args = false; + + /* Ensure arguments are valid */ + if (!valid_args) + { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "- Query file status: %s i \n", argv[0]); + fprintf(stderr, "- Compress file: %s a \n", argv[0]); + fprintf(stderr, "- Extract file: %s x \n", argv[0]); + fprintf(stderr, "Omitting will overwrite \n"); + goto end; + } + + /* Ensure that input file exists */ + if (!path_is_valid(in_file_path)) + { + fprintf(stderr, "ERROR: Input file does not exist: %s\n", in_file_path); + goto end; + } + + /* Get output file path, if specified */ + if ((argc > 3) && !string_is_empty(argv[3])) + { + strlcpy(out_file_path, argv[3], sizeof(out_file_path)); + path_resolve_realpath(out_file_path, sizeof(out_file_path), true); + } + + /* If we are compressing/extracting and an + * output file was not specified, generate a + * temporary output file path */ + if ((action != RZIP_ACTION_QUERY) && + string_is_empty(out_file_path)) + { + const char *in_file_name = path_basename(in_file_path); + char in_file_dir[PATH_MAX_LENGTH]; + + in_file_dir[0] = '\0'; + + fill_pathname_parent_dir(in_file_dir, in_file_path, sizeof(in_file_dir)); + + if (string_is_empty(in_file_name)) + { + fprintf(stderr, "ERROR: Invalid input file: %s\n", in_file_path); + goto end; + } + + srand((unsigned int)time(NULL)); + + for (;;) + { + char tmp_str[10] = {0}; + + /* Generate 'random' file name */ + rand_str(tmp_str, sizeof(tmp_str) - 1); + tmp_str[0] = '.'; + + out_file_path[0] = '\0'; + + if (!string_is_empty(in_file_dir)) + fill_pathname_join(out_file_path, in_file_dir, + tmp_str, sizeof(out_file_path)); + else + strlcpy(out_file_path, tmp_str, sizeof(out_file_path)); + + strlcat(out_file_path, ".", sizeof(out_file_path)); + strlcat(out_file_path, in_file_name, sizeof(out_file_path)); + path_resolve_realpath(out_file_path, sizeof(out_file_path), true); + + if (!path_is_valid(out_file_path)) + break; + } + + in_place = true; + } + + /* Ensure that input and output files + * are different */ + if (string_is_equal(in_file_path, out_file_path)) + { + fprintf(stderr, "ERROR: Input and output are the same file: %s\n", in_file_path); + goto end; + } + + /* Get input file size */ + in_file_size = (int64_t)path_get_size(in_file_path); + + if (in_file_size < 1) + { + fprintf(stderr, "ERROR: Input file is empty: %s\n", in_file_path); + goto end; + } + + /* Open input file + * > Always use RZIP interface */ + in_file = intfstream_open_rzip_file( + in_file_path, RETRO_VFS_FILE_ACCESS_READ); + + if (!in_file) + { + fprintf(stderr, "ERROR: Failed to open input file: %s\n", in_file_path); + goto end; + } + + /* Get input file compression status */ + in_file_compressed = intfstream_is_compressed(in_file); + + /* Get raw (uncompressed) input file size */ + in_file_raw_size = intfstream_get_size(in_file); + + /* If this is a query operation, just + * print current state */ + if (action == RZIP_ACTION_QUERY) + { + printf("%s: %s\n", + in_file_compressed ? "File is in RZIP format" : "File is NOT in RZIP format", + in_file_path); + printf(" Size on disk: %li bytes\n", in_file_size); + if (in_file_compressed) + printf(" Uncompressed size: %li bytes\n", in_file_raw_size); + goto end; + } + + /* Check whether file is already in the + * requested state */ + if ((in_file_compressed && (action == RZIP_ACTION_COMPRESS)) || + (!in_file_compressed && (action == RZIP_ACTION_EXTRACT))) + { + printf("Input file is %s: %s\n", + in_file_compressed ? + "already in RZIP format - cannot compress" : + "not in RZIP format - cannot extract", + in_file_path); + goto end; + } + + /* Check whether output file already exists */ + if (path_is_valid(out_file_path)) + { + char reply[8]; + + reply[0] = '\0'; + + printf("WARNING: Output file already exists: %s\n", out_file_path); + printf(" Overwrite? [Y/n]: "); + fgets(reply, sizeof(reply), stdin); + if (reply[0] != 'Y') + goto end; + } + + /* Open output file */ + if (in_file_compressed) + out_file = intfstream_open_file( + out_file_path, RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + else + out_file = intfstream_open_rzip_file( + out_file_path, RETRO_VFS_FILE_ACCESS_WRITE); + + if (!out_file) + { + fprintf(stderr, "ERROR: Failed to open output file: %s\n", out_file_path); + goto end; + } + + /* Start file transfer */ + printf("%s file\n", in_file_compressed ? "Extracting" : "Compressing"); + printf(" From: %s\n", in_file_path); + printf(" To: %s\n", in_place ? in_file_path : out_file_path); + + for (;;) + { + int64_t data_written = 0; + uint8_t buffer[FILE_TRANSFER_CHUNK_SIZE]; + /* Read a single chunk from input file */ + int64_t data_read = intfstream_read( + in_file, buffer, sizeof(buffer)); + + if (data_read < 0) + { + fprintf(stderr, "ERROR: Failed to read from input file: %s\n", in_file_path); + goto end; + } + + total_data_read += data_read; + + /* Check whether we have reached the end of the file */ + if (data_read == 0) + { + /* Close files */ + intfstream_flush(out_file); + intfstream_close(out_file); + free(out_file); + out_file = NULL; + + intfstream_close(in_file); + free(in_file); + in_file = NULL; + + break; + } + + /* Write chunk to backup file */ + data_written = intfstream_write(out_file, buffer, data_read); + + if (data_written != data_read) + { + fprintf(stderr, "ERROR: Failed to write to output file: %s\n", out_file_path); + goto end; + } + + /* Update progress */ + printf("\rProgress: %li %%", total_data_read * 100 / in_file_raw_size); + fflush(stdout); + } + printf("\rProgress: 100 %%\n"); + + /* Display final status 'report' */ + printf("%s complete:\n", in_file_compressed ? "Extraction" : "Compression"); + + out_file_size = (int64_t)path_get_size(out_file_path); + file_size_diff = (in_file_size > out_file_size) ? + (in_file_size - out_file_size) : + (out_file_size - in_file_size); + + printf(" %li -> %li bytes [%li %% %s]\n", + in_file_size, out_file_size, + file_size_diff * 100 / in_file_size, + (out_file_size >= in_file_size) ? + "increase" : "decrease"); + + /* If this was an in-place operation, + * replace input file with output file */ + if (in_place) + { + filestream_delete(in_file_path); + if (filestream_rename(out_file_path, in_file_path)) + { + fprintf(stderr, "ERROR: Failed to rename temporary file\n"); + fprintf(stderr, " From: %s\n", out_file_path); + fprintf(stderr, " To: %s\n", in_file_path); + goto end; + } + } + + ret = 0; + +end: + if (in_file) + { + intfstream_close(in_file); + free(in_file); + } + + if (out_file) + { + intfstream_close(out_file); + free(out_file); + } + + return ret; +}