[Scummvm-git-logs] scummvm-tools master -> 2caf8ea37db45e542d41e57e0af8bf604e40b52f

mgerhardy noreply at scummvm.org
Thu Jan 12 18:10:16 UTC 2023


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm-tools' repo located at https://github.com/scummvm/scummvm-tools .

Summary:
2caf8ea37d TWINE: new lba1 script decompiler


Commit: 2caf8ea37db45e542d41e57e0af8bf604e40b52f
    https://github.com/scummvm/scummvm-tools/commit/2caf8ea37db45e542d41e57e0af8bf604e40b52f
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2023-01-11T19:13:33+01:00

Commit Message:
TWINE: new lba1 script decompiler

Changed paths:
  A CMakeLists.txt
  A engines/twine/detwine.cpp
  A engines/twine/hqr.cpp
  A engines/twine/hqr.h
  A engines/twine/lba1.cpp
  A engines/twine/lba1.h
  A engines/twine/lba2.cpp
  A engines/twine/lba2.h
    .gitignore
    Makefile
    Makefile.common
    common/scummsys.h


diff --git a/.gitignore b/.gitignore
index 8ab20711..ab28e83b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@
 /deprince
 /descumm
 /desword2
+/detwine
 /extract_hadesch_img
 /extract_mohawk
 /extract_mps
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..cd066a90
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,43 @@
+cmake_minimum_required(VERSION 3.19)
+project(scummvm-tools CXX)
+include(CheckCXXCompilerFlag)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+if (WIN32)
+	add_definitions(-DWIN32)
+else()
+	add_definitions(-DPOSIX)
+endif()
+
+find_package(SDL REQUIRED)
+
+function(add_tool NAME)
+    add_executable(${NAME} ${ARGN})
+    target_include_directories(${NAME} PUBLIC .)
+    target_link_libraries(${NAME} SDL::SDL)
+endfunction()
+
+set(COMMON_SRC
+    common/file.cpp
+    common/hashmap.cpp
+    common/md5.cpp
+    common/memorypool.cpp
+    common/str.cpp
+    common/stream.cpp
+    common/util.cpp
+    sound/adpcm.cpp
+    sound/audiostream.cpp
+    sound/voc.cpp
+    sound/wave.cpp
+)
+
+set(DETWINE_SRC
+    ${COMMON_SRC}
+    engines/twine/detwine.cpp
+    engines/twine/lba1.cpp
+    engines/twine/lba2.cpp
+    engines/twine/hqr.cpp
+)
+add_tool(detwine ${DETWINE_SRC})
diff --git a/Makefile b/Makefile
index 78c4fb66..047c4042 100644
--- a/Makefile
+++ b/Makefile
@@ -108,6 +108,7 @@ endif
 	$(STRIP) deprince$(EXEEXT) -o $(WIN32PATH)/tools/deprince$(EXEEXT)
 	$(STRIP) descumm$(EXEEXT) -o $(WIN32PATH)/tools/descumm$(EXEEXT)
 	$(STRIP) desword2$(EXEEXT) -o $(WIN32PATH)/tools/desword2$(EXEEXT)
+	$(STRIP) detwine$(EXEEXT) -o $(WIN32PATH)/tools/detwine$(EXEEXT)
 	$(STRIP) extract_mohawk$(EXEEXT) -o $(WIN32PATH)/tools/extract_mohawk$(EXEEXT)
 	$(STRIP) gob_loadcalc$(EXEEXT) -o $(WIN32PATH)/tools/gob_loadcalc$(EXEEXT)
 	$(STRIP) grim_animb2txt$(EXEEXT) -o $(WIN32PATH)/tools/grim_animb2txt$(EXEEXT)
@@ -168,6 +169,7 @@ endif
 	$(STRIP) deprince$(EXEEXT)           -o $(srcdir)/$(WIN32BUILD)/deprince$(EXEEXT)
 	$(STRIP) descumm$(EXEEXT)            -o $(srcdir)/$(WIN32BUILD)/descumm$(EXEEXT)
 	$(STRIP) desword2$(EXEEXT)           -o $(srcdir)/$(WIN32BUILD)/desword2$(EXEEXT)
+	$(STRIP) detwine$(EXEEXT)            -o $(srcdir)/$(WIN32BUILD)/detwine$(EXEEXT)
 	$(STRIP) extract_hadesch$(EXEEXT)    -o $(srcdir)/$(WIN32BUILD)/extract_hadesch$(EXEEXT)
 	$(STRIP) extract_lokalizator$(EXEEXT)    -o $(srcdir)/$(WIN32BUILD)/extract_lokalizator$(EXEEXT)
 	$(STRIP) extract_mohawk$(EXEEXT)     -o $(srcdir)/$(WIN32BUILD)/extract_mohawk$(EXEEXT)
@@ -305,6 +307,7 @@ endif
 	$(STRIP) deprince$(EXEEXT) -o $(AMIGAOSPATH)/deprince$(EXEEXT)
 	$(STRIP) descumm$(EXEEXT) -o $(AMIGAOSPATH)/descumm$(EXEEXT)
 	$(STRIP) desword2$(EXEEXT) -o $(AMIGAOSPATH)/desword2$(EXEEXT)
+	$(STRIP) detwine$(EXEEXT) -o $(AMIGAOSPATH)/detwine$(EXEEXT)
 	$(STRIP) extract_mohawk$(EXEEXT) -o $(AMIGAOSPATH)/extract_mohawk$(EXEEXT)
 	$(STRIP) extract_ngi$(EXEEXT) -o $(AMIGAOSPATH)/extract_ngi$(EXEEXT)
 	$(STRIP) gob_loadcalc$(EXEEXT) -o $(AMIGAOSPATH)/gob_loadcalc$(EXEEXT)
diff --git a/Makefile.common b/Makefile.common
index 0cd11e01..af5e53d7 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -29,6 +29,7 @@ PROGRAMS = \
 	deprince \
 	descumm \
 	desword2 \
+	detwine \
 	gob_loadcalc \
 	extract_gob_cdi \
 	extract_mohawk \
@@ -129,23 +130,6 @@ GRIM_LUA := \
 
 decine_OBJS := engines/cine/decine.o
 
-degob_OBJS := \
-	engines/gob/degob.o \
-	engines/gob/degob_script.o \
-	engines/gob/degob_script_v1.o \
-	engines/gob/degob_script_v2.o \
-	engines/gob/degob_script_v3.o \
-	engines/gob/degob_script_v4.o \
-	engines/gob/degob_script_v5.o \
-	engines/gob/degob_script_v6.o \
-	engines/gob/degob_script_v7.o \
-	engines/gob/degob_script_bargon.o \
-	engines/gob/degob_script_fascin.o \
-	engines/gob/degob_script_littlered.o \
-	tool.o \
-	version.o \
-	$(UTILS)
-
 dekyra_OBJS := \
 	engines/kyra/dekyra.o \
 	engines/kyra/dekyra_v1.o \
@@ -215,6 +199,15 @@ degob_OBJS := \
 	version.o \
 	$(UTILS)
 
+detwine_OBJS := \
+	engines/twine/detwine.o \
+	engines/twine/hqr.o \
+	engines/twine/lba1.o \
+	engines/twine/lba2.o \
+	tool.o \
+	version.o \
+	$(UTILS)
+
 gob_loadcalc_OBJS := \
 	engines/gob/gob_loadcalc.o
 
@@ -457,6 +450,7 @@ version.o: $(filter-out version.o,$(scummvm-tools-cli_OBJS))
 ifdef USE_WXWIDGETS
 version.o: $(filter-out version.o,$(scummvm-tools_OBJS))
 endif
+version.o: $(filter-out version.o,$(detwine_OBJS))
 
 
 ######################################################################
diff --git a/common/scummsys.h b/common/scummsys.h
index 53c50dec..dcf7c8c6 100644
--- a/common/scummsys.h
+++ b/common/scummsys.h
@@ -209,6 +209,9 @@
 	typedef unsigned long int uint32;
 	typedef signed long int int32;
 
+	typedef signed long long int64;
+	typedef unsigned long long uint64;
+
 #elif defined(_WIN32_WCE)
 
 	#define scumm_stricmp stricmp
@@ -267,6 +270,19 @@
 		#endif
 	#endif
 
+#ifndef HAVE_CONFIG_H
+	typedef unsigned char byte;
+	typedef unsigned char uint8;
+	typedef signed char int8;
+	typedef unsigned short uint16;
+	typedef signed short int16;
+	typedef unsigned int uint32;
+	typedef signed int int32;
+	typedef unsigned int uint;
+	typedef signed long long int64;
+	typedef unsigned long long uint64;
+#endif
+
 	// You need to set this manually if necessary
 //	#define SCUMM_NEED_ALIGNMENT
 
@@ -325,6 +341,9 @@
 	typedef unsigned long int uint32;
 	typedef signed long int int32;
 
+	typedef signed long long int64;
+	typedef unsigned long long uint64;
+
 #elif defined(__PLAYSTATION2__)
 
 	#define scumm_stricmp strcasecmp
@@ -464,6 +483,8 @@
 	typedef unsigned int uint32;
 	typedef signed int int32;
 	typedef unsigned int uint;
+	typedef signed long long int64;
+	typedef unsigned long long uint64;
 #endif
 
 
