changeset 295:aec448037320

rpg: add support for compressed tilesets/maps
author David Demelier <markand@malikania.fr>
date Wed, 10 Mar 2021 16:40:02 +0100
parents 1606b061e4ce
children 032aeb430424
files CMakeLists.txt cmake/FindZSTD.cmake libmlk-adventure/adventure/molko.c libmlk-core/CMakeLists.txt libmlk-core/core/zfile.c libmlk-core/core/zfile.h libmlk-rpg/CMakeLists.txt libmlk-rpg/rpg/map-file.c libmlk-rpg/rpg/tileset-file.c
diffstat 9 files changed, 253 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Mar 10 13:45:12 2021 +0100
+++ b/CMakeLists.txt	Wed Mar 10 16:40:02 2021 +0100
@@ -40,6 +40,7 @@
 include(cmake/MolkoDefineTest.cmake)
 
 find_package(Jansson REQUIRED)
+find_package(ZSTD REQUIRED)
 find_package(SDL2 REQUIRED COMPONENTS image mixer ttf)
 find_package(Intl)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/FindZSTD.cmake	Wed Mar 10 16:40:02 2021 +0100
@@ -0,0 +1,56 @@
+#
+# FindZSTD.cmake -- find ZSTD C library
+#
+# Copyright (c) 2020 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# Find ZSTD library, this modules defines:
+#
+# ZSTD_INCLUDE_DIRS, where to find zstd.h
+# ZSTD_LIBRARIES, where to find library
+# ZSTD_FOUND, if it is found
+#
+# The following imported targets will be available:
+#
+# ZSTD::ZSTD, if found.
+#
+
+find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)
+find_library(ZSTD_LIBRARY NAMES libzstd zstd)
+
+include(FindPackageHandleStandardArgs)
+
+find_package_handle_standard_args(
+	ZSTD
+	FOUND_VAR ZSTD_FOUND
+	REQUIRED_VARS ZSTD_LIBRARY ZSTD_INCLUDE_DIR
+)
+
+if (ZSTD_FOUND)
+	set(ZSTD_LIBRARIES ${ZSTD_LIBRARY})
+	set(ZSTD_INCLUDE_DIRS ${ZSTD_INCLUDE_DIR})
+
+	if (NOT TARGET ZSTD::ZSTD)
+		add_library(ZSTD::ZSTD UNKNOWN IMPORTED)
+		set_target_properties(
+			ZSTD::ZSTD
+			PROPERTIES
+				IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+				IMPORTED_LOCATION "${ZSTD_LIBRARY}"
+				INTERFACE_INCLUDE_DIRECTORIES "${ZSTD_INCLUDE_DIRS}"
+		)
+	endif ()
+endif ()
+
+mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
--- a/libmlk-adventure/adventure/molko.c	Wed Mar 10 13:45:12 2021 +0100
+++ b/libmlk-adventure/adventure/molko.c	Wed Mar 10 16:40:02 2021 +0100
@@ -99,7 +99,7 @@
 	molko.team.members[0] = &character_neth;
 	molko.team.members[1] = &character_neth;
 	inventory_add(&molko.inventory, &item_potion, 100);
-	molko_teleport("maps/map-world.map", -1, -1);
+	molko_teleport("maps/map-world.map.zst", -1, -1);
 #endif
 }
 
--- a/libmlk-core/CMakeLists.txt	Wed Mar 10 13:45:12 2021 +0100
+++ b/libmlk-core/CMakeLists.txt	Wed Mar 10 16:40:02 2021 +0100
@@ -84,6 +84,8 @@
 	${libmlk-core_SOURCE_DIR}/core/window.c
 	${libmlk-core_SOURCE_DIR}/core/window.h
 	${libmlk-core_SOURCE_DIR}/core/window_p.h
