From 57d5949d8498cb1763679f494d5e78b50abde8cf Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <madebr@users.noreply.github.com>
Date: Sat, 3 Feb 2024 02:03:52 +0100
Subject: [PATCH] Implement some Act1State functions (#520)

* Implement some Act1State functions

* ci: push fix commits to pr

* ci fix

* Show diffs generated by clang-format

* Run clang-format

* Fix naming

* re-use _countof + add parentheses

* Fix naming

* Use MxS32

* Annotate Act1State::NamedPlane::~NamedPlane

* Apply suggestions

* Read and Write Mx3DPointFloat's

* Annotations, spacing

* Add Mx3DPointFloat copy ctor, match some functions

* Fix WriteVector3

* Adding more spacing for readability

* Use MxResult as a return type for Serialize

---------

Co-authored-by: Christian Semmler <mail@csemmler.com>
---
 .github/workflows/format.yml                  |   3 +-
 CMakeLists.txt                                |   5 +-
 LEGO1/lego/legoomni/include/act1state.h       | 102 ++++++-
 LEGO1/lego/legoomni/include/legoutil.h        |  26 ++
 LEGO1/lego/legoomni/src/act1/act1state.cpp    | 264 +++++++++++++++++-
 LEGO1/lego/legoomni/src/common/legoutil.cpp   |  20 ++
 .../src/infocenter/elevatorbottom.cpp         |   2 +-
 LEGO1/lego/sources/misc/legostorage.h         |  40 +++
 LEGO1/mxgeometry/mxgeometry3d.h               |   3 +
 util/decomp.h                                 |   2 +-
 10 files changed, 433 insertions(+), 34 deletions(-)

diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 10e85e0d..4cfb5d9e 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -14,10 +14,9 @@ jobs:
       run: |
         find LEGO1 ISLE -iname '*.h' -o -iname '*.cpp' | xargs \
         pipx run "clang-format>=17,<18" \
-          --Werror \
-          --dry-run \
           --style=file \
           -i
+        git diff --exit-code
 
   python-format:
     name: 'Python'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb509e5b..dfcabe6f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -151,7 +151,7 @@ add_library(misc STATIC
 )
 register_lego1_target(misc)
 set_property(TARGET misc PROPERTY ARCHIVE_OUTPUT_NAME "misc$<$<CONFIG:Debug>:d>")
-target_include_directories(misc PRIVATE "${CMAKE_SOURCE_DIR}/LEGO1/omni/include" "${CMAKE_SOURCE_DIR}/util")
+target_include_directories(misc PRIVATE "${CMAKE_SOURCE_DIR}/LEGO1/omni/include" "${CMAKE_SOURCE_DIR}/LEGO1" "${CMAKE_SOURCE_DIR}/LEGO1/realtime" "${CMAKE_SOURCE_DIR}/util")
 target_link_libraries(misc PRIVATE)
 
 add_library(3dmanager STATIC
@@ -249,7 +249,7 @@ add_library(omni STATIC
 )
 register_lego1_target(omni)
 set_property(TARGET omni PROPERTY ARCHIVE_OUTPUT_NAME "omni$<$<CONFIG:Debug>:d>")
-target_include_directories(omni PRIVATE "${CMAKE_SOURCE_DIR}/LEGO1/omni/include" "${CMAKE_SOURCE_DIR}/LEGO1" "${CMAKE_SOURCE_DIR}/util")
+target_include_directories(omni PRIVATE "${CMAKE_SOURCE_DIR}/LEGO1/omni/include" "${CMAKE_SOURCE_DIR}/LEGO1" "${CMAKE_SOURCE_DIR}/LEGO1" "${CMAKE_SOURCE_DIR}/util")
 target_link_libraries(omni PRIVATE dsound winmm FLIC::FLIC Smacker::Smacker)
 
 add_library(lego1 SHARED
@@ -393,6 +393,7 @@ target_include_directories(lego1 PUBLIC "${CMAKE_SOURCE_DIR}/LEGO1")
 target_include_directories(lego1 PUBLIC "${CMAKE_SOURCE_DIR}/LEGO1/omni/include")
 target_include_directories(lego1 PUBLIC "${CMAKE_SOURCE_DIR}/LEGO1/lego/sources")
 target_include_directories(lego1 PUBLIC "${CMAKE_SOURCE_DIR}/LEGO1/lego/legoomni/include")
+target_include_directories(lego1 PUBLIC "${CMAKE_SOURCE_DIR}/LEGO1/realtime")
 
 # Link libraries
 target_link_libraries(lego1 PRIVATE tglrl viewmanager realtime mxdirectx roi FLIC::FLIC Vec::Vec dinput dxguid misc 3dmanager omni)
diff --git a/LEGO1/lego/legoomni/include/act1state.h b/LEGO1/lego/legoomni/include/act1state.h
index 1ad86c64..8b1a09b1 100644
--- a/LEGO1/lego/legoomni/include/act1state.h
+++ b/LEGO1/lego/legoomni/include/act1state.h
@@ -2,11 +2,19 @@
 #define ACT1STATE_H
 
 #include "legostate.h"
+#include "legoutil.h"
+#include "roi/legoroi.h"
 
 // VTABLE: LEGO1 0x100d7028
 // SIZE 0x26c
 class Act1State : public LegoState {
 public:
+	enum {
+		e_unk953 = 953,
+		e_unk954 = 954,
+		e_unk955 = 955,
+	};
+
 	Act1State();
 
 	// FUNCTION: LEGO1 0x100338a0
@@ -25,29 +33,93 @@ public:
 	MxBool SetFlag() override;                          // vtable+0x18
 	MxResult VTable0x1c(LegoFile* p_legoFile) override; // vtable+0x1c
 
-	inline void SetUnknown18(MxU32 p_unk0x18) { m_unk0x18 = p_unk0x18; }
-	inline MxU32 GetUnknown18() { return m_unk0x18; }
-	inline MxU32 GetUnknown1c() { return m_unk0x1c; }
-	inline MxS16 GetUnknown21() { return m_unk0x21; }
+	inline void SetUnknown18(MxU32 p_unk0x18) { m_unk0x018 = p_unk0x18; }
+	inline MxU32 GetUnknown18() { return m_unk0x018; }
+	inline MxU32 GetUnknown1c() { return m_unk0x01c; }
+	inline MxS16 GetUnknown21() { return m_unk0x021; }
 
-	inline void SetUnknown1c(MxU32 p_unk0x1c) { m_unk0x1c = p_unk0x1c; }
-	inline void SetUnknown21(MxS16 p_unk0x21) { m_unk0x21 = p_unk0x21; }
+	inline void SetUnknown1c(MxU32 p_unk0x1c) { m_unk0x01c = p_unk0x1c; }
+	inline void SetUnknown21(MxS16 p_unk0x21) { m_unk0x021 = p_unk0x21; }
 
 	void FUN_10034d00();
 
 	// SYNTHETIC: LEGO1 0x10033960
 	// Act1State::`scalar deleting destructor'
 
+	// SIZE 0x4c
+	class NamedPlane {
+	public:
+		// FUNCTION: LEGO1 0x10033800
+		NamedPlane() {}
+
+		inline void SetName(const char* p_name) { m_name = p_name; }
+		inline const MxString* GetName() const { return &m_name; }
+
+		// FUNCTION: LEGO1 0x100344d0
+		MxResult Serialize(LegoFile* p_file)
+		{
+			if (p_file->IsWriteMode()) {
+				p_file->FUN_10006030(m_name);
+				p_file->WriteVector3(m_point1);
+				p_file->WriteVector3(m_point2);
+				p_file->WriteVector3(m_point3);
+			}
+			else if (p_file->IsReadMode()) {
+				p_file->ReadString(m_name);
+				p_file->ReadVector3(m_point1);
+				p_file->ReadVector3(m_point2);
+				p_file->ReadVector3(m_point3);
+			}
+
+			return SUCCESS;
+		}
+
+	private:
+		MxString m_name;         // 0x00
+		Mx3DPointFloat m_point1; // 0x10
+		Mx3DPointFloat m_point2; // 0x24
+		Mx3DPointFloat m_point3; // 0x38
+	};
+
 protected:
-	undefined m_unk0x08[0x10]; // 0x08
-	MxU32 m_unk0x18;           // 0x18
-	undefined2 m_unk0x1c;      // 0x1c
-	undefined m_unk0x1e;       // 0x1e
-	undefined m_unk0x1f;       // 0x1f
-	undefined m_unk0x20;       // 0x20
-	MxBool m_unk0x21;          // 0x21
-	undefined m_unk0x22;       // 0x22
-							   // TODO
+	MxS32* m_unk0x008; // 0x008
+	// FIXME: count for m_unk0x008
+	MxS16 m_unk0x00c;         // 0x00c
+	undefined2 m_unk0x00e;    // 0x00e
+	undefined2 m_unk0x010;    // 0x010
+	undefined m_unk0x012;     // 0x012
+	MxS32 m_unk0x014;         // 0x014
+	MxU32 m_unk0x018;         // 0x018
+	MxU16 m_unk0x01c;         // 0x01c
+	undefined m_unk0x01e;     // 0x01e
+	undefined m_unk0x01f;     // 0x01f
+	undefined m_unk0x020;     // 0x020
+	undefined m_unk0x021;     // 0x021
+	undefined m_unk0x022;     // 0x022
+	undefined m_unk0x023;     // 0x023
+	NamedPlane m_unk0x024;    // 0x024
+	NamedPlane m_unk0x070;    // 0x070
+	NamedPlane m_unk0x0bc;    // 0x0bc
+	NamedPlane m_unk0x108;    // 0x108
+	NamedTexture* m_unk0x154; // 0x154
+	NamedTexture* m_unk0x158; // 0x158
+	NamedTexture* m_unk0x15c; // 0x15c
+	MxCore* m_unk0x160;       // 0x160
+	NamedPlane m_unk0x164;    // 0x164
+	NamedTexture* m_unk0x1b0; // 0x1b0
+	NamedTexture* m_unk0x1b4; // 0x1b4
+	MxCore* m_unk0x1b8;       // 0x1b8
+	NamedPlane m_unk0x1bc;    // 0x1bc
+	NamedTexture* m_unk0x208; // 0x208
+	MxCore* m_unk0x20c;       // 0x20c
+	NamedPlane m_unk0x210;    // 0x210
+	NamedTexture* m_unk0x25c; // 0x25c
+	NamedTexture* m_unk0x260; // 0x260
+	NamedTexture* m_unk0x264; // 0x264
+	MxCore* m_unk0x268;       // 0x268
 };
 
+// FUNCTION: LEGO1 0x10033a70
+// Act1State::NamedPlane::~NamedPlane
+
 #endif // ACT1STATE_H
diff --git a/LEGO1/lego/legoomni/include/legoutil.h b/LEGO1/lego/legoomni/include/legoutil.h
index 922a2c60..696a1cc4 100644
--- a/LEGO1/lego/legoomni/include/legoutil.h
+++ b/LEGO1/lego/legoomni/include/legoutil.h
@@ -2,6 +2,9 @@
 #define LEGOUTIL_H
 
 #include "extra.h"
+#include "misc/legostorage.h"
+#include "misc/legotexture.h"
+#include "mxstring.h"
 #include "mxtypes.h"
 #include "mxutil.h"
 
@@ -11,6 +14,23 @@ class MxAtomId;
 class LegoEntity;
 class LegoAnimPresenter;
 
+class LegoTexture;
+
+// SIZE 0x14
+class NamedTexture {
+public:
+	~NamedTexture() { delete m_texture; }
+
+	// FUNCTION: LEGO1 0x1003f920
+	const MxString* GetName() const { return &m_name; }
+
+	LegoTexture* GetTexture() { return m_texture; }
+
+private:
+	MxString m_name;        // 0x00
+	LegoTexture* m_texture; // 0x04
+};
+
 void FUN_1003e050(LegoAnimPresenter* p_presenter);
 Extra::ActionType MatchActionString(const char*);
 void InvokeAction(Extra::ActionType p_actionId, MxAtomId& p_pAtom, int p_targetEntityId, LegoEntity* p_sender);
@@ -20,5 +40,11 @@ void FUN_1003ef00(MxBool);
 void SetAppCursor(WPARAM p_wparam);
 MxBool FUN_1003ef60();
 MxBool RemoveFromWorld(MxAtomId& p_atomId1, MxS32 p_id1, MxAtomId& p_atomId2, MxS32 p_id2);
+NamedTexture* ReadNamedTexture(LegoFile* p_file);
+void FUN_1003f540(LegoFile* p_file, const char* p_filename);
+void WriteNamedTexture(LegoFile* p_file, NamedTexture* p_texture);
+
+// SYNTHETIC: LEGO1 0x10034b40
+// LegoTexture::`scalar deleting destructor'
 
 #endif // LEGOUTIL_H
diff --git a/LEGO1/lego/legoomni/src/act1/act1state.cpp b/LEGO1/lego/legoomni/src/act1/act1state.cpp
index cd39f1e2..b4696e2e 100644
--- a/LEGO1/lego/legoomni/src/act1/act1state.cpp
+++ b/LEGO1/lego/legoomni/src/act1/act1state.cpp
@@ -1,30 +1,268 @@
 #include "act1state.h"
 
+DECOMP_SIZE_ASSERT(Act1State, 0x26c)
+DECOMP_SIZE_ASSERT(Act1State::NamedPlane, 0x4c)
+
+// GLOBAL: ISLE 0x100f37f0
+MxS32 g_unk0x100f37f0[] = {
+	Act1State::e_unk953,
+	Act1State::e_unk954,
+	Act1State::e_unk955,
+};
+
 // STUB: LEGO1 0x100334b0
-Act1State::Act1State()
+Act1State::Act1State() : m_unk0x00c(0), m_unk0x00e(0), m_unk0x008(NULL), m_unk0x010(0)
 {
-	// TODO
-	m_unk0x1e = 0;
-	m_unk0x18 = 1;
-	m_unk0x20 = 0;
-	m_unk0x1f = 0;
-	m_unk0x21 = TRUE;
-	m_unk0x22 = 0;
-	m_unk0x1c = 1;
+	m_unk0x01e = 0;
+	m_unk0x018 = 1;
+	m_unk0x010 = 0;
+	m_unk0x020 = 0;
+	m_unk0x00e = 0;
+	m_unk0x01f = 0;
+	m_unk0x008 = g_unk0x100f37f0;
+	m_unk0x014 = -1;
+	m_unk0x022 = 0;
+	m_unk0x154 = NULL;
+	m_unk0x158 = NULL;
+	m_unk0x15c = NULL;
+	m_unk0x160 = NULL;
+	m_unk0x1b0 = NULL;
+	m_unk0x021 = 1;
+	m_unk0x01c = 1;
+	m_unk0x00c = _countof(g_unk0x100f37f0);
+	m_unk0x1b4 = NULL;
+	m_unk0x1b8 = NULL;
+	m_unk0x208 = NULL;
+	m_unk0x20c = NULL;
+	m_unk0x25c = NULL;
+	m_unk0x260 = NULL;
+	m_unk0x264 = NULL;
+	m_unk0x268 = NULL;
+	SetFlag();
 }
 
-// STUB: LEGO1 0x10033ac0
+// FUNCTION: LEGO1 0x10033ac0
 MxResult Act1State::VTable0x1c(LegoFile* p_legoFile)
 {
+	if (p_legoFile->IsWriteMode()) {
+		p_legoFile->FUN_10006030(ClassName());
+	}
+
+	m_unk0x024.Serialize(p_legoFile);
+	m_unk0x070.Serialize(p_legoFile);
+	m_unk0x0bc.Serialize(p_legoFile);
+	m_unk0x108.Serialize(p_legoFile);
+	m_unk0x164.Serialize(p_legoFile);
+	m_unk0x1bc.Serialize(p_legoFile);
+	m_unk0x210.Serialize(p_legoFile);
+
+	if (p_legoFile->IsWriteMode()) {
+		if (m_unk0x108.GetName()->Compare("") != 0) {
+			if (m_unk0x154) {
+				WriteNamedTexture(p_legoFile, m_unk0x154);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "chwind.gif");
+			}
+			if (m_unk0x158) {
+				WriteNamedTexture(p_legoFile, m_unk0x158);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "chjetl.gif");
+			}
+			if (m_unk0x15c) {
+				WriteNamedTexture(p_legoFile, m_unk0x15c);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "chjetr.gif");
+			}
+		}
+		if (m_unk0x164.GetName()->Compare("") != 0) {
+			if (m_unk0x1b0) {
+				WriteNamedTexture(p_legoFile, m_unk0x1b0);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "jsfrnt.gif");
+			}
+			if (m_unk0x1b4) {
+				WriteNamedTexture(p_legoFile, m_unk0x1b4);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "jswnsh.gif");
+			}
+		}
+		if (m_unk0x1bc.GetName()->Compare("") != 0) {
+			if (m_unk0x208) {
+				WriteNamedTexture(p_legoFile, m_unk0x208);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "dbfrfn.gif");
+			}
+		}
+		if (m_unk0x210.GetName()->Compare("") != 0) {
+			if (m_unk0x25c) {
+				WriteNamedTexture(p_legoFile, m_unk0x25c);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "rcfrnt.gif");
+			}
+			if (m_unk0x260) {
+				WriteNamedTexture(p_legoFile, m_unk0x260);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "rcback.gif");
+			}
+			if (m_unk0x264) {
+				WriteNamedTexture(p_legoFile, m_unk0x264);
+			}
+			else {
+				FUN_1003f540(p_legoFile, "rctail.gif");
+			}
+		}
+
+		p_legoFile->Write(&m_unk0x010, sizeof(undefined2));
+		p_legoFile->Write(&m_unk0x022, sizeof(undefined));
+	}
+	else if (p_legoFile->IsReadMode()) {
+		if (m_unk0x108.GetName()->Compare("") != 0) {
+			m_unk0x154 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x154 == NULL) {
+				return FAILURE;
+			}
+
+			m_unk0x158 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x158 == NULL) {
+				return FAILURE;
+			}
+
+			m_unk0x15c = ReadNamedTexture(p_legoFile);
+			if (m_unk0x15c == NULL) {
+				return FAILURE;
+			}
+		}
+		if (m_unk0x164.GetName()->Compare("") != 0) {
+			m_unk0x1b0 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x1b0 == NULL) {
+				return FAILURE;
+			}
+
+			m_unk0x1b4 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x1b4 == NULL) {
+				return FAILURE;
+			}
+		}
+		if (m_unk0x1bc.GetName()->Compare("") != 0) {
+			m_unk0x208 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x208 == NULL) {
+				return FAILURE;
+			}
+		}
+		if (m_unk0x210.GetName()->Compare("") != 0) {
+			m_unk0x25c = ReadNamedTexture(p_legoFile);
+			if (m_unk0x25c == NULL) {
+				return FAILURE;
+			}
+
+			m_unk0x260 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x260 == NULL) {
+				return FAILURE;
+			}
+
+			m_unk0x264 = ReadNamedTexture(p_legoFile);
+			if (m_unk0x264 == NULL) {
+				return FAILURE;
+			}
+		}
+
+		p_legoFile->Read(&m_unk0x010, sizeof(undefined2));
+		p_legoFile->Read(&m_unk0x022, sizeof(undefined));
+	}
+
 	// TODO
 	return SUCCESS;
 }
 
