From 699d83c61b9c34499fb9d238a00779c75eee2475 Mon Sep 17 00:00:00 2001
From: TheBrokenRail <connor24nolan@live.com>
Date: Mon, 27 Jun 2022 14:47:55 -0400
Subject: [PATCH] Recipes API

---
 example-mods/README.md                        |   1 +
 example-mods/recipes/.gitignore               |  14 +++
 example-mods/recipes/CMakeLists.txt           |  15 +++
 example-mods/recipes/recipes.cpp              |  52 +++++++++
 .../gles/src/compatibility-layer/draw.c       |  27 +++--
 mods/CMakeLists.txt                           |   2 +-
 mods/include/mods/misc/misc.h                 |   8 +-
 mods/src/creative/creative.cpp                |   1 +
 mods/src/misc/api.cpp                         | 100 ++++++++++++++++++
 mods/src/misc/misc-internal.h                 |   1 +
 mods/src/misc/misc.c                          |   1 +
 mods/src/misc/misc.cpp                        |  44 --------
 symbols/include/symbols/minecraft.h           |  27 +++++
 13 files changed, 234 insertions(+), 59 deletions(-)
 create mode 100644 example-mods/recipes/.gitignore
 create mode 100644 example-mods/recipes/CMakeLists.txt
 create mode 100644 example-mods/recipes/recipes.cpp
 create mode 100644 mods/src/misc/api.cpp

diff --git a/example-mods/README.md b/example-mods/README.md
index 3f4f2a3b..926f6126 100644
--- a/example-mods/README.md
+++ b/example-mods/README.md
@@ -3,6 +3,7 @@ This is an example of a mod that cane be built using the modding SDK.
 
 * **Expanded Creative Mod**: This specific mod adds even more items and blocks to the Creative Inventory. It was originally by [@Bigjango13](https://github.com/bigjango13).
 * **Chat Commands Mod**: This specific mod makes an chat message starting with a ``/`` handled by the MCPI API.
+* **Recipes Mod**: This specific mod demos custom recipes.
 
 ## The SDK
 The modding SDK is a collection of exported CMake targets that allows anyone to create their own MCPI mod!
diff --git a/example-mods/recipes/.gitignore b/example-mods/recipes/.gitignore
new file mode 100644
index 00000000..8563b985
--- /dev/null
+++ b/example-mods/recipes/.gitignore
@@ -0,0 +1,14 @@
+out
+debian/tmp
+.vscode
+build*
+CMakeLists.txt.user
+*.autosave
+AppImageBuilder.yml
+appimage-builder-cache
+appimage-build
+AppDir
+*.zsync
+*.AppImage
+core*
+qemu_*
diff --git a/example-mods/recipes/CMakeLists.txt b/example-mods/recipes/CMakeLists.txt
new file mode 100644
index 00000000..cdf6bce8
--- /dev/null
+++ b/example-mods/recipes/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16.0)
+
+# Build For ARM
+set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
+set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
+
+# Start Project
+project(recipes)
+
+# Include SDK
+include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake")
+
+# Build
+add_library(recipes SHARED recipes.cpp)
+target_link_libraries(recipes mods-headers reborn-util symbols misc)
diff --git a/example-mods/recipes/recipes.cpp b/example-mods/recipes/recipes.cpp
new file mode 100644
index 00000000..f0e6aca0
--- /dev/null
+++ b/example-mods/recipes/recipes.cpp
@@ -0,0 +1,52 @@
+// Headers
+
+#include <libreborn/libreborn.h>
+#include <symbols/minecraft.h>
+#include <mods/misc/misc.h>
+
+// Custom Crafting Recipes
+static void Recipes_injection(unsigned char *recipes) {
+    // Add
+    Recipes_Type type1 = {
+        .item = 0,
+        .tile = 0,
+        .instance = {
+            .count = 1,
+            .id = 12,
+            .auxiliary = 0
+        },
+        .letter = 'a'
+    };
+    Recipes_Type type2 = {
+        .item = 0,
+        .tile = 0,
+        .instance = {
+            .count = 1,
+            .id = 13,
+            .auxiliary = 0
+        },
+        .letter = 'b'
+    };
+    ItemInstance result = {
+        .count = 1,
+        .id = 344,
+        .auxiliary = 0
+    };
+    (*Recipes_addShapelessRecipe)(recipes, result, {type1, type2});
+}
+
+// Custom Furnace Recipes
+static void FurnaceRecipes_injection(unsigned char *recipes) {
+    // Add
+    (*FurnaceRecipes_addFurnaceRecipe)(recipes, 49, {.count = 1, .id = 246, .auxiliary = 0});
+}
+
+// Init
+__attribute__((constructor)) static void init_recipes() {
+    // Log
+    INFO("Loading Custom Recipes");
+
+    // Setup
+    misc_run_on_recipes_setup(Recipes_injection);
+    misc_run_on_furnace_recipes_setup(FurnaceRecipes_injection);
+}
diff --git a/media-layer/gles/src/compatibility-layer/draw.c b/media-layer/gles/src/compatibility-layer/draw.c
index b7c4253a..f0c21447 100644
--- a/media-layer/gles/src/compatibility-layer/draw.c
+++ b/media-layer/gles/src/compatibility-layer/draw.c
@@ -157,6 +157,11 @@ static void use_shader(GLuint program) {
 
 // Array Pointer Drawing
 GL_FUNC(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count));