+	${libmlk-core_SOURCE_DIR}/core/zfile.c
+	${libmlk-core_SOURCE_DIR}/core/zfile.h
 )
 
 configure_file(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-core/core/zfile.c	Wed Mar 10 16:40:02 2021 +0100
@@ -0,0 +1,126 @@
+/*
+ * zfile.c -- load potentially ZSTD compressed file
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/stat.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <zstd.h>
+
+#include "zfile.h"
+
+static int
+is_zstd(int fd)
+{
+	uint32_t magic = 0;
+
+	read(fd, &magic, sizeof (magic));
+	lseek(fd, 0, SEEK_SET);
+
+#if SDL_BYTEORDER != SDL_LIL_ENDIAN
+	magic = SDL_Swap32(magic);
+#endif
+
+	return magic == ZSTD_MAGICNUMBER;
+}
+
+static int
+decompress(int fd, struct zfile *zf)
+{
+	char *in = NULL;
+	unsigned long long datasz;
+	struct stat st;
+
+	/* Load uncompressed data. */
+	if (fstat(fd, &st) < 0)
+		goto fail;
+	if (!(in = calloc(1, st.st_size)) || read(fd, in, st.st_size) != st.st_size)
+		goto fail;
+
+	switch ((datasz = ZSTD_getFrameContentSize(in, st.st_size))) {
+	case ZSTD_CONTENTSIZE_ERROR:
+		errno = EINVAL;
+		goto fail;
+	case ZSTD_CONTENTSIZE_UNKNOWN:
+		errno = ENOTSUP;
+		goto fail;
+	default:
+		break;
+	}
+
+	/* Finally decompress. */
+	if (!(zf->data = calloc(1, datasz + 1)))
+		goto fail;
+	if (ZSTD_isError(ZSTD_decompress(zf->data, datasz, in, st.st_size))) {
+		errno = EINVAL;
+		goto fail;
+	}
+	if (!(zf->fp = fmemopen(zf->data, datasz, "r")))
+		goto fail;
+
+	close(fd);
+	free(in);
+
+	return 0;
+
+fail:
+	close(fd);
+	free(zf->data);
+	free(in);
+
+	return -1;
+}
+
+static int
+reopen(int fd, struct zfile *zf)
+{
+	if (!(zf->fp = fdopen(fd, "r")))
+		return close(fd), -1;
+
+	return 0;
+}
+
+int
+zfile_open(struct zfile *zf, const char *path)
+{
+	assert(zf);
+	assert(path);
+
+	int fd, status;
+
+	memset(zf, 0, sizeof (*zf));
+
+	if ((fd = open(path, O_RDONLY)) < 0)
+		return -1;
+
+	return is_zstd(fd) ? decompress(fd, zf) : reopen(fd, zf);
+}
+
+void
+zfile_close(struct zfile *zf)
+{
+	assert(zf);
+
+	free(zf->data);
+	fclose(zf->fp);
+	memset(zf, 0, sizeof (*zf));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-core/core/zfile.h	Wed Mar 10 16:40:02 2021 +0100
@@ -0,0 +1,41 @@
+/*
+ * zfile.h -- load potentially ZSTD compressed file
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MOLKO_CORE_ZFILE_H
+#define MOLKO_CORE_ZFILE_H
+
+#include <stdio.h>
+
+#include "core.h"
+
+struct zfile {
+	FILE *fp;
+	char *data;
+};
+
+CORE_BEGIN_DECLS
+
+int
+zfile_open(struct zfile *, const char *);
+
+void
+zfile_close(struct zfile *);
+
+CORE_END_DECLS
+
+#endif /* !MOLKO_CORE_ZFILE_H */
--- a/libmlk-rpg/CMakeLists.txt	Wed Mar 10 13:45:12 2021 +0100
+++ b/libmlk-rpg/CMakeLists.txt	Wed Mar 10 16:40:02 2021 +0100
@@ -105,6 +105,7 @@
 	SOURCES ${SOURCES} ${PO} ${ASSETS}
 	LIBRARIES
 		PUBLIC
+			ZSTD::ZSTD
 			libmlk-core
 			libmlk-ui
 	INCLUDES
--- a/libmlk-rpg/rpg/map-file.c	Wed Mar 10 13:45:12 2021 +0100
+++ b/libmlk-rpg/rpg/map-file.c	Wed Mar 10 16:40:02 2021 +0100
@@ -29,6 +29,7 @@
 #include <core/error.h>
 #include <core/image.h>
 #include <core/trace.h>
+#include <core/zfile.h>
 
 #include "map-file.h"
 #include "rpg_p.h"
@@ -271,24 +272,31 @@
 		.mf = file,
 		.map = map,
 	};
+	struct zfile zf;
 	bool ret = true;
 
 	memset(map, 0, sizeof (*map));
 
 	if (!alloc_pool_init(&file->blocks, sizeof (*map->blocks), NULL))
-		return false;
+		goto fail;
+	if (zfile_open(&zf, path) < 0)
+		goto fail;
 
-	if (!(ctx.fp = fopen(path, "r")))
-		return errorf("%s: %s", path, strerror(errno));
+	ctx.fp = zf.fp;
+
+	if (!(ret = parse(&ctx, path)) || !(ret = check(map)))
+		goto fail;
 
-	if (!(ret = parse(&ctx, path)) || !(ret = check(map))) {
-		map_finish(map);
-		map_file_finish(file);
-	}
+	zfile_close(&zf);
+
+	return true;
 
-	fclose(ctx.fp);
+fail:
+	map_finish(map);
+	map_file_finish(file);
+	zfile_close(&zf);
 
-	return ret;
+	return false;
 }
 
 void
--- a/libmlk-rpg/rpg/tileset-file.c	Wed Mar 10 13:45:12 2021 +0100
+++ b/libmlk-rpg/rpg/tileset-file.c	Wed Mar 10 16:40:02 2021 +0100
@@ -30,6 +30,7 @@
 #include <core/error.h>
 #include <core/image.h>
 #include <core/util.h>
+#include <core/zfile.h>
 
 #include "rpg_p.h"
 #include "tileset-file.h"
@@ -310,15 +311,21 @@
 		.tf = tf,
 		.tileset = tileset
 	};
+	struct zfile zf;
 	bool ret = true;
 
 	memset(tileset, 0, sizeof (*tileset));
 
-	if (!(ctx.fp = fopen(path, "r")))
+	if (zfile_open(&zf, path) < 0)
 		return errorf("%s: %s", path, strerror(errno));
+
+	ctx.fp = zf.fp;
+
 	if (!(ret = parse(&ctx, path)) || !(ret = check(tileset)))
 		tileset_file_finish(tf);
 
+	zfile_close(&zf);
+
 	return ret;
 }