-// STUB: LEGO1 0x100346d0
+// FUNCTION: LEGO1 0x100346d0
 MxBool Act1State::SetFlag()
 {
-	// TODO
-	return FALSE;
+	m_unk0x024.SetName("");
+	m_unk0x070.SetName("");
+	m_unk0x0bc.SetName("");
+	m_unk0x022 = 0;
+	m_unk0x108.SetName("");
+
+	if (m_unk0x154) {
+		delete m_unk0x154;
+		m_unk0x154 = NULL;
+	}
+
+	if (m_unk0x158) {
+		delete m_unk0x158;
+		m_unk0x158 = NULL;
+	}
+
+	if (m_unk0x15c) {
+		delete m_unk0x15c;
+		m_unk0x15c = NULL;
+	}
+
+	if (m_unk0x160) {
+		delete m_unk0x160;
+		m_unk0x160 = NULL;
+	}
+
+	m_unk0x164.SetName("");
+
+	if (m_unk0x1b0) {
+		delete m_unk0x1b0;
+		m_unk0x1b0 = NULL;
+	}
+
+	if (m_unk0x1b4) {
+		delete m_unk0x1b4;
+		m_unk0x1b4 = NULL;
+	}
+
+	if (m_unk0x1b8) {
+		delete m_unk0x1b8;
+		m_unk0x1b8 = NULL;
+	}
+
+	m_unk0x1bc.SetName("");
+
+	if (m_unk0x208) {
+		delete m_unk0x208;
+		m_unk0x208 = NULL;
+	}
+
+	if (m_unk0x20c) {
+		delete m_unk0x20c;
+		m_unk0x20c = NULL;
+	}
+
+	m_unk0x210.SetName("");
+
+	if (m_unk0x25c) {
+		delete m_unk0x25c;
+		m_unk0x25c = NULL;
+	}
+
+	if (m_unk0x260) {
+		delete m_unk0x260;
+		m_unk0x260 = NULL;
+	}
+
+	if (m_unk0x264) {
+		delete m_unk0x264;
+		m_unk0x264 = NULL;
+	}
+
+	if (m_unk0x268) {
+		delete m_unk0x268;
+		m_unk0x268 = NULL;
+	}
+
+	return TRUE;
 }
 
 // STUB: LEGO1 0x10034d00