diff --git a/engines/twine/detwine.cpp b/engines/twine/detwine.cpp
new file mode 100644
index 00000000..c000701e
--- /dev/null
+++ b/engines/twine/detwine.cpp
@@ -0,0 +1,83 @@
+/* ScummVM Tools
+ *
+ * ScummVM Tools is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* TwinE Script disassembler */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "common/util.h"
+#include "common/memstream.h"
+#include "engines/twine/hqr.h"
+#include "engines/twine/lba1.h"
+#include "engines/twine/lba2.h"
+
+static void printHelp(const char *bin) {
+	printf("Usage: %s <variant> <index> <scene.hqr>\n\n", bin);
+	printf("The disassembled script will be written to stdout.\n\n");
+	printf("Supported variants:\n");
+	printf("	lba1      - Little Big Adventure 1\n");
+	printf("	lba2      - Little Big Adventure 2\n");
+	printf("\n");
+}
+
+static int getVariant(const char *verStr) {
+	if (!scumm_stricmp(verStr, "lba1")) {
+		return 1;
+	} else if (!scumm_stricmp(verStr, "lba2")) {
+		return 2;
+	}
+	return -1;
+}
+
+int main(int argc, char **argv) {
+	if ((argc < 3) || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+		printHelp(argv[0]);
+		return -1;
+	}
+
+	int variant = getVariant(argv[1]);
+	if (variant == -1) {
+		printHelp(argv[0]);
+		return -1;
+	}
+
+	int index = atoi(argv[2]);
+	const char *sceneHqr = "scene.hqr";
+	if (argc >= 4) {
+		sceneHqr = argv[3];
+	}
+	const Common::Filename fn(sceneHqr);
+	uint8 *data = nullptr;
+	int size = 0;
+	if (fn.exists()) {
+		size = TwinE::HQR::getAllocEntry(&data, fn, index);
+	}
+	if (data == nullptr || size == 0) {
+		fprintf(stderr, "Failed to load index %i from %s", index, fn.getFullName().c_str());
+		return 127;
+	}
+
+	if (variant == 1) {
+		return decompileLBA1(data, size);
+	}
+	return decompileLBA2(data, size);
+}
diff --git a/engines/twine/hqr.cpp b/engines/twine/hqr.cpp
new file mode 100644
index 00000000..13b7e411
--- /dev/null
+++ b/engines/twine/hqr.cpp
@@ -0,0 +1,280 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/twine/hqr.h"
+#include "common/file.h"
+#include "common/util.h"
+#include "common/substream.h"
+#include "common/memstream.h"
+
+namespace TwinE {
+
+namespace HQR {
+
+/**
+ * Decompress entry based in Yaz0r and Zink decompression code
+ * @param dst destination pointer where will be the decompressed entry
+ * @param compBuf compressed data pointer
+ * @param compSize @p compBuf buffer size
+ * @param decompsize real file size after decompression
+ * @param mode compression mode used
+ */
+static void decompressEntry(uint8 *dst, const uint8 *compBuf, uint32 compSize, int32 decompsize, int32 mode) {
+	Common::MemoryReadStream stream(compBuf, compSize);
+	do {
+		uint8 b = stream.readByte();
+		for (int32 d = 0; d < 8; d++) {
+			int32 length;
+			if (!(b & (1 << d))) {
+				const uint16 offset = stream.readUint16LE();
+				length = (offset & 0x0F) + (mode + 1);
+				const uint8 *ptr = dst - (offset >> 4) - 1;
+				for (int32 i = 0; i < length; i++) {
+					*(dst++) = *(ptr++);
+				}
+			} else {
+				length = 1;
+				*(dst++) = stream.readByte();
+			}
+			decompsize -= length;
+			if (decompsize <= 0) {
+				return;
+			}
+		}
+	} while (decompsize);
+}
+
+/**
+ * Get a HQR entry pointer
+ * @param filename HQR file name
+ * @param index entry index to extract
+ * @return entry real size
+ */
+static int voxEntrySize(const Common::Filename &filename, int32 index, int32 hiddenIndex) {
+	Common::File file;
+	file.open(filename, "r");
+	if (!file.isOpen()) {
+		warning("HQR: Could not open %s", filename.getFullPath().c_str());
+		return 0;
+	}
+
+	uint32 headerSize = file.readUint32LE();
+	if ((uint32)index >= headerSize / 4) {
+		warning("HQR: Invalid entry index");
+		return 0;
+	}
+
+	file.seek(index * 4, SEEK_SET);
+	uint32 offsetToData = file.readUint32LE();
+
+	file.seek(offsetToData, SEEK_SET);
+	uint32 realSize = file.readUint32LE();
+	uint32 compSize = file.readUint32LE();
+
+	// exist hidden entries
+	for (int32 i = 0; i < hiddenIndex; i++) {
+		file.seek(offsetToData + compSize + 10, SEEK_SET); // hidden entry
+		offsetToData = offsetToData + compSize + 10;  // current hidden offset
+
+		realSize = file.readUint32LE();
+		compSize = file.readUint32LE();
+	}
+
+	return realSize;
+}
+
+int32 getEntry(uint8 *ptr, const Common::Filename &filename, int32 index) {
+	if (!ptr) {
+		return 0;
+	}
+
+	Common::File file;
+	file.open(filename, "r");
+	if (!file.isOpen()) {
+		warning("HQR: Could not open %s", filename.getFullPath().c_str());
+		return 0;
+	}
+
+	uint32 headerSize = file.readUint32LE();
+
+	if ((uint32)index >= headerSize / 4) {
+		warning("HQR: Invalid entry index");
+		return 0;
+	}
+
+	file.seek(index * 4, SEEK_SET);
+	uint32 offsetToData = file.readUint32LE();
+
+	file.seek(offsetToData, SEEK_SET);
+	uint32 realSize = file.readUint32LE();
+	uint32 compSize = file.readUint32LE();
+	uint16 mode = file.readUint16LE();
+
+	// uncompressed
+	if (mode == 0) {
+		file.read_throwsOnError(ptr, realSize);
+	}
+	// compressed: modes (1 & 2)
+	else if (mode == 1 || mode == 2) {
+		uint8 *compDataPtr = (uint8 *)malloc(compSize);
+		file.read_throwsOnError(compDataPtr, compSize);
+		decompressEntry(ptr, compDataPtr, compSize, realSize, mode);
+		free(compDataPtr);
+	}
+
+	return realSize;
+}
+
+int32 entrySize(const Common::Filename &filename, int32 index) {
+	Common::File file;
+	file.open(filename, "r");
+	if (!file.isOpen()) {
+		warning("HQR: Could not open %s", filename.getFullPath().c_str());
+		return 0;
+	}
+
+	uint32 headerSize = file.readUint32LE();
+	if ((uint32)index >= headerSize / 4) {
+		warning("HQR: Invalid entry index");
+		return 0;
+	}
+
+	file.seek(index * 4, SEEK_SET);
+	uint32 offsetToData = file.readUint32LE();
+
+	file.seek(offsetToData, SEEK_SET);
+	uint32 realSize = file.readUint32LE();
+
+	return realSize;
+}
+
+int32 numEntries(const Common::Filename &filename) {
+	Common::File file;
+	file.open(filename, "r");
+	if (!file.isOpen()) {
+		warning("HQR: Could not open %s", filename.getFullPath().c_str());
+		return 0;
+	}
+
+	uint32 headerSize = file.readUint32LE();
+	return ((int)headerSize / 4) - 1;
+}
+
+Common::SeekableReadStream *makeReadStream(const Common::Filename &filename, int index) {
+	uint8 *data = nullptr;
+	const int32 size = getAllocEntry(&data, filename, index);
+	if (size == 0) {
+		return nullptr;
+	}
+	return new Common::MemoryReadStream(data, size, DisposeAfterUse::YES);
+}
+
+int32 getAllocEntry(uint8 **ptr, const Common::Filename &filename, int32 index) {
+	if (*ptr) {
+		free(*ptr);
+	}
+	const int32 size = entrySize(filename, index);
+	if (size <= 0) {
+		*ptr = nullptr;
+		warning("HQR: failed to get entry for index %i from file: %s", index, filename.getFullPath().c_str());
+		return 0;
+	}
+	*ptr = (uint8 *)malloc(size * sizeof(uint8));
+	if (!*ptr) {
+		warning("HQR: unable to allocate entry memory");
+		return 0;
+	}
+	const int32 entrySize = getEntry(*ptr, filename, index);
+	assert(entrySize == size);
+	return entrySize;
+}
+
+int32 getVoxEntry(uint8 *ptr, const Common::Filename &filename, int32 index, int32 hiddenIndex) {
+	if (!ptr) {
+		return 0;
+	}
+	Common::File file;
+	file.open(filename, "r");
+	if (!file.isOpen()) {
+		warning("HQR: Could not open %s", filename.getFullPath().c_str());
+		return 0;
+	}
+
+	uint32 headerSize = file.readUint32LE();
+
+	if ((uint32)index >= headerSize / 4) {
+		warning("HQR: Invalid entry index");
+		return 0;
+	}
+
+	file.seek(index * 4, SEEK_SET);
+	uint32 offsetToData = file.readUint32LE();
+
+	file.seek(offsetToData, SEEK_SET);
+	uint32 realSize = file.readUint32LE();
+	uint32 compSize = file.readUint32LE();
+	uint16 mode = file.readSint16LE();
+
+	// exist hidden entries
+	for (int32 i = 0; i < hiddenIndex; i++) {
+		file.seek(offsetToData + compSize + 10, SEEK_SET); // hidden entry
+		offsetToData = offsetToData + compSize + 10;  // current hidden offset
+
+		realSize = file.readUint32LE();
+		compSize = file.readUint32LE();
+		mode = file.readUint16LE();
+	}
+
+	// uncompressed
+	if (mode == 0) {
+		file.read_throwsOnError(ptr, realSize);
+	}
+	// compressed: modes (1 & 2)
+	else if (mode == 1 || mode == 2) {
+		uint8 *compDataPtr = (uint8 *)malloc(compSize);
+		file.read_throwsOnError(compDataPtr, compSize);
+		decompressEntry(ptr, compDataPtr, compSize, realSize, mode);
+		free(compDataPtr);
+	}
+
+	return realSize;
+}
+
+int32 getAllocVoxEntry(uint8 **ptr, const Common::Filename &filename, int32 index, int32 hiddenIndex) {
+	const int32 size = voxEntrySize(filename, index, hiddenIndex);
+	if (size == 0) {
+		warning("HQR: vox entry with 0 size found for index: %d", index);
+		return 0;
+	}
+
+	*ptr = (uint8 *)malloc(size * sizeof(uint8));
+	if (!*ptr) {
+		warning("HQR: unable to allocate entry memory of size %d for index: %d", size, index);
+		return 0;
+	}
+	const int32 entrySize = getVoxEntry(*ptr, filename, index, hiddenIndex);
+	assert(entrySize == size);
+	return entrySize;
+}
+
+} // namespace HQR
+
+} // namespace TwinE
diff --git a/engines/twine/hqr.h b/engines/twine/hqr.h
new file mode 100644
index 00000000..8b103402
--- /dev/null
+++ b/engines/twine/hqr.h
@@ -0,0 +1,98 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TWINE_HQR_H
+#define TWINE_HQR_H
+
+#include "common/stream.h"
+#include "common/file.h"
+
+namespace TwinE {
+
+class TwinEEngine;
+
+/**
+ * High Quality Resource
+ *
+ * https://web.archive.org/web/20181218233826/http://lbafileinfo.kazekr.net/index.php?title=High_quality_resource
+ */
+namespace HQR {
+
+
+/**
+ * Get a HQR entry pointer
+ * @param ptr pointer to save the entry
+ * @param filename HQR file name
+ * @param index entry index to extract
+ * @return entry real size
+ */
+int32 getEntry(uint8 *ptr, const Common::Filename &filename, int32 index);
+
+/**
+ * Get a HQR entry pointer
+ * @param filename HQR file name
+ * @param index entry index to extract
+ * @return entry real size
+ */
+int32 entrySize(const Common::Filename &filename, int32 index);
+
+/**
+ * Get a HQR total number of entries
+ * @param filename HQR file name
+ * @return total number of entries
+ */
+int32 numEntries(const Common::Filename &filename);
+
+/**
+ * Get a HQR entry pointer with memory allocation
+ * @param ptr pointer to save the entry. This pointer is automatically freed and therefore must be initialized
+ * to @c nullptr on the first run.
+ * @param filename HQR file name
+ * @param index entry index to extract
+ * @return entry real size
+ */
+int32 getAllocEntry(uint8 **ptr, const Common::Filename &filename, int32 index);
+
+/**
+ * Get a HQR entry pointer
+ * @param ptr pointer to save the entry
+ * @param filename HQR file name
+ * @param index entry index to extract
+ * @return entry real size
+ */
+int32 getVoxEntry(uint8 *ptr, const Common::Filename &filename, int32 index, int32 hiddenIndex);
+/**
+ * Get a HQR entry pointer with memory allocation
+ * @param ptr pointer to save the entry. This pointer is automatically freed and therefore must be initialized
+ * to @c nullptr on the first run.
+ * @param filename HQR file name
+ * @param index entry index to extract
+ * @return entry real size
+ */
+int32 getAllocVoxEntry(uint8 **ptr, const Common::Filename &filename, int32 index, int32 hiddenIndex);
+
+Common::SeekableReadStream *makeReadStream(const Common::Filename &filename, int index);
+
+} // namespace HQR
+
+} // namespace TwinE
+
+#endif
diff --git a/engines/twine/lba1.cpp b/engines/twine/lba1.cpp
new file mode 100644
index 00000000..d48d44fe
--- /dev/null
+++ b/engines/twine/lba1.cpp
@@ -0,0 +1,1241 @@
+/* ScummVM Tools
+ *
+ * ScummVM Tools is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/twine/lba1.h"
+#include <stdio.h>
+#include <string.h>
+
+#include "common/array.h"
+#include "common/util.h"
+#include "common/memstream.h"
+
+struct ScriptContext {
+	Common::MemoryReadStream stream;
+	Common::Array<int> offsets;
+	int level;
+	int comportmentId;
+	bool comportement;
+};
+
+typedef void ScriptFunc(ScriptContext &ctx);
+
+struct ScriptFunction {
+	const char *name;
+	ScriptFunc *function;
+};
+
+#define MAPFUNC(name, func) \
+	{ name, func }
+
+static const int initialLevel = 2;
+static const int indentWidth = 2;
+
+static void mEND(ScriptContext &ctx) {
+	printf("%*sEND\n", ctx.level, " ");
+}
+
+static void mNOP(ScriptContext &ctx) {
+	printf("%*sNOP\n", ctx.level, " ");
+}
+
+static void mBODY(ScriptContext &ctx) {
+	printf("%*sBODY %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mANIM(ScriptContext &ctx) {
+	printf("%*sANIM %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mGOTO_POINT(ScriptContext &ctx) {
+	printf("%*sGOTO_POINT %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mWAIT_ANIM(ScriptContext &ctx) {
+	printf("%*sWAIT_ANIM\n", ctx.level, " ");
+}
+
+static void mLOOP(ScriptContext &ctx) {
+	printf("%*sLOOP\n", ctx.level, " ");
+}
+
+static void mANGLE(ScriptContext &ctx) {
+	printf("%*sANGLE %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mPOS_POINT(ScriptContext &ctx) {
+	printf("%*sPOS_POINT %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mLABEL(ScriptContext &ctx) {
+	ctx.level = initialLevel;
+	printf("%*sLABEL %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+	ctx.level += indentWidth;
+}
+
+static void mGOTO(ScriptContext &ctx) {
+	printf("%*sGOTO_SYM_POINT %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mSTOP(ScriptContext &ctx) {
+	printf("%*sSTOP\n", ctx.level, " ");
+}
+
+static void mGOTO_SYM_POINT(ScriptContext &ctx) {
+	printf("%*sGOTO_SYM_POINT %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mWAIT_NUM_ANIM(ScriptContext &ctx) {
+	const int32 animRepeats = ctx.stream.readByte();
+	const int32 animPos = ctx.stream.readByte();
+	printf("%*sWAIT_NUM_ANIM %i %i\n", ctx.level, " ", animRepeats, animPos);
+}
+
+static void mSAMPLE(ScriptContext &ctx) {
+	printf("%*sSAMPLE %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mGOTO_POINT_3D(ScriptContext &ctx) {
+	printf("%*sGOTO_POINT_3D %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mSPEED(ScriptContext &ctx) {
+	printf("%*sSPEED %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mBACKGROUND(ScriptContext &ctx) {
+	printf("%*sBACKGROUND %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void mWAIT_NUM_SECOND(ScriptContext &ctx) {
+	printf("%*sWAIT_NUM_SECOND %i %i\n", ctx.level, " ", (int)ctx.stream.readByte(), ctx.stream.readSint32LE());
+}
+
+static void mNO_BODY(ScriptContext &ctx) {
+	printf("%*sNO_BODY\n", ctx.level, " ");
+}
+
+static void mBETA(ScriptContext &ctx) {
+	printf("%*sBETA %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mOPEN_LEFT(ScriptContext &ctx) {
+	printf("%*sOPEN_LEFT %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mOPEN_RIGHT(ScriptContext &ctx) {
+	printf("%*sOPEN_RIGHT %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mOPEN_UP(ScriptContext &ctx) {
+	printf("%*sOPEN_UP %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mOPEN_DOWN(ScriptContext &ctx) {
+	printf("%*sOPEN_DOWN %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mCLOSE(ScriptContext &ctx) {
+	printf("%*sCLOSE\n", ctx.level, " ");
+}
+
+static void mWAIT_DOOR(ScriptContext &ctx) {
+	printf("%*sWAIT_DOOR\n", ctx.level, " ");
+}
+
+static void mSAMPLE_RND(ScriptContext &ctx) {
+	printf("%*sSAMPLE_RND %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mSAMPLE_ALWAYS(ScriptContext &ctx) {
+	printf("%*sSAMPLE_ALWAYS %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mSAMPLE_STOP(ScriptContext &ctx) {
+	printf("%*sSAMPLE_STOP %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mPLAY_FLA(ScriptContext &ctx) {
+	int strIdx = 0;
+	char movie[64];
+	do {
+		const byte c = ctx.stream.readByte();
+		movie[strIdx++] = c;
+		if (c == '\0') {
+			break;
+		}
+		if (strIdx >= ARRAYSIZE(movie)) {
+			error("Max string size exceeded for fla name");
+		}
+	} while (true);
+
+	printf("%*sPLAY_FLA %s\n", ctx.level, " ", movie);
+}
+
+static void mREPEAT_SAMPLE(ScriptContext &ctx) {
+	printf("%*sREPEAT_SAMPLE %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mSIMPLE_SAMPLE(ScriptContext &ctx) {
+	printf("%*sSIMPLE_SAMPLE %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mFACE_HERO(ScriptContext &ctx) {
+	printf("%*sFACE_HERO %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void mANGLE_RND(ScriptContext &ctx) {
+	const int16 val1 = ctx.stream.readSint16LE();
+	const int16 val2 = ctx.stream.readSint16LE();
+	printf("%*sANGLE_RND %i %i\n", ctx.level, " ", (int)val1, (int)val2);
+}
+
+static const ScriptFunction moveScriptFunctions[] = {
+	/*0x00*/ MAPFUNC("END", mEND),
+	/*0x01*/ MAPFUNC("NOP", mNOP),
+	/*0x02*/ MAPFUNC("BODY", mBODY),
+	/*0x03*/ MAPFUNC("ANIM", mANIM),
+	/*0x04*/ MAPFUNC("GOTO_POINT", mGOTO_POINT),
+	/*0x05*/ MAPFUNC("WAIT_ANIM", mWAIT_ANIM),
+	/*0x06*/ MAPFUNC("LOOP", mLOOP),
+	/*0x07*/ MAPFUNC("ANGLE", mANGLE),
+	/*0x08*/ MAPFUNC("POS_POINT", mPOS_POINT),
+	/*0x09*/ MAPFUNC("LABEL", mLABEL),
+	/*0x0A*/ MAPFUNC("GOTO", mGOTO),
+	/*0x0B*/ MAPFUNC("STOP", mSTOP),
+	/*0x0C*/ MAPFUNC("GOTO_SYM_POINT", mGOTO_SYM_POINT),
+	/*0x0D*/ MAPFUNC("WAIT_NUM_ANIM", mWAIT_NUM_ANIM),
+	/*0x0E*/ MAPFUNC("SAMPLE", mSAMPLE),
+	/*0x0F*/ MAPFUNC("GOTO_POINT_3D", mGOTO_POINT_3D),
+	/*0x10*/ MAPFUNC("SPEED", mSPEED),
+	/*0x11*/ MAPFUNC("BACKGROUND", mBACKGROUND),
+	/*0x12*/ MAPFUNC("WAIT_NUM_SECOND", mWAIT_NUM_SECOND),
+	/*0x13*/ MAPFUNC("NO_BODY", mNO_BODY),
+	/*0x14*/ MAPFUNC("BETA", mBETA),
+	/*0x15*/ MAPFUNC("OPEN_LEFT", mOPEN_LEFT),
+	/*0x16*/ MAPFUNC("OPEN_RIGHT", mOPEN_RIGHT),
+	/*0x17*/ MAPFUNC("OPEN_UP", mOPEN_UP),
+	/*0x18*/ MAPFUNC("OPEN_DOWN", mOPEN_DOWN),
+	/*0x19*/ MAPFUNC("CLOSE", mCLOSE),
+	/*0x1A*/ MAPFUNC("WAIT_DOOR", mWAIT_DOOR),
+	/*0x1B*/ MAPFUNC("SAMPLE_RND", mSAMPLE_RND),
+	/*0x1C*/ MAPFUNC("SAMPLE_ALWAYS", mSAMPLE_ALWAYS),
+	/*0x1D*/ MAPFUNC("SAMPLE_STOP", mSAMPLE_STOP),
+	/*0x1E*/ MAPFUNC("PLAY_FLA", mPLAY_FLA),
+	/*0x1F*/ MAPFUNC("REPEAT_SAMPLE", mREPEAT_SAMPLE),
+	/*0x20*/ MAPFUNC("SIMPLE_SAMPLE", mSIMPLE_SAMPLE),
+	/*0x21*/ MAPFUNC("FACE_HERO", mFACE_HERO),
+	/*0x22*/ MAPFUNC("ANGLE_RND", mANGLE_RND)
+};
+
+
+/** Script condition operators */
+static const char *LifeScriptOperators[] = {
+	"==",
+	">",
+	"<",
+	">=",
+	"<=",
+	"!="
+};
+
+/** Script condition command opcodes */
+enum LifeScriptConditions {
+	/*0x00*/ kcCOL = 0,              /*<! Current actor collision with another actor. (Parameter = Actor Index) */
+	/*0x01*/ kcCOL_OBJ = 1,          /*<! Actor collision with the actor passed as parameter. (Parameter = Actor Index, Parameter = Actor Index) */
+	/*0x02*/ kcDISTANCE = 2,         /*<! Distance between the current actor and the actor passed as parameter. (Parameter = Actor Index, Parameter = Distance between) */
+	/*0x03*/ kcZONE = 3,             /*<! Current actor tread on zone passed as parameter. (Parameter = Zone Index) */
+	/*0x04*/ kcZONE_OBJ = 4,         /*<! The actor passed as parameter will tread on zone passed as parameter. (Parameter = Actor Index, Parameter = Zone Index) */
+	/*0x05*/ kcBODY = 5,             /*<! Body of the current actor. (Parameter = Body Index) */
+	/*0x06*/ kcBODY_OBJ = 6,         /*<! Body of the actor passed as parameter. (Parameter = Body Index) */
+	/*0x07*/ kcANIM = 7,             /*<! Body Animation of the current actor. (Parameter = Animation Index) */
+	/*0x08*/ kcANIM_OBJ = 8,         /*<! Body Animation of the actor passed as parameter. (Parameter = Animation Index) */
+	/*0x09*/ kcL_TRACK = 9,          /*<! Current actor track. (Parameter = Track Index) */
+	/*0x0A*/ kcL_TRACK_OBJ = 10,     /*<! Track of the actor passed as parameter. (Parameter = Track Index) */
+	/*0x0B*/ kcFLAG_CUBE = 11,       /*<! Game Cube Flags. (Parameter = Cube Flag Index, Parameter = 0 (not set), = 1 (set))k */
+	/*0x0C*/ kcCONE_VIEW = 12,       /*<! The actor passed as parameter have a "vision in circle". (Parameter = Actor Index, Parameter = Distance) */
+	/*0x0D*/ kcHIT_BY = 13,          /*<! Current actor hited by the actor passed as parameter. (Parameter = Actor Index) */
+	/*0x0E*/ kcACTION = 14,          /*<! Hero action behavior. (Parameter = Behaviour Index) */
+	/*0x0F*/ kcFLAG_GAME = 15,       /*<! Game Flags (See further list). (Parameter = Flag Index, Parameter = 0 (not set), = 1 (set)) */
+	/*0x10*/ kcLIFE_POINT = 16,      /*<! Current actor life points. (Parameter = Life points) */
+	/*0x11*/ kcLIFE_POINT_OBJ = 17,  /*<! Life points of the current actor passed as parameter. (Parameter = Life points) */
+	/*0x12*/ kcNUM_LITTLE_KEYS = 18, /*<! Number of keys. (Parameter = Number of keys) */
+	/*0x13*/ kcNUM_GOLD_PIECES = 19, /*<! Coins/Gold Amount. (Parameter = Coins/Gold amount) */
+	/*0x14*/ kcBEHAVIOUR = 20,       /*<! Hero behaviour. (Parameter = Behaviour Index) */
+	/*0x15*/ kcCHAPTER = 21,         /*<! Story Chapters. (Parameter = Chapter Index) */
+	/*0x16*/ kcDISTANCE_3D = 22,     /*<! Distance between the actor passed as parameter and the current actor. (Parameter = Actor Index, Parameter = Distance) */
+	/*0x17*/ kcMAGIC_LEVEL = 23,
+	/*0x18*/ kcMAGIC_POINTS = 24,
+	/*0x19*/ kcUSE_INVENTORY = 25,   /*<! Use inventory object. (Parameter = Object Index in the inventory, Paramenter = 0 (Not in Inventory), = 1 (In the Inventory)) */
+	/*0x1A*/ kcCHOICE = 26,          /*<! Menu choice. (Parameter = Text Index in the current Text Bank) */
+	/*0x1B*/ kcFUEL = 27,            /*<! Amount of fuel gas the Hero have in his inventory. (Parameter = Gas amount) */
+	/*0x1C*/ kcCARRIED_BY = 28,      /*<! The current is carried by the actor passed as paramenter. (Parameter = Actor Index) */
+	/*0x1D*/ kcCDROM = 29            /*<! CDROM audio tracks. (Parameter = Audio Tracks Index) */
+};
+
+static int32 processLifeConditions(ScriptContext &ctx) {
+	int32 conditionValueSize = 1;
+	int32 conditionOpcode = ctx.stream.readByte();
+	switch (conditionOpcode) {
+	case kcCOL:
+		printf("collision");
+		break;
+	case kcCOL_OBJ: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("col_obj %i", actorIdx);
+		break;
+	}
+	case kcDISTANCE: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("distance %i", actorIdx);
+		conditionValueSize = 2;
+		break;
+	}
+	case kcZONE:
+		printf("zone");
+		break;
+	case kcZONE_OBJ: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("zone_obj %i", actorIdx);
+		break;
+	}
+	case kcBODY:
+		printf("body");
+		break;
+	case kcBODY_OBJ: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("body_obj %i", actorIdx);
+		break;
+	}
+	case kcANIM:
+		printf("anim");
+		break;
+	case kcANIM_OBJ: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("anim_obj %i", actorIdx);
+		break;
+	}
+	case kcL_TRACK:
+		printf("track");
+		break;
+	case kcL_TRACK_OBJ: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("track_obj %i", actorIdx);
+		break;
+	}
+	case kcFLAG_CUBE: {
+		int32 flagIdx = ctx.stream.readByte();
+		printf("flag_cube %i", flagIdx);
+		break;
+	}
+	case kcCONE_VIEW: {
+		int32 targetActorIdx = ctx.stream.readByte();
+		printf("cone_view %i", targetActorIdx);
+		break;
+	}
+	case kcHIT_BY:
+		printf("hit_by");
+		break;
+	case kcACTION:
+		printf("action");
+		break;
+	case kcFLAG_GAME: {
+		int32 flagIdx = ctx.stream.readByte();
+		printf("flag_game %i", flagIdx);
+		break;
+	}
+	case kcLIFE_POINT:
+		printf("life_point");
+		break;
+	case kcLIFE_POINT_OBJ: {
+		int32 actorIdx = ctx.stream.readByte();
+		printf("life_point_obj %i", actorIdx);
+		break;
+	}
+	case kcNUM_LITTLE_KEYS:
+		printf("num_little_keys");
+		break;
+	case kcNUM_GOLD_PIECES:
+		printf("num_gold_pieces");
+		conditionValueSize = 2;
+		break;
+	case kcBEHAVIOUR:
+		printf("behaviour");
+		break;
+	case kcCHAPTER:
+		printf("chapter");
+		break;
+	case kcDISTANCE_3D: {
+		int32 targetActorIdx = ctx.stream.readByte();
+		printf("distance_3d %i ", targetActorIdx);
+		conditionValueSize = 2;
+		break;
+	}
+	case kcMAGIC_LEVEL:
+		printf("magic_level");
+		break;
+	case kcMAGIC_POINTS:
+		printf("magic_points");
+		break;
+	case kcUSE_INVENTORY: {
+		int32 item = ctx.stream.readByte();
+		printf("use_inventory %i", item);
+		break;
+	}
+	case kcCHOICE:
+		printf("choice");
+		conditionValueSize = 2;
+		break;
+	case kcFUEL:
+		printf("fuel");
+		break;
+	case kcCARRIED_BY:
+		printf("carried_by");
+		break;
+	case kcCDROM:
+		printf("cdrom");
+		break;
+	default:
+		error("Actor condition opcode %d", conditionOpcode);
+		break;
+	}
+
+	return conditionValueSize;
+}
+
+static void processLifeOperators(ScriptContext &ctx, int32 valueSize) {
+	const uint8 operatorCode = ctx.stream.readByte();
+	if (operatorCode >= ARRAYSIZE(LifeScriptOperators)) {
+		error("Invalid operator %i", (int)operatorCode);
+	}
+	printf(" %s ", LifeScriptOperators[operatorCode]);
+	int32 conditionValue;
+	if (valueSize == 1) {
+		conditionValue = ctx.stream.readByte();
+	} else if (valueSize == 2) {
+		conditionValue = ctx.stream.readSint16LE();
+	} else {
+		error("Unknown operator value size %d", valueSize);
+	}
+	printf("%i", conditionValue);
+}
+
+static void lEMPTY(ScriptContext &ctx) {
+	printf("%*sEMPTY\n", ctx.level, " ");
+}
+
+static void lEND(ScriptContext &ctx) {
+	printf("%*sEND\n", ctx.level, " ");
+	ctx.level -= indentWidth;
+}
+
+static void lNOP(ScriptContext &ctx) {
+	printf("%*sNOP\n", ctx.level, " ");
+}
+
+static void lOFFSET(ScriptContext &ctx) {
+	const int16 offset = ctx.stream.readSint16LE();
+	printf("%*sOFFSET %i\n", ctx.level, " ", (int)offset);
+}
+
+static void lSNIF(ScriptContext &ctx) {
+	printf("%*sSWITCH_NO_IF ", ctx.level, " ");
+	const int32 valueSize = processLifeConditions(ctx);
+	processLifeOperators(ctx, valueSize);
+	const int16 offset = ctx.stream.readSint16LE();
+	ctx.offsets.push_back(offset);
+	printf(" (offset %i)\n", offset);
+	ctx.level += indentWidth;
+}
+
+static void lNEVERIF(ScriptContext &ctx) {
+	printf("%*sNEVER_IF ", ctx.level, " ");
+	const int32 valueSize = processLifeConditions(ctx);
+	processLifeOperators(ctx, valueSize);
+	const int16 offset = ctx.stream.readSint16LE();
+	ctx.offsets.push_back(offset);
+	printf(" (offset %i)\n", offset);
+	ctx.level += indentWidth;
+}
+
+static void lOR_IF(ScriptContext &ctx) {
+	printf("%*sOR_IF ", ctx.level, " ");
+	const int32 valueSize = processLifeConditions(ctx);
+	processLifeOperators(ctx, valueSize);
+	const int16 offset = ctx.stream.readSint16LE();
+	printf(" (offset %i)\n", offset);
+}
+
+static void lNO_IF(ScriptContext &ctx) {
+	printf("%*sNO_IF\n", ctx.level, " ");
+	ctx.level += indentWidth;
+}
+
+static void lIF(ScriptContext &ctx) {
+	printf("%*sIF ", ctx.level, " ");
+	const int32 valueSize = processLifeConditions(ctx);
+	processLifeOperators(ctx, valueSize);
+	const int16 offset = ctx.stream.readSint16LE();
+	ctx.offsets.push_back(offset);
+	printf(" (offset %i)\n", offset);
+	ctx.level += indentWidth;
+}
+
+static void lSWIF(ScriptContext &ctx) {
+	printf("%*sSWITCH_IF ", ctx.level, " ");
+	const int32 valueSize = processLifeConditions(ctx);
+	processLifeOperators(ctx, valueSize);
+	const int16 offset = ctx.stream.readSint16LE();
+	ctx.offsets.push_back(offset);
+	printf(" (offset %i)\n", offset);
+	ctx.level += indentWidth;
+}
+
+static void lONEIF(ScriptContext &ctx) {
+	printf("%*sONEIF ", ctx.level, " ");
+	const int32 valueSize = processLifeConditions(ctx);
+	processLifeOperators(ctx, valueSize);
+	const int16 offset = ctx.stream.readSint16LE();
+	ctx.offsets.push_back(offset);
+	printf(" (offset %i)\n", offset);
+	ctx.level += indentWidth;
+}
+
+static void lELSE(ScriptContext &ctx) {
+	const int16 offset = ctx.stream.readSint16LE();
+	ctx.offsets.push_back(ctx.stream.pos());
+	ctx.level -= indentWidth;
+	printf("%*sELSE (offset %i)\n", ctx.level, " ", offset);
+	ctx.level += indentWidth;
+}
+
+static void lLABEL(ScriptContext &ctx) {
+	printf("%*sLABEL %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lRETURN(ScriptContext &ctx) {
+	printf("%*sRETURN\n", ctx.level, " ");
+	ctx.level -= indentWidth;
+}
+
+static void lBODY(ScriptContext &ctx) {
+	const int32 bodyIdx = ctx.stream.readByte();
+	printf("%*sBODY %i\n", ctx.level, " ", (int)bodyIdx);
+}
+
+static void lBODY_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 otherBodyIdx = ctx.stream.readByte();
+	printf("%*sBODY_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)otherBodyIdx);
+}
+
+static void lANIM(ScriptContext &ctx) {
+	const int32 animIdx = ctx.stream.readByte();
+	printf("%*sANIM %i\n", ctx.level, " ", (int)animIdx);
+}
+
+static void lANIM_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 otherAnimIdx = ctx.stream.readByte();
+	printf("%*sANIM_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)otherAnimIdx);
+}
+
+static void lSET_LIFE(ScriptContext &ctx) {
+	const int16 offset = ctx.stream.readSint16LE();
+	printf("%*sSET_LIFE %i\n", ctx.level, " ", (int)offset);
+}
+
+static void lSET_LIFE_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int16 offset = ctx.stream.readSint16LE();
+	printf("%*sSET_LIFE_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)offset);
+}
+
+static void lSET_TRACK(ScriptContext &ctx) {
+	const int16 offset = ctx.stream.readSint16LE();
+	printf("%*sSET_TRACK %i\n", ctx.level, " ", (int)offset);
+}
+
+static void lSET_TRACK_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int16 offset = ctx.stream.readSint16LE();
+	printf("%*sSET_TRACK_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)offset);
+}
+
+static void lMESSAGE(ScriptContext &ctx) {
+	printf("%*sMESSAGE %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void lFALLABLE(ScriptContext &ctx) {
+	printf("%*sFALLABLE %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lSET_DIRMODE(ScriptContext &ctx) {
+	const int32 controlMode = ctx.stream.readByte();
+	if (controlMode == 2) { // kFollow
+		printf("%*sSET_DIRMODE %i %i\n", ctx.level, " ", (int)controlMode, (int)ctx.stream.readByte());
+	} else {
+		printf("%*sSET_DIRMODE %i\n", ctx.level, " ", (int)controlMode);
+	}
+}
+
+static void lSET_DIRMODE_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 controlMode = ctx.stream.readByte();
+
+	if (controlMode == 2) {
+		int32 followedActor = ctx.stream.readByte();
+		printf("%*sSET_DIRMODE_OBJ %i %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)controlMode, (int)followedActor);
+	} else {
+		printf("%*sSET_DIRMODE_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)controlMode);
+	}
+}
+
+static void lCAM_FOLLOW(ScriptContext &ctx) {
+	printf("%*sCAM_FOLLOW %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lSET_BEHAVIOUR(ScriptContext &ctx) {
+	const int32 behavior = ctx.stream.readByte();
+	printf("%*sSET_BEHAVIOUR %i\n", ctx.level, " ", (int)behavior);
+}
+
+static void lSET_FLAG_CUBE(ScriptContext &ctx) {
+	const int32 flagIdx = ctx.stream.readByte();
+	const int32 flagValue = ctx.stream.readByte();
+	printf("%*sSET_FLAG_CUBE %i %i\n", ctx.level, " ", (int)flagIdx, (int)flagValue);
+}
+
+static void lCOMPORTEMENT(ScriptContext &ctx) {
+	printf("%*sCOMPORTEMENT %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lSET_COMPORTEMENT(ScriptContext &ctx) {
+	printf("%*sSET_COMPORTEMENT %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void lSET_COMPORTEMENT_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int16 pos = ctx.stream.readSint16LE();
+	printf("%*sSET_COMPORTEMENT_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)pos);
+}
+
+static void lEND_COMPORTEMENT(ScriptContext &ctx) {
+	ctx.level -= indentWidth;
+	printf("%*sEND_COMPORTEMENT\n", ctx.level, " ");
+	ctx.comportement = false;
+}
+
+static void lSET_FLAG_GAME(ScriptContext &ctx) {
+	const uint8 flagIdx = ctx.stream.readByte();
+	const uint8 flagValue = ctx.stream.readByte();
+	printf("%*sSET_FLAG_GAME %i %i\n", ctx.level, " ", (int)flagIdx, (int)flagValue);
+}
+
+static void lKILL_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	printf("%*slKILL_OBJ %i\n", ctx.level, " ", (int)otherActorIdx);
+}
+
+static void lSUICIDE(ScriptContext &ctx) {
+	printf("%*sSUICIDE\n", ctx.level, " ");
+}
+
+static void lUSE_ONE_LITTLE_KEY(ScriptContext &ctx) {
+	printf("%*sUSE_ONE_LITTLE_KEY\n", ctx.level, " ");
+}
+
+static void lGIVE_GOLD_PIECES(ScriptContext &ctx) {
+	const int16 kashes = ctx.stream.readSint16LE();
+	printf("%*sGIVE_GOLD_PIECES %i\n", ctx.level, " ", (int)kashes);
+}
+
+static void lEND_LIFE(ScriptContext &ctx) {
+	printf("%*sEND_LIFE\n", ctx.level, " ");
+}
+
+static void lSTOP_L_TRACK(ScriptContext &ctx) {
+	printf("%*sSTOP_L_TRACK\n", ctx.level, " ");
+}
+
+static void lRESTORE_L_TRACK(ScriptContext &ctx) {
+	printf("%*sRESTORE_L_TRACK\n", ctx.level, " ");
+}
+
+static void lMESSAGE_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 int32x = ctx.stream.readSint16LE();
+	printf("%*sMESSAGE_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)int32x);
+}
+
+static void lINC_CHAPTER(ScriptContext &ctx) {
+	printf("%*sINC_CHAPTER\n", ctx.level, " ");
+}
+
+static void lFOUND_OBJECT(ScriptContext &ctx) {
+	printf("%*sFOUND_OBJECT %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lSET_DOOR_LEFT(ScriptContext &ctx) {
+	const int32 distance = ctx.stream.readSint16LE();
+	printf("%*sSET_DOOR_LEFT %i\n", ctx.level, " ", (int)distance);
+}
+
+static void lSET_DOOR_RIGHT(ScriptContext &ctx) {
+	const int32 distance = ctx.stream.readSint16LE();
+	printf("%*sSET_DOOR_RIGHT %i\n", ctx.level, " ", (int)distance);
+}
+
+static void lSET_DOOR_UP(ScriptContext &ctx) {
+	const int32 distance = ctx.stream.readSint16LE();
+	printf("%*sSET_DOOR_UP %i\n", ctx.level, " ", (int)distance);
+}
+
+static void lSET_DOOR_DOWN(ScriptContext &ctx) {
+	const int32 distance = ctx.stream.readSint16LE();
+	printf("%*sSET_DOOR_DOWN %i\n", ctx.level, " ", (int)distance);
+}
+
+static void lGIVE_BONUS(ScriptContext &ctx) {
+	const int32 flag = ctx.stream.readByte();
+	printf("%*sGIVE_BONUS %i\n", ctx.level, " ", (int)flag);
+}
+
+static void lCHANGE_CUBE(ScriptContext &ctx) {
+	const int32 sceneIdx = ctx.stream.readByte();
+	printf("%*sCHANGE_CUBE %i\n", ctx.level, " ", (int)sceneIdx);
+}
+
+static void lOBJ_COL(ScriptContext &ctx) {
+	const int32 collision = ctx.stream.readByte();
+	printf("%*sOBJ_COL %i\n", ctx.level, " ", (int)collision);
+}
+
+static void lBRICK_COL(ScriptContext &ctx) {
+	const int32 collision = ctx.stream.readByte();
+	printf("%*sBRICK_COL %i\n", ctx.level, " ", (int)collision);
+}
+
+static void lINVISIBLE(ScriptContext &ctx) {
+	printf("%*sINVISIBLE %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lZOOM(ScriptContext &ctx) {
+	const int zoomScreen = ctx.stream.readByte();
+	printf("%*sZOOM %i\n", ctx.level, " ", zoomScreen);
+}
+
+static void lPOS_POINT(ScriptContext &ctx) {
+	const int32 trackIdx = ctx.stream.readByte();
+	printf("%*sPOS_POINT %i\n", ctx.level, " ", (int)trackIdx);
+}
+
+static void lSET_MAGIC_LEVEL(ScriptContext &ctx) {
+	printf("%*sSET_MAGIC_LEVEL %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lSUB_MAGIC_POINT(ScriptContext &ctx) {
+	const int16 magicPoints = (int16)ctx.stream.readByte();
+	printf("%*sSET_MAGIC_POINT %i\n", ctx.level, " ", (int)magicPoints);
+}
+
+static void lSET_LIFE_POINT_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 lifeValue = ctx.stream.readByte();
+	printf("%*sSET_LIFE_POINT_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)lifeValue);
+}
+
+static void lSUB_LIFE_POINT_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 lifeValue = ctx.stream.readByte();
+	printf("%*sSUB_LIFE_POINT_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)lifeValue);
+}
+
+static void lHIT_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 strengthOfHit = ctx.stream.readByte();
+	printf("%*sHIT_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)strengthOfHit);
+}
+
+static void lPLAY_FLA(ScriptContext &ctx) {
+	int strIdx = 0;
+	char movie[64];
+	do {
+		const byte c = ctx.stream.readByte();
+		movie[strIdx++] = c;
+		if (c == '\0') {
+			break;
+		}
+		if (strIdx >= ARRAYSIZE(movie)) {
+			error("Max string size exceeded for fla name");
+		}
+	} while (true);
+	printf("%*sPLAY_FLA %s\n", ctx.level, " ", movie);
+}
+
+static void lPLAY_MIDI(ScriptContext &ctx) {
+	const int32 midiIdx = ctx.stream.readByte();
+	printf("%*sPLAY_MIDI %i\n", ctx.level, " ", (int)midiIdx);
+}
+
+static void lINC_CLOVER_BOX(ScriptContext &ctx) {
+	printf("%*sINC_CLOVER_BOX\n", ctx.level, " ");
+}
+
+static void lSET_USED_INVENTORY(ScriptContext &ctx) {
+	const int32 item = ctx.stream.readByte();
+	printf("%*sSET_USED_INVENTORY %i\n", ctx.level, " ", (int)item);
+}
+
+static void lADD_CHOICE(ScriptContext &ctx) {
+	const int32 choiceIdx = ctx.stream.readSint16LE();
+	printf("%*sADD_CHOICE %i\n", ctx.level, " ", (int)choiceIdx);
+}
+
+static void lASK_CHOICE(ScriptContext &ctx) {
+	const int32 choiceIdx = ctx.stream.readSint16LE();
+	printf("%*sASK_CHOICE %i\n", ctx.level, " ", (int)choiceIdx);
+}
+
+static void lBIG_MESSAGE(ScriptContext &ctx) {
+	const int32 int32x = ctx.stream.readSint16LE();
+	printf("%*sBIG_MESSAGE %i\n", ctx.level, " ", (int)int32x);
+}
+
+static void lINIT_PINGOUIN(ScriptContext &ctx) {
+	const int16 penguinActor = ctx.stream.readByte();
+	printf("%*sINIT_PINGOUIN %i\n", ctx.level, " ", (int)penguinActor);
+}
+
+static void lSET_HOLO_POS(ScriptContext &ctx) {
+	const int32 location = ctx.stream.readByte();
+	printf("%*sSET_HOLO_POS %i\n", ctx.level, " ", (int)location);
+}
+
+static void lCLR_HOLO_POS(ScriptContext &ctx) {
+	const int32 location = ctx.stream.readByte();
+	printf("%*sCLR_HOLO_POS %i\n", ctx.level, " ", (int)location);
+}
+
+static void lADD_FUEL(ScriptContext &ctx) {
+	const int16 value = ctx.stream.readByte();
+	printf("%*sADD_FUEL %i\n", ctx.level, " ", (int)value);
+}
+
+static void lSUB_FUEL(ScriptContext &ctx) {
+	const int16 value = ctx.stream.readByte();
+	printf("%*sSUB_FUEL %i\n", ctx.level, " ", (int)value);
+}
+
+static void lSET_GRM(ScriptContext &ctx) {
+	printf("%*sSET_GRM %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lSAY_MESSAGE(ScriptContext &ctx) {
+	const int32 textEntry = ctx.stream.readSint16LE();
+	printf("%*sSAY_MESSAGE %i\n", ctx.level, " ", (int)textEntry);
+}
+
+static void lSAY_MESSAGE_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 textEntry = ctx.stream.readSint16LE();
+	printf("%*sSAY_MESSAGE_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)textEntry);
+}
+
+static void lFULL_POINT(ScriptContext &ctx) {
+	printf("%*sFULL_POINT\n", ctx.level, " ");
+}
+
+static void lBETA(ScriptContext &ctx) {
+	const int32 newAngle = ctx.stream.readSint16LE();
+	printf("%*sBETA %i\n", ctx.level, " ", (int)newAngle);
+}
+
+static void lGRM_OFF(ScriptContext &ctx) {
+	printf("%*sGRM_OFF\n", ctx.level, " ");
+}
+
+static void lFADE_PAL_RED(ScriptContext &ctx) {
+	printf("%*sFADE_PAL_RED\n", ctx.level, " ");
+}
+
+static void lFADE_ALARM_RED(ScriptContext &ctx) {
+	printf("%*sFADE_ALARM_RED\n", ctx.level, " ");
+}
+
+static void lFADE_ALARM_PAL(ScriptContext &ctx) {
+	printf("%*sFADE_ALARM_PAL\n", ctx.level, " ");
+}
+
+static void lFADE_RED_PAL(ScriptContext &ctx) {
+	printf("%*sFADE_RED_PAL\n", ctx.level, " ");
+}
+
+static void lFADE_RED_ALARM(ScriptContext &ctx) {
+	printf("%*sFADE_RED_ALARM\n", ctx.level, " ");
+}
+
+static void lFADE_PAL_ALARM(ScriptContext &ctx) {
+	printf("%*sFADE_PAL_ALARM\n", ctx.level, " ");
+}
+
+static void lEXPLODE_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	printf("%*sEXPLODE_OBJ %i\n", ctx.level, " ", (int)otherActorIdx);
+}
+
+static void lBUBBLE_ON(ScriptContext &ctx) {
+	printf("%*sBUBBLE_ON\n", ctx.level, " ");
+}
+
+static void lBUBBLE_OFF(ScriptContext &ctx) {
+	printf("%*sBUBBLE_OFF\n", ctx.level, " ");
+}
+
+static void lASK_CHOICE_OBJ(ScriptContext &ctx) {
+	const int32 otherActorIdx = ctx.stream.readByte();
+	const int32 choiceIdx = ctx.stream.readSint16LE();
+	printf("%*sASK_CHOICE_OBJ %i %i\n", ctx.level, " ", (int)otherActorIdx, (int)choiceIdx);
+}
+
+static void lSET_DARK_PAL(ScriptContext &ctx) {
+	printf("%*sSET_DARK_PAL\n", ctx.level, " ");
+}
+
+static void lSET_NORMAL_PAL(ScriptContext &ctx) {
+	printf("%*sSET_NORMAL_PAL\n", ctx.level, " ");
+}
+
+static void lMESSAGE_SENDELL(ScriptContext &ctx) {
+	printf("%*sMESSAGE_SENDELL\n", ctx.level, " ");
+}
+
+static void lANIM_SET(ScriptContext &ctx) {
+	printf("%*sANIM_SET %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lHOLOMAP_TRAJ(ScriptContext &ctx) {
+	printf("%*sHOLOMAP_TRAJ %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lGAME_OVER(ScriptContext &ctx) {
+	printf("%*sGAME_OVER\n", ctx.level, " ");
+}
+
+static void lTHE_END(ScriptContext &ctx) {
+	printf("%*sTHE_END\n", ctx.level, " ");
+}
+
+static void lMIDI_OFF(ScriptContext &ctx) {
+	printf("%*sMIDI_OFF\n", ctx.level, " ");
+}
+
+static void lPLAY_CD_TRACK(ScriptContext &ctx) {
+	printf("%*sPLAY_CD_TRACK %i\n", ctx.level, " ", (int)ctx.stream.readByte());
+}
+
+static void lPROJ_ISO(ScriptContext &ctx) {
+	printf("%*sPROJ_ISO\n", ctx.level, " ");
+}
+
+static void lPROJ_3D(ScriptContext &ctx) {
+	printf("%*sPROJ_3D\n", ctx.level, " ");
+}
+
+static void lTEXT(ScriptContext &ctx) {
+	printf("%*sTEXT %i\n", ctx.level, " ", (int)ctx.stream.readSint16LE());
+}
+
+static void lCLEAR_TEXT(ScriptContext &ctx) {
+	printf("%*sCLEAR_TEXT\n", ctx.level, " ");
+}
+
+static void lBRUTAL_EXIT(ScriptContext &ctx) {
+	printf("%*sBRUTAL_EXIT\n", ctx.level, " ");
+}
+
+static const ScriptFunction lifeScriptFunctions[] = {
+	/*0x00*/ MAPFUNC("END", lEND),
+	/*0x01*/ MAPFUNC("NOP", lNOP),
+	/*0x02*/ MAPFUNC("SNIF", lSNIF),
+	/*0x03*/ MAPFUNC("OFFSET", lOFFSET),
+	/*0x04*/ MAPFUNC("NEVERIF", lNEVERIF),
+	/*0x05*/ MAPFUNC("", lEMPTY), // unused
+	/*0x06*/ MAPFUNC("NO_IF", lNO_IF),
+	/*0x07*/ MAPFUNC("", lEMPTY), // unused
+	/*0x08*/ MAPFUNC("", lEMPTY), // unused
+	/*0x09*/ MAPFUNC("", lEMPTY), // unused
+	/*0x0A*/ MAPFUNC("LABEL", lLABEL),
+	/*0x0B*/ MAPFUNC("RETURN", lRETURN),
+	/*0x0C*/ MAPFUNC("IF", lIF),
+	/*0x0D*/ MAPFUNC("SWIF", lSWIF),
+	/*0x0E*/ MAPFUNC("ONEIF", lONEIF),
+	/*0x0F*/ MAPFUNC("ELSE", lELSE),
+	/*0x10*/ MAPFUNC("ENDIF", lEMPTY),
+	/*0x11*/ MAPFUNC("BODY", lBODY),
+	/*0x12*/ MAPFUNC("BODY_OBJ", lBODY_OBJ),
+	/*0x13*/ MAPFUNC("ANIM", lANIM),
+	/*0x14*/ MAPFUNC("ANIM_OBJ", lANIM_OBJ),
+	/*0x15*/ MAPFUNC("SET_LIFE", lSET_LIFE),
+	/*0x16*/ MAPFUNC("SET_LIFE_OBJ", lSET_LIFE_OBJ),
+	/*0x17*/ MAPFUNC("SET_TRACK", lSET_TRACK),
+	/*0x18*/ MAPFUNC("SET_TRACK_OBJ", lSET_TRACK_OBJ),
+	/*0x19*/ MAPFUNC("MESSAGE", lMESSAGE),
+	/*0x1A*/ MAPFUNC("FALLABLE", lFALLABLE),
+	/*0x1B*/ MAPFUNC("SET_DIRMODE", lSET_DIRMODE),
+	/*0x1C*/ MAPFUNC("SET_DIRMODE_OBJ", lSET_DIRMODE_OBJ),
+	/*0x1D*/ MAPFUNC("CAM_FOLLOW", lCAM_FOLLOW),
+	/*0x1E*/ MAPFUNC("SET_BEHAVIOUR", lSET_BEHAVIOUR),
+	/*0x1F*/ MAPFUNC("SET_FLAG_CUBE", lSET_FLAG_CUBE),
+	/*0x20*/ MAPFUNC("COMPORTEMENT", lCOMPORTEMENT),
+	/*0x21*/ MAPFUNC("SET_COMPORTEMENT", lSET_COMPORTEMENT),
+	/*0x22*/ MAPFUNC("SET_COMPORTEMENT_OBJ", lSET_COMPORTEMENT_OBJ),
+	/*0x23*/ MAPFUNC("END_COMPORTEMENT", lEND_COMPORTEMENT),
+	/*0x24*/ MAPFUNC("SET_FLAG_GAME", lSET_FLAG_GAME),
+	/*0x25*/ MAPFUNC("KILL_OBJ", lKILL_OBJ),
+	/*0x26*/ MAPFUNC("SUICIDE", lSUICIDE),
+	/*0x27*/ MAPFUNC("USE_ONE_LITTLE_KEY", lUSE_ONE_LITTLE_KEY),
+	/*0x28*/ MAPFUNC("GIVE_GOLD_PIECES", lGIVE_GOLD_PIECES),
+	/*0x29*/ MAPFUNC("END_LIFE", lEND_LIFE),
+	/*0x2A*/ MAPFUNC("STOP_L_TRACK", lSTOP_L_TRACK),
+	/*0x2B*/ MAPFUNC("RESTORE_L_TRACK", lRESTORE_L_TRACK),
+	/*0x2C*/ MAPFUNC("MESSAGE_OBJ", lMESSAGE_OBJ),
+	/*0x2D*/ MAPFUNC("INC_CHAPTER", lINC_CHAPTER),
+	/*0x2E*/ MAPFUNC("FOUND_OBJECT", lFOUND_OBJECT),
+	/*0x2F*/ MAPFUNC("SET_DOOR_LEFT", lSET_DOOR_LEFT),
+	/*0x30*/ MAPFUNC("SET_DOOR_RIGHT", lSET_DOOR_RIGHT),
+	/*0x31*/ MAPFUNC("SET_DOOR_UP", lSET_DOOR_UP),
+	/*0x32*/ MAPFUNC("SET_DOOR_DOWN", lSET_DOOR_DOWN),
+	/*0x33*/ MAPFUNC("GIVE_BONUS", lGIVE_BONUS),
+	/*0x34*/ MAPFUNC("CHANGE_CUBE", lCHANGE_CUBE),
+	/*0x35*/ MAPFUNC("OBJ_COL", lOBJ_COL),
+	/*0x36*/ MAPFUNC("BRICK_COL", lBRICK_COL),
+	/*0x37*/ MAPFUNC("OR_IF", lOR_IF),
+	/*0x38*/ MAPFUNC("INVISIBLE", lINVISIBLE),
+	/*0x39*/ MAPFUNC("ZOOM", lZOOM),
+	/*0x3A*/ MAPFUNC("POS_POINT", lPOS_POINT),
+	/*0x3B*/ MAPFUNC("SET_MAGIC_LEVEL", lSET_MAGIC_LEVEL),
+	/*0x3C*/ MAPFUNC("SUB_MAGIC_POINT", lSUB_MAGIC_POINT),
+	/*0x3D*/ MAPFUNC("SET_LIFE_POINT_OBJ", lSET_LIFE_POINT_OBJ),
+	/*0x3E*/ MAPFUNC("SUB_LIFE_POINT_OBJ", lSUB_LIFE_POINT_OBJ),
+	/*0x3F*/ MAPFUNC("HIT_OBJ", lHIT_OBJ),
+	/*0x40*/ MAPFUNC("PLAY_FLA", lPLAY_FLA),
+	/*0x41*/ MAPFUNC("PLAY_MIDI", lPLAY_MIDI),
+	/*0x42*/ MAPFUNC("INC_CLOVER_BOX", lINC_CLOVER_BOX),
+	/*0x43*/ MAPFUNC("SET_USED_INVENTORY", lSET_USED_INVENTORY),
+	/*0x44*/ MAPFUNC("ADD_CHOICE", lADD_CHOICE),
+	/*0x45*/ MAPFUNC("ASK_CHOICE", lASK_CHOICE),
+	/*0x46*/ MAPFUNC("BIG_MESSAGE", lBIG_MESSAGE),
+	/*0x47*/ MAPFUNC("INIT_PINGOUIN", lINIT_PINGOUIN),
+	/*0x48*/ MAPFUNC("SET_HOLO_POS", lSET_HOLO_POS),
+	/*0x49*/ MAPFUNC("CLR_HOLO_POS", lCLR_HOLO_POS),
+	/*0x4A*/ MAPFUNC("ADD_FUEL", lADD_FUEL),
+	/*0x4B*/ MAPFUNC("SUB_FUEL", lSUB_FUEL),
+	/*0x4C*/ MAPFUNC("SET_GRM", lSET_GRM),
+	/*0x4D*/ MAPFUNC("SAY_MESSAGE", lSAY_MESSAGE),
+	/*0x4E*/ MAPFUNC("SAY_MESSAGE_OBJ", lSAY_MESSAGE_OBJ),
+	/*0x4F*/ MAPFUNC("FULL_POINT", lFULL_POINT),
+	/*0x50*/ MAPFUNC("BETA", lBETA),
+	/*0x51*/ MAPFUNC("GRM_OFF", lGRM_OFF),
+	/*0x52*/ MAPFUNC("FADE_PAL_RED", lFADE_PAL_RED),
+	/*0x53*/ MAPFUNC("FADE_ALARM_RED", lFADE_ALARM_RED),
+	/*0x54*/ MAPFUNC("FADE_ALARM_PAL", lFADE_ALARM_PAL),
+	/*0x55*/ MAPFUNC("FADE_RED_PAL", lFADE_RED_PAL),
+	/*0x56*/ MAPFUNC("FADE_RED_ALARM", lFADE_RED_ALARM),
+	/*0x57*/ MAPFUNC("FADE_PAL_ALARM", lFADE_PAL_ALARM),
+	/*0x58*/ MAPFUNC("EXPLODE_OBJ", lEXPLODE_OBJ),
+	/*0x59*/ MAPFUNC("BUBBLE_ON", lBUBBLE_ON),
+	/*0x5A*/ MAPFUNC("BUBBLE_OFF", lBUBBLE_OFF),
+	/*0x5B*/ MAPFUNC("ASK_CHOICE_OBJ", lASK_CHOICE_OBJ),
+	/*0x5C*/ MAPFUNC("SET_DARK_PAL", lSET_DARK_PAL),
+	/*0x5D*/ MAPFUNC("SET_NORMAL_PAL", lSET_NORMAL_PAL),
+	/*0x5E*/ MAPFUNC("MESSAGE_SENDELL", lMESSAGE_SENDELL),
+	/*0x5F*/ MAPFUNC("ANIM_SET", lANIM_SET),
+	/*0x60*/ MAPFUNC("HOLOMAP_TRAJ", lHOLOMAP_TRAJ),
+	/*0x61*/ MAPFUNC("GAME_OVER", lGAME_OVER),
+	/*0x62*/ MAPFUNC("THE_END", lTHE_END),
+	/*0x63*/ MAPFUNC("MIDI_OFF", lMIDI_OFF),
+	/*0x64*/ MAPFUNC("PLAY_CD_TRACK", lPLAY_CD_TRACK),
+	/*0x65*/ MAPFUNC("PROJ_ISO", lPROJ_ISO),
+	/*0x66*/ MAPFUNC("PROJ_3D", lPROJ_3D),
+	/*0x67*/ MAPFUNC("TEXT", lTEXT),
+	/*0x68*/ MAPFUNC("CLEAR_TEXT", lCLEAR_TEXT),
+	/*0x69*/ MAPFUNC("BRUTAL_EXIT", lBRUTAL_EXIT)
+};
+
+static int decompileLBA1MoveScript(int actorIdx, const uint8 *data, int16 size) {
+	Common::MemoryReadStream stream(data, size);
+	ScriptContext ctx{stream, {}, initialLevel, 0};
+
+	printf("Actor %i\n", actorIdx);
+
+	while (ctx.stream.pos() < ctx.stream.size()) {
+		const byte scriptOpcode = ctx.stream.readByte();
+		if (scriptOpcode < ARRAYSIZE(moveScriptFunctions)) {
+			moveScriptFunctions[scriptOpcode].function(ctx);
+		} else {
+			fprintf(stderr, "Actor %d with wrong offset/opcode - Offset: %d/%d (opcode: %u)", actorIdx, (int)ctx.stream.pos() - 1, (int)ctx.stream.size(), scriptOpcode);
+			return 1;
+		}
+	};
+
+	return 0;
+}
+
+static int decompileLBA1LifeScript(int actorIdx, const uint8 *data, int16 size) {
+	Common::MemoryReadStream stream(data, size);
+	ScriptContext ctx{stream, {}, initialLevel, 0, true};
+
+	printf("Actor %i\n", actorIdx);
+
+	printf("%*sCOMPORTMENT main\n", ctx.level, " ");
+	ctx.level += indentWidth;
+	while (ctx.stream.pos() < ctx.stream.size()) {
+		const byte scriptOpcode = ctx.stream.readByte();
+		if (scriptOpcode < ARRAYSIZE(lifeScriptFunctions)) {
+			if (scriptOpcode && !ctx.comportement) {
+				++ctx.comportmentId;
+				printf("%*sCOMPORTEMENT %i\n", ctx.level, " ", ctx.comportmentId);
+				ctx.level += indentWidth;
+				ctx.comportement = true;
+			}
+			lifeScriptFunctions[scriptOpcode].function(ctx);
+			while (!ctx.offsets.empty()) {
+				if (ctx.stream.pos() == ctx.offsets.back()) {
+					ctx.level -= indentWidth;
+					printf("%*sENDIF\n", ctx.level, " ");
+					ctx.offsets.pop_back();
+				} else {
+					break;
+				}
+			}
+		} else {
+			fprintf(stderr, "Actor %d with wrong offset/opcode - Offset: %d/%d (opcode: %u)", actorIdx, (int)ctx.stream.pos() - 1, (int)ctx.stream.size(), scriptOpcode);
+			return 1;
+		}
+	};
+
+	return 0;
+}
+
+int decompileLBA1(const uint8 *data, int size) {
+	Common::MemoryReadStream stream(data, size);
+	uint8_t sceneTextBank = stream.readByte();
+	uint8_t currentGameOverScene = stream.readByte();
+	stream.skip(4);
+	int16 _alphaLight = (int16)stream.readUint16LE();
+	int16 _betaLight = (int16)stream.readUint16LE();
+
+	uint16 sampleAmbiance[4];
+	uint16 sampleRepeat[4];
+	uint16 sampleRound[4];
+
+	for (int i = 0; i < 4; ++i) {
+		sampleAmbiance[i] = stream.readUint16LE();
+		sampleRepeat[i] = stream.readUint16LE();
+		sampleRound[i] = stream.readUint16LE();
+	}
+
+	uint16 sampleMinDelay = stream.readUint16LE();
+	uint16 sampleMinDelayRnd = stream.readUint16LE();
+
+	uint8 sceneMusic = stream.readByte();
+
+	int16 sceneHeroPosx = (int16)stream.readUint16LE();
+	int16 sceneHeroPosy = (int16)stream.readUint16LE();
+	int16 sceneHeroPosz = (int16)stream.readUint16LE();
+
+	int16 moveScriptSize = (int16)stream.readUint16LE();
+	const uint8 *moveScript = data + stream.pos();
+	stream.skip(moveScriptSize);
+	decompileLBA1MoveScript(0, moveScript, moveScriptSize);
+
+	int16 lifeScriptSize = (int16)stream.readUint16LE();
+	const uint8 *lifeScript = data + stream.pos();
+	stream.skip(lifeScriptSize);
+	decompileLBA1LifeScript(0, lifeScript, lifeScriptSize);
+
+	int16 sceneNumActors = (int16)stream.readUint16LE();
+	int cnt = 1;
+	for (int32 a = 1; a < sceneNumActors; a++, cnt++) {
+		uint16 staticflags = stream.readUint16LE();
+		uint16 body = stream.readUint16LE();
+		uint8 genBody = stream.readByte();
+		uint8 genAnim = stream.readByte();
+		int16 sprite = stream.readSint16LE();
+		int16 posx = stream.readSint16LE();
+		int16 posy = stream.readSint16LE();
+		int16 posz = stream.readSint16LE();
+		uint8 strengthOfHit = stream.readByte();
+		uint16 bonusflags = stream.readUint16LE();
+		int16 beta = stream.readSint16LE();
+		int16 speed = stream.readSint16LE();
+		uint16 controlMode = stream.readUint16LE();
+		int16 cropLeft = stream.readSint16LE();
+		int16 cropTop = stream.readSint16LE();
+		int16 cropRight = stream.readSint16LE();
+		int16 cropBottom = stream.readSint16LE();
+		uint8 bonusAmount = stream.readByte();
+		uint8 talkColor = stream.readByte();
+		uint8 armor = stream.readByte();
+		uint8 lifePoints = stream.readByte();
+
+		moveScriptSize = (int16)stream.readUint16LE();
+		moveScript = data + stream.pos();
+		stream.skip(moveScriptSize);
+		decompileLBA1MoveScript(a, moveScript, moveScriptSize);
+
+		lifeScriptSize = (int16)stream.readUint16LE();
+		lifeScript = data + stream.pos();
+		stream.skip(lifeScriptSize);
+		decompileLBA1LifeScript(a, lifeScript, lifeScriptSize);
+	}
+
+	int16 sceneNumZones = stream.readSint16LE();
+	for (int16 i = 0; i < sceneNumZones; i++) {
+		int16 zoneminsx = stream.readSint16LE();
+		int16 zoneminsy = stream.readSint16LE();
+		int16 zoneminsz = stream.readSint16LE();
+
+		int16 zonemaxsx = stream.readSint16LE();
+		int16 zonemaxsy = stream.readSint16LE();
+		int16 zonemaxsz = stream.readSint16LE();
+
+		uint16 zonetype = stream.readUint16LE();
+		int16 zonenum = stream.readSint16LE();
+
+		int16 info0 = stream.readSint16LE();
+		int16 info1 = stream.readSint16LE();
+		int16 info2 = stream.readSint16LE();
+		int16 info3 = stream.readSint16LE();
+	}
+
+	uint16 sceneNumTracks = stream.readUint16LE();
+	for (uint16 i = 0; i < sceneNumTracks; i++) {
+		int16 pointx = stream.readSint16LE();
+		int16 pointy = stream.readSint16LE();
+		int16 pointz = stream.readSint16LE();
+	}
+
+	if (stream.err()) {
+		return 1;
+	}
+	return 0;
+}
diff --git a/engines/twine/lba1.h b/engines/twine/lba1.h
new file mode 100644
index 00000000..e50ef31f
--- /dev/null
+++ b/engines/twine/lba1.h
@@ -0,0 +1,29 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TWINE_LBA1_H
+#define TWINE_LBA1_H
+
+#include "common/scummsys.h"
+
+int decompileLBA1(const uint8 *data, int size);
+
+#endif
diff --git a/engines/twine/lba2.cpp b/engines/twine/lba2.cpp
new file mode 100644
index 00000000..1b10b4b3
--- /dev/null
+++ b/engines/twine/lba2.cpp
@@ -0,0 +1,39 @@
+/* ScummVM Tools
+ *
+ * ScummVM Tools is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/twine/lba2.h"
+#include "common/memstream.h"
+
+// static int decompileLBA2MoveScript(int actor, const uint8 *moveScript, int16 moveScriptSize) {
+// 	return 0;
+// }
+
+// static int decompileLBA2LifeScript(int actor, const uint8 *moveScript, int16 moveScriptSize) {
+// 	return 0;
+// }
+
+int decompileLBA2(const uint8 *data, int size) {
+	Common::MemoryReadStream stream(data, size);
+	if (stream.err()) {
+		return 1;
+	}
+	return 0;
+}
diff --git a/engines/twine/lba2.h b/engines/twine/lba2.h
new file mode 100644
index 00000000..7bf39076
--- /dev/null
+++ b/engines/twine/lba2.h
@@ -0,0 +1,29 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TWINE_LBA2_H
+#define TWINE_LBA2_H
+
+#include "common/scummsys.h"
+
+int decompileLBA2(const uint8 *data, int size);
+
+#endif




More information about the Scummvm-git-logs mailing list