+#define lazy_uniform(name) \
+    static GLint name##_handle = -1; \
+    if (name##_handle == -1) { \
+        name##_handle = real_glGetUniformLocation()(program, #name); \
+    }
 void glDrawArrays(GLenum mode, GLint first, GLsizei count) {
     // Verify
     if (gl_state.array_pointers.vertex.size != 3 || !gl_state.array_pointers.vertex.enabled || gl_state.array_pointers.vertex.type != GL_FLOAT) {
@@ -178,30 +183,30 @@ void glDrawArrays(GLenum mode, GLint first, GLsizei count) {
     use_shader(program);
 
     // Projection Matrix
-    GLint u_projection_handle = real_glGetUniformLocation()(program, "u_projection");
+    lazy_uniform(u_projection);
     matrix_t *p = &gl_state.matrix_stacks.projection.stack[gl_state.matrix_stacks.projection.i];
     real_glUniformMatrix4fv()(u_projection_handle, 1, 0, (GLfloat *) &p->data[0][0]);
 
     // Model View Matrix
-    GLint u_model_view_handle = real_glGetUniformLocation()(program, "u_model_view");
+    lazy_uniform(u_model_view);
     p = &gl_state.matrix_stacks.model_view.stack[gl_state.matrix_stacks.model_view.i];
     real_glUniformMatrix4fv()(u_model_view_handle, 1, 0, (GLfloat *) &p->data[0][0]);
 
     // Has Texture
-    GLint u_has_texture_handle = real_glGetUniformLocation()(program, "u_has_texture"); \
+    lazy_uniform(u_has_texture); \
     real_glUniform1i()(u_has_texture_handle, use_texture); \
 
     // Texture Matrix
-    GLint u_texture_handle = real_glGetUniformLocation()(program, "u_texture");
+    lazy_uniform(u_texture);
     p = &gl_state.matrix_stacks.texture.stack[gl_state.matrix_stacks.texture.i];
     real_glUniformMatrix4fv()(u_texture_handle, 1, 0, (GLfloat *) &p->data[0][0]);
 
     // Texture Unit
-    GLint u_texture_unit_handle = real_glGetUniformLocation()(program, "u_texture_unit");
+    lazy_uniform(u_texture_unit);
     real_glUniform1i()(u_texture_unit_handle, 0);
 
     // Alpha Test
-    GLint u_alpha_test_handle = real_glGetUniformLocation()(program, "u_alpha_test");
+    lazy_uniform(u_alpha_test);
     real_glUniform1i()(u_alpha_test_handle, gl_state.alpha_test);
 
     // Color
@@ -214,16 +219,16 @@ void glDrawArrays(GLenum mode, GLint first, GLsizei count) {
     }
 
     // Fog
-    GLint u_fog_handle = real_glGetUniformLocation()(program, "u_fog");
+    lazy_uniform(u_fog);
     real_glUniform1i()(u_fog_handle, gl_state.fog.enabled);
     if (gl_state.fog.enabled) {
-        GLint u_fog_color_handle = real_glGetUniformLocation()(program, "u_fog_color");
+        lazy_uniform(u_fog_color);
         real_glUniform4f()(u_fog_color_handle, gl_state.fog.color[0], gl_state.fog.color[1], gl_state.fog.color[2], gl_state.fog.color[3]);
-        GLint u_fog_is_linear_handle = real_glGetUniformLocation()(program, "u_fog_is_linear");
+        lazy_uniform(u_fog_is_linear);
         real_glUniform1i()(u_fog_is_linear_handle, gl_state.fog.mode == GL_LINEAR);
-        GLint u_fog_start_handle = real_glGetUniformLocation()(program, "u_fog_start");
+        lazy_uniform(u_fog_start);
         real_glUniform1f()(u_fog_start_handle, gl_state.fog.start);
-        GLint u_fog_end_handle = real_glGetUniformLocation()(program, "u_fog_end");
+        lazy_uniform(u_fog_end);
         real_glUniform1f()(u_fog_end_handle, gl_state.fog.end);
     }
 
diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt
index 28e33092..a4c73671 100644
--- a/mods/CMakeLists.txt
+++ b/mods/CMakeLists.txt
@@ -78,7 +78,7 @@ endif()
 add_library(death SHARED src/death/death.cpp)
 target_link_libraries(death mods-headers reborn-patch symbols feature)
 
-add_library(misc SHARED src/misc/misc.c src/misc/misc.cpp src/misc/logging.cpp)
+add_library(misc SHARED src/misc/misc.c src/misc/misc.cpp src/misc/logging.cpp src/misc/api.cpp)
 target_link_libraries(misc mods-headers reborn-patch symbols media-layer-core feature GLESv1_CM)
 
 add_library(options SHARED src/options/options.c src/options/options.cpp)
diff --git a/mods/include/mods/misc/misc.h b/mods/include/mods/misc/misc.h
index 3995ae29..4c66d8dc 100644
--- a/mods/include/mods/misc/misc.h
+++ b/mods/include/mods/misc/misc.h
@@ -4,9 +4,11 @@
 extern "C" {
 #endif
 
-typedef void (*misc_update_function_t)(unsigned char *minecraft);
-void misc_run_on_update(misc_update_function_t function);
-void misc_run_on_tick(misc_update_function_t function);
+typedef void (*misc_update_function_t)(unsigned char *obj);
+void misc_run_on_update(misc_update_function_t function); // obj == Minecraft *
+void misc_run_on_tick(misc_update_function_t function); // obj == Minecraft *
+void misc_run_on_recipes_setup(misc_update_function_t function); // obj == Recipes *
+void misc_run_on_furnace_recipes_setup(misc_update_function_t function); // obj == FurnaceRecipes *
 
 void Level_saveLevelData_injection(unsigned char *level);
 
diff --git a/mods/src/creative/creative.cpp b/mods/src/creative/creative.cpp
index b4b75fae..53e8e1b1 100644
--- a/mods/src/creative/creative.cpp
+++ b/mods/src/creative/creative.cpp
@@ -3,6 +3,7 @@
 
 #include <mods/init/init.h>
 #include <mods/feature/feature.h>
+#include <mods/misc/misc.h>
 #include <mods/creative/creative.h>
 
 #ifndef MCPI_SERVER_MODE
diff --git a/mods/src/misc/api.cpp b/mods/src/misc/api.cpp
new file mode 100644
index 00000000..7ab07575
--- /dev/null
+++ b/mods/src/misc/api.cpp
@@ -0,0 +1,100 @@
+#include <vector>
+
+#include <libreborn/libreborn.h>
+#include <symbols/minecraft.h>
+
+#include <mods/misc/misc.h>
+#include "misc-internal.h"
+
+// Run Functions On Update
+static std::vector<misc_update_function_t> &get_misc_update_functions() {
+    static std::vector<misc_update_function_t> functions;
+    return functions;
+}
+void misc_run_on_update(misc_update_function_t function) {
+    get_misc_update_functions().push_back(function);
+}
+// Handle Custom Update Behavior
+static void Minecraft_update_injection(unsigned char *minecraft) {
+    // Call Original Method
+    (*Minecraft_update)(minecraft);
+
+    // Run Functions
+    for (misc_update_function_t function : get_misc_update_functions()) {
+        (*function)(minecraft);
+    }
+}
+
+// Run Functions On Tick
+static std::vector<misc_update_function_t> &get_misc_tick_functions() {
+    static std::vector<misc_update_function_t> functions;
+    return functions;
+}
+void misc_run_on_tick(misc_update_function_t function) {
+    get_misc_tick_functions().push_back(function);
+}
+// Handle Custom Tick Behavior
+static void Minecraft_tick_injection(unsigned char *minecraft, int32_t param_1, int32_t param_2) {
+    // Call Original Method
+    (*Minecraft_tick)(minecraft, param_1, param_2);
+
+    // Run Functions
+    for (misc_update_function_t function : get_misc_tick_functions()) {
+        (*function)(minecraft);
+    }
+}
+
+// Run Functions On Recipes Setup
+static std::vector<misc_update_function_t> &get_misc_recipes_setup_functions() {
+    static std::vector<misc_update_function_t> functions;
+    return functions;
+}
+void misc_run_on_recipes_setup(misc_update_function_t function) {
+    get_misc_recipes_setup_functions().push_back(function);
+}
+// Handle Custom Recipes Setup Behavior
+static unsigned char *Recipes_injection(unsigned char *recipes) {
+    // Call Original Method
+    (*Recipes)(recipes);
+
+    // Run Functions
+    for (misc_update_function_t function : get_misc_recipes_setup_functions()) {
+        (*function)(recipes);
+    }
+
+    // Return
+    return recipes;
+}
+
+// Run Functions On Furnace Recipes Setup
+static std::vector<misc_update_function_t> &get_misc_furnace_recipes_setup_functions() {
+    static std::vector<misc_update_function_t> functions;
+    return functions;
+}
+void misc_run_on_furnace_recipes_setup(misc_update_function_t function) {
+    get_misc_furnace_recipes_setup_functions().push_back(function);
+}
+// Handle Custom Furnace Recipes Setup Behavior
+static unsigned char *FurnaceRecipes_injection(unsigned char *recipes) {
+    // Call Original Method
+    (*FurnaceRecipes)(recipes);
+
+    // Run Functions
+    for (misc_update_function_t function : get_misc_furnace_recipes_setup_functions()) {
+        (*function)(recipes);
+    }
+
+    // Return
+    return recipes;
+}
+
+// Init
+void _init_misc_api() {
+    // Handle Custom Update Behavior
+    overwrite_calls((void *) Minecraft_update, (void *) Minecraft_update_injection);
+    // Handle Custom Tick Behavior
+    overwrite_calls((void *) Minecraft_tick, (void *) Minecraft_tick_injection);
+    // Handle Custom Recipe Setup Behavior
+    overwrite_calls((void *) Recipes, (void *) Recipes_injection);
+    overwrite_calls((void *) FurnaceRecipes, (void *) FurnaceRecipes_injection);
+}
diff --git a/mods/src/misc/misc-internal.h b/mods/src/misc/misc-internal.h
index 16cff902..3f9e8853 100644
--- a/mods/src/misc/misc-internal.h
+++ b/mods/src/misc/misc-internal.h
@@ -6,6 +6,7 @@ extern "C" {
 
 __attribute__((visibility("internal"))) void _init_misc_cpp();
 __attribute__((visibility("internal"))) void _init_misc_logging();
+__attribute__((visibility("internal"))) void _init_misc_api();
 
 #ifdef __cplusplus
 }
diff --git a/mods/src/misc/misc.c b/mods/src/misc/misc.c
index 4750462d..187a8632 100644
--- a/mods/src/misc/misc.c
+++ b/mods/src/misc/misc.c
@@ -258,4 +258,5 @@ void init_misc() {
     // Init C++ And Logging
     _init_misc_cpp();
     _init_misc_logging();
+    _init_misc_api();
 }
diff --git a/mods/src/misc/misc.cpp b/mods/src/misc/misc.cpp
index 3fce748e..0318b497 100644
--- a/mods/src/misc/misc.cpp
+++ b/mods/src/misc/misc.cpp
@@ -1,7 +1,6 @@
 #include <string>
 #include <fstream>
 #include <streambuf>
-#include <vector>
 
 #include <cstring>
 
@@ -26,44 +25,6 @@ static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injectio
     return ret;
 }
 
-// Run Functions On Update
-static std::vector<misc_update_function_t> &get_misc_update_functions() {
-    static std::vector<misc_update_function_t> functions;
-    return functions;
-}
-void misc_run_on_update(misc_update_function_t function) {
-    get_misc_update_functions().push_back(function);
-}
-// Handle Custom Update Behavior
-static void Minecraft_update_injection(unsigned char *minecraft) {
-    // Call Original Method
-    (*Minecraft_update)(minecraft);
-
-    // Run Functions
-    for (misc_update_function_t function : get_misc_update_functions()) {
-        (*function)(minecraft);
-    }
-}
-
-// Run Functions On Update
-static std::vector<misc_update_function_t> &get_misc_tick_functions() {
-    static std::vector<misc_update_function_t> functions;
-    return functions;
-}
-void misc_run_on_tick(misc_update_function_t function) {
-    get_misc_tick_functions().push_back(function);
-}
-// Handle Custom Tick Behavior
-static void Minecraft_tick_injection(unsigned char *minecraft, int32_t param_1, int32_t param_2) {
-    // Call Original Method
-    (*Minecraft_tick)(minecraft, param_1, param_2);
-
-    // Run Functions
-    for (misc_update_function_t function : get_misc_tick_functions()) {
-        (*function)(minecraft);
-    }
-}
-
 // Add Missing Buttons To Pause Menu
 static void PauseScreen_init_injection(unsigned char *screen) {
     // Call Original Method
@@ -105,11 +66,6 @@ void _init_misc_cpp() {
         overwrite((void *) AppPlatform_readAssetFile, (void *) AppPlatform_readAssetFile_injection);
     }
 
-    // Handle Custom Update Behavior
-    overwrite_calls((void *) Minecraft_update, (void *) Minecraft_update_injection);
-    // Handle Custom Tick Behavior
-    overwrite_calls((void *) Minecraft_tick, (void *) Minecraft_tick_injection);
-
     // Fix Pause Menu
     if (feature_has("Fix Pause Menu", server_disabled)) {
         // Add Missing Buttons To Pause Menu
diff --git a/symbols/include/symbols/minecraft.h b/symbols/include/symbols/minecraft.h
index 1ab71289..3142a902 100644
--- a/symbols/include/symbols/minecraft.h
+++ b/symbols/include/symbols/minecraft.h
@@ -722,6 +722,23 @@ static SoundEngine_update_t SoundEngine_update = (SoundEngine_update_t) 0x67778;
 static uint32_t SoundEngine_minecraft_property_offset = 0xa08; // Minecraft *
 static uint32_t SoundEngine_options_property_offset = 0x4; // Options *
 
+// Recipes
+
+// If there are multiple item IDs, the priority order is: "item" > "tile" > "instance"
+typedef struct {
+    unsigned char *item;
+    unsigned char *tile;
+    ItemInstance instance;
+    char letter;
+} Recipes_Type;
+
+typedef unsigned char *(*Recipes_t)(unsigned char *recipes);
+static Recipes_t Recipes = (Recipes_t) 0x9cabc;
+
+// FurnaceRecipes
+
+static Recipes_t FurnaceRecipes = (Recipes_t) 0xa0778;
+
 // Method That Require C++ Types
 #ifdef __cplusplus
 
@@ -848,6 +865,16 @@ static OptionsPane_unknown_toggle_creating_function_t OptionsPane_unknown_toggle
 typedef void (*Textures_loadAndBindTexture_t)(unsigned char *textures, std::string const& name);
 static Textures_loadAndBindTexture_t Textures_loadAndBindTexture = (Textures_loadAndBindTexture_t) 0x539cc;
 
+// Recipes
+
+typedef void (*Recipes_addShapelessRecipe_t)(unsigned char *recipes, ItemInstance const& result, std::vector<Recipes_Type> const& param_2);
+static Recipes_addShapelessRecipe_t Recipes_addShapelessRecipe = (Recipes_addShapelessRecipe_t) 0x9c3dc;
+
+// FurnaceRecipes
+
+typedef void (*FurnaceRecipes_addFurnaceRecipe_t)(unsigned char *recipes, int32_t input_item_id, ItemInstance const& result);
+static FurnaceRecipes_addFurnaceRecipe_t FurnaceRecipes_addFurnaceRecipe = (FurnaceRecipes_addFurnaceRecipe_t) 0xa0714;
+
 #endif
 
 #pragma GCC diagnostic pop