diff --git a/LEGO1/lego/legoomni/src/common/legoutil.cpp b/LEGO1/lego/legoomni/src/common/legoutil.cpp
index 51cf4342..c6d0c22e 100644
--- a/LEGO1/lego/legoomni/src/common/legoutil.cpp
+++ b/LEGO1/lego/legoomni/src/common/legoutil.cpp
@@ -10,6 +10,8 @@
 #include <process.h>
 #include <string.h>
 
+DECOMP_SIZE_ASSERT(NamedTexture, 0x14)
+
 // STUB: LEGO1 0x1003e050
 void FUN_1003e050(LegoAnimPresenter* p_presenter)
 {
@@ -253,3 +255,21 @@ MxBool FUN_1003ef60()
 {
 	return TRUE;
 }
+
+// STUB: LEGO1 0x1003f3b0
+NamedTexture* ReadNamedTexture(LegoFile* p_file)
+{
+	return NULL;
+}
+
+// STUB: LEGO1 0x1003f540
+void FUN_1003f540(LegoFile* p_file, const char* p_filename)
+{
+}
+
+// FUNCTION: LEGO1 0x1003f8a0
+void WriteNamedTexture(LegoFile* p_file, NamedTexture* p_texture)
+{
+	p_file->FUN_10006030(*p_texture->GetName());
+	p_texture->GetTexture()->Write(p_file);
+}
diff --git a/LEGO1/lego/legoomni/src/infocenter/elevatorbottom.cpp b/LEGO1/lego/legoomni/src/infocenter/elevatorbottom.cpp
index 98e7d1b9..d8a87fa4 100644
--- a/LEGO1/lego/legoomni/src/infocenter/elevatorbottom.cpp
+++ b/LEGO1/lego/legoomni/src/infocenter/elevatorbottom.cpp
@@ -14,7 +14,7 @@ DECOMP_SIZE_ASSERT(ElevatorBottom, 0xfc)
 
 // STRING: LEGO1 0x100f0d34
 // GLOBAL: LEGO1 0x100f3a44
-char* g_cameraLoc = "CAMERA_LOCATION";
+const char* g_cameraLoc = "CAMERA_LOCATION";
 
 // FUNCTION: LEGO1 0x10017e90
 ElevatorBottom::ElevatorBottom()
diff --git a/LEGO1/lego/sources/misc/legostorage.h b/LEGO1/lego/sources/misc/legostorage.h
index 97dcc538..e975c347 100644
--- a/LEGO1/lego/sources/misc/legostorage.h
+++ b/LEGO1/lego/sources/misc/legostorage.h
@@ -2,6 +2,7 @@
 #define __LEGOSTORAGE_H
 
 #include "legotypes.h"
+#include "mxgeometry/mxgeometry3d.h"
 #include "mxstring.h"
 
 #include <stdio.h>
@@ -72,6 +73,45 @@ public:
 	LegoResult SetPosition(LegoU32 p_position) override;
 	LegoResult Open(const char* p_name, LegoU32 p_mode);
 
+	// FUNCTION: LEGO1 0x100343d0
+	LegoStorage* WriteVector3(Mx3DPointFloat p_vec3)
+	{
+		float data = p_vec3[0];
+		Write(&data, sizeof(float));
+
+		data = p_vec3[1];
+		Write(&data, sizeof(float));
+
+		data = p_vec3[2];
+		Write(&data, sizeof(float));
+		return this;
+	}
+
+	// FUNCTION: LEGO1 0x10034430
+	LegoStorage* ReadVector3(Mx3DPointFloat& p_vec3)
+	{
+		Read(&p_vec3[0], sizeof(float));
+		Read(&p_vec3[1], sizeof(float));
+		Read(&p_vec3[2], sizeof(float));
+		return this;
+	}
+
+	// FUNCTION: LEGO1 0x10034470
+	LegoStorage* ReadString(MxString& p_str)
+	{
+		MxS16 len;
+		Read(&len, sizeof(MxS16));
+
+		char* text = new char[len + 1];
+		Read(text, len);
+
+		text[len] = '\0';
+		p_str = text;
+		delete[] text;
+
+		return this;
+	}
+
 	// FUNCTION: LEGO1 0x10006030
 	LegoStorage* FUN_10006030(MxString p_str)
 	{
diff --git a/LEGO1/mxgeometry/mxgeometry3d.h b/LEGO1/mxgeometry/mxgeometry3d.h
index 837a5b3a..bdb3f191 100644
--- a/LEGO1/mxgeometry/mxgeometry3d.h
+++ b/LEGO1/mxgeometry/mxgeometry3d.h
@@ -15,6 +15,9 @@ public:
 		m_elements[2] = p_z;
 	}
 
+	// FUNCTION: LEGO1 0x100343a0
+	inline Mx3DPointFloat(const Mx3DPointFloat& p_other) : Vector3(m_elements) { EqualsImpl(p_other.m_data); }
+
 	// SYNTHETIC: LEGO1 0x1001d170
 	// Mx3DPointFloat::Mx3DPointFloat
 
diff --git a/util/decomp.h b/util/decomp.h
index 3470fcc3..100f9f21 100644
--- a/util/decomp.h
+++ b/util/decomp.h
@@ -14,7 +14,7 @@
 #endif
 
 #ifndef _countof
-#define _countof(arr) sizeof(arr) / sizeof(arr[0])
+#define _countof(arr) (sizeof(arr) / sizeof(arr[0]))
 #endif
 
 typedef unsigned char undefined;