code:
#include "ImageInterpreter.hpp"

#undef __BASE_FILE__
#define __BASE_FILE__ "ImageInterpreter.cpp"

ImageInterpreter::ImageInterpreter() :
    _ImageBasePtr(nullptr),
    _NTHeadersPtr(nullptr),
    _SectionHeaderTablePtr(nullptr) {}

bool ImageInterpreter::ParseImage(const PVOID PtrToImageBase, bool DisableRelocParsing) {
    if (PtrToImageBase == nullptr)
        return false;

    PIMAGE_DOS_HEADER PtrToDosHeader =
        reinterpret_cast<PIMAGE_DOS_HEADER>(PtrToImageBase);
    if (PtrToDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
        return false;

    PIMAGE_NT_HEADERS PtrToNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(
        reinterpret_cast<uint8_t*>(PtrToImageBase) +
        PtrToDosHeader->e_lfanew
        );
    if (PtrToNtHeaders->Signature != IMAGE_NT_SIGNATURE)
        return false;
    if (PtrToNtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
        return false;
#if defined(_M_AMD64)
    if (PtrToNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64)
        return false;
#elif defined(_M_IX86)
    if (PtrToNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
        return false;
#else
#error "Unsupported architecture."
#endif

    PIMAGE_SECTION_HEADER PtrToSectionHeaderTable =
        reinterpret_cast<PIMAGE_SECTION_HEADER>(
            reinterpret_cast<char*>(&PtrToNtHeaders->OptionalHeader) +
            PtrToNtHeaders->FileHeader.SizeOfOptionalHeader
            );

    std::map<uint64_t, size_t> SectionNameTable;
    std::map<uintptr_t, size_t> SectioMapAddressTable;
    std::map<uintptr_t, size_t> RelocationAddressTable;

    for (WORD i = 0; i < PtrToNtHeaders->FileHeader.NumberOfSections; ++i) {
        uint64_t SectionName =
            *reinterpret_cast<uint64_t*>(PtrToSectionHeaderTable[i].Name);
        if (SectionNameTable.find(SectionName) != SectionNameTable.end())
            continue;
        SectionNameTable[SectionName] = i;

        uintptr_t SectionMapAddress =
            PtrToSectionHeaderTable[i].VirtualAddress;
        SectioMapAddressTable[SectionMapAddress] = i;
    }

    if (DisableRelocParsing == false &&
        PtrToNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0) {
        DWORD RelocTableRva =
            PtrToNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
        PIMAGE_BASE_RELOCATION PtrToRelocTable = nullptr;

        {
            auto v = SectioMapAddressTable.lower_bound(RelocTableRva);

            if (v != SectioMapAddressTable.end()) {
                if (v->first != RelocTableRva) {
                    if (v != SectioMapAddressTable.begin()) {
                        --v;
                        PtrToRelocTable = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
                            reinterpret_cast<uint8_t*>(PtrToImageBase) +
                            PtrToSectionHeaderTable[v->second].PointerToRawData
                            );
                    }
                } else {
                    PtrToRelocTable = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
                        reinterpret_cast<uint8_t*>(PtrToImageBase) +
                        PtrToSectionHeaderTable[v->second].PointerToRawData
                        );
                }
            }
        }

        while (PtrToRelocTable != nullptr && PtrToRelocTable->VirtualAddress != 0) {
            DWORD Rva = PtrToRelocTable->VirtualAddress;
            PWORD RelocItems = reinterpret_cast<PWORD>(PtrToRelocTable + 1);
            DWORD RelocItemsCount = (PtrToRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
            for (DWORD i = 0; i < RelocItemsCount; ++i) {
                int RelocType = RelocItems[i] >> 12;
                switch (RelocType) {
                case IMAGE_REL_BASED_ABSOLUTE:
                    break;
                case IMAGE_REL_BASED_HIGH:
                case IMAGE_REL_BASED_LOW:
                case IMAGE_REL_BASED_HIGHADJ:
                    RelocationAddressTable[Rva + (RelocItems[i] & 0x0fff)] = 2;
                    break;
                case IMAGE_REL_BASED_HIGHLOW:
                    RelocationAddressTable[Rva + (RelocItems[i] & 0x0fff)] = 4;
                    break;
#if defined(IMAGE_REL_BASED_DIR64)
                case IMAGE_REL_BASED_DIR64:
                    RelocationAddressTable[Rva + (RelocItems[i] & 0x0fff)] = 8;
                    break;
#endif
                default:
                    break;
                }
            }
            PtrToRelocTable = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
                &RelocItems[RelocItemsCount]
                );
        }
    }

    _ImageBasePtr = PtrToImageBase;
    _NTHeadersPtr = PtrToNtHeaders;
    _SectionHeaderTablePtr = PtrToSectionHeaderTable;
    _SectionNameTable = std::move(SectionNameTable);
    _SectionMapAddressTable = std::move(SectioMapAddressTable);
    _RelocationAddressTable = std::move(RelocationAddressTable);
    return true;
}

PIMAGE_DOS_HEADER ImageInterpreter::GetImageDosHeader() const {
    return reinterpret_cast<PIMAGE_DOS_HEADER>(_ImageBasePtr);
}

PIMAGE_NT_HEADERS ImageInterpreter::GetImageNTHeaders() const {
    return _NTHeadersPtr;
}

PIMAGE_SECTION_HEADER ImageInterpreter::GetSectionHeaderTable() const {
    return _SectionHeaderTablePtr;
}

PIMAGE_SECTION_HEADER ImageInterpreter::GetSectionHeader(const char* SectionName) const {
    uint64_t NameValue = 0;

    for (int i = 0; i < sizeof(NameValue) && SectionName[i]; ++i)
        reinterpret_cast<char*>(&NameValue)[i] = SectionName[i];

    auto v = _SectionNameTable.find(NameValue);

    if (v == _SectionNameTable.end())
        return nullptr;
    else
        return &_SectionHeaderTablePtr[v->second];
}

PIMAGE_SECTION_HEADER ImageInterpreter::GetSectionHeader(uintptr_t Rva) const {
    auto v = _SectionMapAddressTable.lower_bound(Rva);

    if (v == _SectionMapAddressTable.end())
        return nullptr;

    if (v->first != Rva) {
        if (v == _SectionMapAddressTable.begin())
            return nullptr;
        --v;
        return &_SectionHeaderTablePtr[v->second];
    } else {
        return &_SectionHeaderTablePtr[v->second];
    }
}

bool ImageInterpreter::IsRvaRangeInRelocTable(uintptr_t Rva, size_t Size) const {
    auto v = _RelocationAddressTable.lower_bound(Rva);

    if (v == _RelocationAddressTable.end())
        return false;

    if (v->first == Rva) {
        return true;
    } else {
        auto w = v--;
        if (v->first <= Rva && Rva < v->first + v->second)
            return true;

        if (Rva + Size <= w->first)
            return false;

        return true;
    }
}


code:
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <windows.h>
#include <map>

class ImageInterpreter {
private:
    PVOID _ImageBasePtr;
    PIMAGE_NT_HEADERS _NTHeadersPtr;
    PIMAGE_SECTION_HEADER _SectionHeaderTablePtr;
    std::map<uint64_t, size_t> _SectionNameTable;
    std::map<uintptr_t, size_t> _SectionMapAddressTable;
    std::map<uintptr_t, size_t> _RelocationAddressTable;
public:
    ImageInterpreter();

    bool ParseImage(const PVOID PtrToImageBase, bool DisableRelocParsing);

    template<typename __Type>
    __Type* GetImageBase() const {
        return reinterpret_cast<__Type*>(_ImageBasePtr);
    }

    PIMAGE_DOS_HEADER GetImageDosHeader() const;
    PIMAGE_NT_HEADERS GetImageNTHeaders() const;
    PIMAGE_SECTION_HEADER GetSectionHeaderTable() const;
    PIMAGE_SECTION_HEADER GetSectionHeader(const char* SectionName) const;
    PIMAGE_SECTION_HEADER GetSectionHeader(uintptr_t Rva) const;

    template<typename __Type>
    __Type* GetSectionView(const char* SectionName) const {
        auto PtrToSectionHeader = GetSectionHeader(SectionName);

        if (PtrToSectionHeader == nullptr)
            return nullptr;

        return reinterpret_cast<__Type*>(
            reinterpret_cast<uint8_t*>(_ImageBasePtr) +
            PtrToSectionHeader->PointerToRawData
        );
    }

    template<typename __Type>
    __Type* GetSectionView(uintptr_t Rva) const {
        auto PtrToSectionHeader = GetSectionHeader(Rva);

        if (PtrToSectionHeader == nullptr)
            return nullptr;

        return reinterpret_cast<__Type*>(
            reinterpret_cast<uint8_t*>(_ImageBasePtr) +
            PtrToSectionHeader->PointerToRawData
        );
    }

    template<typename __Type>
    __Type* RvaToPointer(uintptr_t Rva) const {
        auto PtrToSectionHeader = GetSectionHeader(Rva);

        if (PtrToSectionHeader == nullptr)
            return nullptr;

        uint8_t* SectionViewPtr =
            reinterpret_cast<uint8_t*>(_ImageBasePtr) +
            PtrToSectionHeader->PointerToRawData;

        return reinterpret_cast<__Type*>(
            SectionViewPtr + (Rva - PtrToSectionHeader->VirtualAddress)
        );
    }

    bool IsRvaRangeInRelocTable(uintptr_t Rva, size_t Size) const;
};

repo_name: xianyu/navicat

license: mit


code:
#pragma once
#include <openssl/crypto.h>
#include <openssl/blowfish.h>
#include <openssl/sha.h>
#include <string>

#ifdef _DEBUG
#pragma comment(lib, "libcryptoMTd.lib")
#else
#pragma comment(lib, "libcryptoMT.lib")
#endif
#pragma comment(lib, "WS2_32.lib")      // some symbol are used in OpenSSL static lib
#pragma comment(lib, "Crypt32.lib")     // some symbol are used in OpenSSL static lib

class Navicat11Crypto {
protected:
    BF_KEY BlowfishKey;

    void BytesToHex(const void* src, size_t len, char* dst) {
        for (size_t i = 0; i < len; ++i) {
            char h = reinterpret_cast<const uint8_t*>(src)[i] >> 4;
            char l = reinterpret_cast<const uint8_t*>(src)[i] & 0x0F;

            h += h >= 10 ? 'A' - 10 : '0';
            l += l >= 10 ? 'A' - 10 : '0';
            dst[2 * i] = h;
            dst[2 * i + 1] = l;
        }
    }

    bool CheckHex(const char* src, size_t len) {
        if (len % 2 != 0)
            return false;

        for (size_t i = 0; i < len; i += 2) {
            char h = src[i];
            char l = src[i + 1];

            if (src[i] < '0' || src[i] > 'F')
                return false;
            if (src[i] < 'A' && src[i] > '9')
                return false;
            if (src[i + 1] < '0' || src[i + 1] > 'F')
                return false;
            if (src[i + 1] < 'A' && src[i + 1] > '9')
                return false;
        }

        return true;
    }

    void HexToBytes(const char* src, size_t len, void* dst) {
        for (size_t i = 0; i < len; i += 2) {
            uint8_t h = src[i];
            uint8_t l = src[i + 1];

            h -= h > '9' ? 'A' - 10 : '0';
            l -= l > '9' ? 'A' - 10 : '0';

            reinterpret_cast<uint8_t*>(dst)[i / 2] = (h << 4) ^ l;
        }
    }

    std::string EncryptString(const void* srcBytes, size_t srclen) {
        std::string ret;
        uint8_t CV[BF_BLOCK] = {
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        };

        if (srclen == 0)
            return ret;

        ret.resize(2 * srclen);

        BF_ecb_encrypt(CV, CV, &BlowfishKey, BF_ENCRYPT);

        const uint64_t* blocks = reinterpret_cast<const uint64_t*>(srcBytes);
        size_t blocks_len = srclen / BF_BLOCK;
        for (size_t i = 0; i < blocks_len; ++i) {
            union {
                uint8_t byte[8];
                uint64_t qword;
            } temp;

            temp.qword = blocks[i];
            temp.qword ^= *reinterpret_cast<uint64_t*>(CV);
            BF_ecb_encrypt(temp.byte, temp.byte, &BlowfishKey, BF_ENCRYPT);
            *reinterpret_cast<uint64_t*>(CV) ^= temp.qword;
            BytesToHex(&temp, 8, ret.data() + 16 * i);
        }

        if (srclen % BF_BLOCK) {
            BF_ecb_encrypt(CV, CV, &BlowfishKey, BF_ENCRYPT);
            for (size_t i = 0; i < srclen % BF_BLOCK; ++i) {
                CV[i] ^= reinterpret_cast<const uint8_t*>(blocks + blocks_len)[i];
            }
            BytesToHex(CV, srclen % BF_BLOCK, ret.data() + 16 * blocks_len);
        }

        return ret;
    }

    std::string DecryptString(const char* srchex, size_t srclen) {
        std::string ret;
        uint8_t CV[BF_BLOCK] = {
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        };

        if (CheckHex(srchex, srclen) == false)
            throw std::invalid_argument("Not hex string.");

        ret.resize(srclen / 2);

        BF_ecb_encrypt(CV, CV, &BlowfishKey, BF_ENCRYPT);

        const char(*blocks)[16] = reinterpret_cast<const char(*)[16]>(srchex);
        size_t blocks_len = srclen / 16;
        for (size_t i = 0; i < blocks_len; ++i) {
            union {
                uint8_t byte[8];
                uint64_t qword;
            } temp, temp2;

            HexToBytes(blocks[i], 16, temp.byte);
            temp2.qword = temp.qword;
            BF_ecb_encrypt(temp.byte, temp.byte, &BlowfishKey, BF_DECRYPT);
            temp.qword ^= *reinterpret_cast<uint64_t*>(CV);
            *reinterpret_cast<uint64_t*>(ret.data() + 8 * i) = temp.qword;
            *reinterpret_cast<uint64_t*>(CV) ^= temp2.qword;
        }

        if (srclen % 16) {
            union {
                uint8_t byte[8];
                uint64_t qword;
            } temp = { };
            HexToBytes(blocks[blocks_len], srclen % 16, temp.byte);

            BF_ecb_encrypt(CV, CV, &BlowfishKey, BF_ENCRYPT);
            for (size_t i = 0; i < (srclen % 16) / 2; ++i)
                ret[blocks_len * 8 + i] = temp.byte[i] ^ CV[i];
        }

        return ret;
    }

public:

    Navicat11Crypto() {
        static const uint8_t PresetKey[20] = {
            0x42, 0xCE, 0xB2, 0x71, 0xA5, 0xE4, 0x58, 0xB7,
            0x4A, 0xEA, 0x93, 0x94, 0x79, 0x22, 0x35, 0x43,
            0x91, 0x87, 0x33, 0x40
        };

        BF_set_key(&BlowfishKey, SHA_DIGEST_LENGTH, PresetKey);
    }

    Navicat11Crypto(const void* UserKey, size_t Length) {
        SetKey(UserKey, Length);
    }

    void SetKey(const void* UserKey, size_t Length) {
        unsigned char MessageDigest[SHA_DIGEST_LENGTH];

        SHA1(reinterpret_cast<const unsigned char*>(UserKey), Length, MessageDigest);
        BF_set_key(&BlowfishKey, SHA_DIGEST_LENGTH, MessageDigest);
        OPENSSL_cleanse(MessageDigest, SHA_DIGEST_LENGTH);
    }

    std::string EncryptString(const std::string& Plaintext) {
        return EncryptString(Plaintext.c_str(), Plaintext.length());
    }

    std::string DecryptString(const std::string& Ciphertext) {
        return DecryptString(Ciphertext.c_str(), Ciphertext.length());
    }

    void Clear() {
        OPENSSL_cleanse(&BlowfishKey, sizeof(BlowfishKey));
    }

    ~Navicat11Crypto() {
        Clear();
    }

};


code:
#pragma once
#include "RSACipher.hpp"
#include "CapstoneDisassembler.hpp"
#include "ImageInterpreter.hpp"

class PatchSolution{
public:
    virtual void SetFile(void* pFile) = 0;
    virtual bool FindPatchOffset() noexcept = 0;
    virtual bool CheckKey(RSACipher* pCipher) const = 0;
    virtual void MakePatch(RSACipher* pCipher) const = 0;
    virtual ~PatchSolution() {}
};

// PatchSolution0 will replace the RSA public key stored in main application.
// Main application can be "Navicat.exe", "Modeler.exe" or "Rviewer.exe"
class PatchSolution0 : public PatchSolution {
private:
    static const char Keyword[461];

    ImageInterpreter _MainExeInterpreter;
    off_t _PatchOffset;
public:
    PatchSolution0();
    virtual void SetFile(void* pFile) override;
    virtual bool FindPatchOffset() noexcept override;
    virtual bool CheckKey(RSACipher* pCipher) const override;
    virtual void MakePatch(RSACipher* cipher) const override;
};

// PatchSolution1, 2, 3 will replace the RSA public key stored in libcc.dll
class PatchSolution1 : public PatchSolution {
private:

    enum KeywordDataType {
        IMM_DATA,
        STRING_DATA
    };

    struct KeywordInfo {
        const char* Data;
        size_t Length;
        KeywordDataType Type;
    };

    struct PatchPointInfo {
        uint8_t* Ptr;
        size_t PatchSize;
        size_t MaxPatchSize;
    };

    static const KeywordInfo Keywords[5];

    ImageInterpreter _LibccDllInterpreter;
    mutable PatchPointInfo _Patches[5];
public:
    PatchSolution1();
    virtual void SetFile(void* pFile) override;
    virtual bool FindPatchOffset() noexcept override;
    virtual bool CheckKey(RSACipher* pCipher) const override;
    virtual void MakePatch(RSACipher* pCipher) const override;
};

class PatchSolution2 : public PatchSolution {
private:
    static const char KeywordsMeta[0x188 + 1];
    static uint8_t Keywords[0x188][5];

    ImageInterpreter _LibccDllInterpreter;
    off_t _PatchOffsets[0x188];

    void BuildKeywords() noexcept;
public:
    PatchSolution2();
    virtual void SetFile(void* pFile) override;
    virtual bool FindPatchOffset() noexcept override;
    virtual bool CheckKey(RSACipher* pCipher) const override;
    virtual void MakePatch(RSACipher* pCipher) const override;
};

class PatchSolution3 : public PatchSolution {
private:
    enum KeywordDataType {
        IMM_DATA,
        STRING_DATA,
    };

    struct KeywordInfo {
        uint8_t Data[8];
        size_t Length;
        KeywordDataType Type;
        bool NotRecommendedToModify;
    };

    struct PatchPointInfo {
        union {
            uint8_t* Ptr;
            const uint8_t* ConstPtr;
        } Opcode;
        uint64_t OpcodeRva;
        uint8_t* PatchPtr;
        size_t PatchSize;
        char* OriginalStringPtr;
        char* ReplaceStringPtr;
    };

    static const KeywordInfo Keywords[111];

    CapstoneEngine _CapstoneEngine;
    ImageInterpreter _LibccDllInterpreter;
    mutable PatchPointInfo _Patches[111];

    bool CheckIfMatchPattern(cs_insn* pInsn) const;

    bool CheckIfFound(cs_insn* pInsn, size_t i) const;

    PatchPointInfo
    CreatePatchPoint(const uint8_t* pOpcode, cs_insn* pInsn, size_t i) const;

    CapstoneDisassembler::Context
    GetJumpedBranch(const CapstoneDisassembler::Context& NotJumpedBranch, cs_insn* pJxxInsn) const;

    CapstoneDisassembler::Context
    HandleJcc(const CapstoneDisassembler::Context& NotJumpedBranch,
              const CapstoneDisassembler::Context& JumpedBranch,
              size_t i) const;
public:
    PatchSolution3();
    virtual void SetFile(void* pLibccFile) override;
    virtual bool FindPatchOffset() noexcept override;
    virtual bool CheckKey(RSACipher* pCipher) const override;
    virtual void MakePatch(RSACipher* pCipher) const override;
};


code:
#include "PatchSolution.hpp"
#include "Exception.hpp"
#include "Helper.hpp"
#include <tchar.h>

#undef __BASE_FILE__
#define __BASE_FILE__ TEXT("PatchSolution0.cpp")

const char PatchSolution0::Keyword[461] =
    "-----BEGIN PUBLIC KEY-----\r\n"
    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I\r\n"
    "qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv\r\n"
    "a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF\r\n"
    "R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2\r\n"
    "WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt\r\n"
    "YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ\r\n"
    "awIDAQAB\r\n"
    "-----END PUBLIC KEY-----\r\n";

PatchSolution0::PatchSolution0() :
    _PatchOffset(-1) {}

void PatchSolution0::SetFile(void* pFile) {
    if (!_MainExeInterpreter.ParseImage(pFile, true)) {
        throw Exception(__BASE_FILE__, __LINE__,
                        TEXT("Invalid PE file."));
    }
}

bool PatchSolution0::FindPatchOffset() noexcept {
    _PatchOffset = -1;

    auto pResourceSectionHeader =
        _MainExeInterpreter.GetSectionHeader(".rsrc");
    auto pResourceSection =
        _MainExeInterpreter.GetSectionView<uint8_t>(".rsrc");

    if (pResourceSectionHeader == nullptr || pResourceSection == nullptr)
        return false;

    for (DWORD i = 0; i < pResourceSectionHeader->SizeOfRawData; ++i) {
        if (memcmp(pResourceSection + i, Keyword, 460) == 0) {
            _PatchOffset = pResourceSectionHeader->PointerToRawData + i;
            _tprintf_s(TEXT("[*] PatchSolution0 ...... Ready to apply\n"));
            _tprintf_s(TEXT(" |--[*] Keyword offset = +0x%.8lx\n"), _PatchOffset);
            _tprintf_s(TEXT(" |\n"));
            return true;
        }
    }

    return false;
}

bool PatchSolution0::CheckKey(RSACipher* pCipher) const {
    std::string PublicKeyString =
        pCipher->ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();

    Helper::StringReplace<std::string>(PublicKeyString, "\n", "\r\n");

    return PublicKeyString.length() == 460;
}

void PatchSolution0::MakePatch(RSACipher* pCipher) const {
    uint8_t* pView = _MainExeInterpreter.GetImageBase<uint8_t>();

    std::string PublicKeyString =
        pCipher->ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();
    Helper::StringReplace<std::string>(PublicKeyString, "\n", "\r\n");

    _putts(TEXT("******************************************"));
    _putts(TEXT("*            PatchSulution0              *"));
    _putts(TEXT("******************************************"));
    _tprintf_s(TEXT("@ +0x%.8lx\n"), _PatchOffset);
    _putts(TEXT("Previous:"));
    Helper::PrintMemory(pView + _PatchOffset,
                        pView + _PatchOffset + 460,
                        pView);

    memcpy(pView + _PatchOffset, PublicKeyString.c_str(), 460);

    _putts(TEXT("After:"));
    Helper::PrintMemory(pView + _PatchOffset,
                        pView + _PatchOffset + 460,
                        pView);
}


code:
#include "PatchSolution.hpp"
#include "Exception.hpp"
#include "Helper.hpp"
#include <tchar.h>

#undef __BASE_FILE__
#define __BASE_FILE__ TEXT("PatchSolution2.cpp")

const char PatchSolution2::KeywordsMeta[0x188 + 1] =
    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I"
    "qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv"
    "a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF"
    "R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2"
    "WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt"
    "YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ"
    "awIDAQAB";

uint8_t PatchSolution2::Keywords[0x188][5];

PatchSolution2::PatchSolution2() {
    for (size_t i = 0; i < 0x188; ++i)
        _PatchOffsets[i] = -1;
}

void PatchSolution2::SetFile(void* pFile) {
    if (!_LibccDllInterpreter.ParseImage(pFile, true)) {
        throw Exception(__BASE_FILE__, __LINE__,
                        TEXT("Invalid PE file."));
    }
}

#if defined(_M_X64)

void PatchSolution2::BuildKeywords() noexcept {
    for (size_t i = 0; i < 0x188; ++i) {
        Keywords[i][0] = 0x83;      // Keywords[i] = asm('xor eax, KeywordsMeta[i]') +
        Keywords[i][1] = 0xf0;
        Keywords[i][2] = KeywordsMeta[i];
        Keywords[i][3] = 0x88;      //               asm_prefix('mov byte ptr ds:xxxxxxxxxxxxxxxx, al')
        Keywords[i][4] = 0x05;
    }
}

bool PatchSolution2::FindPatchOffset() noexcept {
    PIMAGE_SECTION_HEADER ptextSectionHeader = _LibccDllInterpreter.GetSectionHeader(".text");
    uint8_t* ptextSection = _LibccDllInterpreter.GetSectionView<uint8_t>(".text");
    off_t Offsets[0x188];

    for (size_t i = 0; i < 0x188; ++i)
        Offsets[i] = -1;

    if (ptextSectionHeader == nullptr)
        return false;
    if (ptextSection == nullptr)
        return false;

    BuildKeywords();

    // Find offsets
    {
        size_t FirstKeywordCounter = 0;
        uint32_t Hints[9];
        DWORD PossibleRangeStart = 0xffffffff;
        DWORD PossibleRangeEnd;
        for (DWORD i = 0; i < ptextSectionHeader->SizeOfRawData; ++i) {
            if (memcmp(ptextSection + i, Keywords[0], sizeof(Keywords[0])) == 0) {
                Hints[FirstKeywordCounter++] =
                    *reinterpret_cast<uint32_t*>(ptextSection + i + sizeof(Keywords[0])) +
                    i + sizeof(Keywords[0]) + sizeof(uint32_t);
                if (i < PossibleRangeStart)
                    PossibleRangeStart = i;
            }
        }

        PossibleRangeStart -= 0x1000;
        PossibleRangeEnd = PossibleRangeStart + 0x100000;

        // Keywords[0] should occur 9 times.
        // Because there's only 9 'M' chars in `KeywordsMeta`.
        if (FirstKeywordCounter != 9)
            return false;

        Helper::QuickSort(Hints, 0, _countof(Hints));

        // assert
        // if not satisfied, refuse to patch
        if (Hints[8] - Hints[0] != 0x18360F8F8 - 0x18360F7D0)
            return false;

        for (size_t i = 0; i < 0x188; ++i) {
            if (Offsets[i] != -1)
                continue;

            for (DWORD j = PossibleRangeStart; j < PossibleRangeEnd; ++j) {
                if (memcmp(ptextSection + j, Keywords[i], sizeof(Keywords[i])) == 0) {
                    off_t index =
                        *reinterpret_cast<uint32_t*>(ptextSection + j + sizeof(Keywords[i])) +
                        j + sizeof(Keywords[i]) + sizeof(uint32_t) - Hints[0];

                    if (0 <= index && index < 0x188 && KeywordsMeta[index] == KeywordsMeta[i]) {
                        Offsets[index] = ptextSectionHeader->PointerToRawData + j;
                    }
                }
            }

            // if not found, refuse to patch
            if (Offsets[i] == -1)
                return false;
        }
    }

    _tprintf_s(TEXT("[*] PatchSolution2 ...... Ready to apply\n"));
    for (size_t i = 0; i < 0x188; ++i) {
        _PatchOffsets[i] = Offsets[i];
        _tprintf_s(TEXT(" |--[*] Keyword[%zu]\n"), i);
        _tprintf_s(TEXT(" |   | - Offset = +0x%.8lx\n"), _PatchOffsets[i]);
        _tprintf_s(TEXT(" |\n"));
    }

    return true;
}

#else

void PatchSolution2::BuildKeywords() noexcept {
    for (size_t i = 0; i < 0x188; ++i) {
        switch (i % 3) {
        case 0:
            Keywords[i][0] = 0x83;      // Keywords[i] = asm('xor edx, KeywordsMeta[i]') +
            Keywords[i][1] = 0xf2;
            Keywords[i][2] = KeywordsMeta[i];
            Keywords[i][3] = 0x88;      //               asm_prefix('mov byte ptr ds:xxxxxxxx, dl')
            Keywords[i][4] = 0x15;
            break;
        case 1:
            Keywords[i][0] = 0x83;      // Keywords[i] = asm('xor eax, KeywordsMeta[i]') +
            Keywords[i][1] = 0xf0;
            Keywords[i][2] = KeywordsMeta[i];
            Keywords[i][3] = 0xa2;      //               asm_prefix('mov byte ptr ds:xxxxxxxx, al')
            break;
        default:
            Keywords[i][0] = 0x83;      // Keywords[i] = asm('xor ecx, KeywordsMeta[i]') +
            Keywords[i][1] = 0xf1;
            Keywords[i][2] = KeywordsMeta[i];
            Keywords[i][3] = 0x88;      //               asm_prefix('mov byte ptr ds:xxxxxxxx, cl')
            Keywords[i][4] = 0x0D;
            break;
        }

    }
}

bool PatchSolution2::FindPatchOffset() noexcept {
    PIMAGE_SECTION_HEADER ptextSectionHeader = _LibccDllInterpreter.GetSectionHeader(".text");
    uint8_t* ptextSection = _LibccDllInterpreter.GetSectionView<uint8_t>(".text");
    off_t Offsets[0x188];

    for (size_t i = 0; i < 0x188; ++i)
        Offsets[i] = -1;

    if (ptextSectionHeader == nullptr)
        return false;
    if (ptextSection == nullptr)
        return false;

    BuildKeywords();

    // Find offsets
    {
        size_t FirstKeywordCounter = 0;
        uint32_t Hints[3];
        DWORD PossibleRangeStart = 0xffffffff;
        DWORD PossibleRangeEnd;
        for (DWORD i = 0; i < ptextSectionHeader->SizeOfRawData; ++i) {
            if (memcmp(ptextSection + i, Keywords[0], sizeof(Keywords[0])) == 0) {
                Hints[FirstKeywordCounter++] =
                    *reinterpret_cast<uint32_t*>(ptextSection + i + sizeof(Keywords[0]));
                if (i < PossibleRangeStart)
                    PossibleRangeStart = i;
            }
        }

        PossibleRangeStart -= 0x1000;
        PossibleRangeEnd = PossibleRangeStart + 0x100000;

        // Keywords[0] should occur 3 times.
        if (FirstKeywordCounter != 3)
            return false;

        Helper::QuickSort(Hints, 0, _countof(Hints));

        // assert
        // if not satisfied, refuse to patch
        if (Hints[2] - Hints[0] != 0x127382BE - 0x12738210)
            return false;

        for (size_t i = 0; i < 0x188; ++i) {
            uint8_t CurrentKeyword[9];
            size_t CurrentKeywordSize = i % 3 == 1 ? 4 : 5;
            memcpy(CurrentKeyword, Keywords[i], CurrentKeywordSize);
            *reinterpret_cast<uint32_t*>(CurrentKeyword + CurrentKeywordSize) = Hints[0] + i;
            CurrentKeywordSize += sizeof(uint32_t);

            for (DWORD j = PossibleRangeStart; j < PossibleRangeEnd; ++j) {
                if (memcmp(ptextSection + j, CurrentKeyword, CurrentKeywordSize) == 0) {
                    Offsets[i] = ptextSectionHeader->PointerToRawData + j;
                    break;
                }
            }

            // if not found, refuse to patch
            if (Offsets[i] == -1)
                return false;
        }
    }

    _tprintf_s(TEXT("[*] PatchSolution2 ...... Ready to apply\n"));
    for (size_t i = 0; i < 0x188; ++i) {
        _PatchOffsets[i] = Offsets[i];
        _tprintf_s(TEXT(" |--[*] Keyword[%zu]\n"), i);
        _tprintf_s(TEXT(" |   | - Offset = +0x%.8zx\n"), _PatchOffsets[i]);
        _tprintf_s(TEXT(" |"));
    }

    return true;
}

#endif

bool PatchSolution2::CheckKey(RSACipher* pCipher) const {
    std::string PublicKeyString =
        pCipher->ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();

    Helper::StringRemove<std::string>(PublicKeyString, "-----BEGIN PUBLIC KEY-----");
    Helper::StringRemove<std::string>(PublicKeyString, "-----END PUBLIC KEY-----");
    Helper::StringRemove<std::string>(PublicKeyString, "\n");

    return PublicKeyString.length() == 0x188;
}

void PatchSolution2::MakePatch(RSACipher* pCipher) const {
    uint8_t* pView = _LibccDllInterpreter.GetImageBase<uint8_t>();

    std::string PublicKeyString =
        pCipher->ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();

    Helper::StringRemove<std::string>(PublicKeyString, "-----BEGIN PUBLIC KEY-----");
    Helper::StringRemove<std::string>(PublicKeyString, "-----END PUBLIC KEY-----");
    Helper::StringRemove<std::string>(PublicKeyString, "\n");

    _putts(TEXT("******************************************"));
    _putts(TEXT("*            PatchSulution2              *"));
    _putts(TEXT("******************************************"));

    for (size_t i = 0; i < 0x188; ++i) {
        _tprintf_s(TEXT("@ +0x%.8lx: %.2X %.2X %.2X ---> "),
                   _PatchOffsets[i],
                   pView[_PatchOffsets[i]],
                   pView[_PatchOffsets[i] + 1],
                   pView[_PatchOffsets[i] + 2]);
        pView[_PatchOffsets[i] + 2] = PublicKeyString[i];
        _tprintf_s(TEXT("%.2X %.2X %.2X\n"),
                   pView[_PatchOffsets[i]],
                   pView[_PatchOffsets[i] + 1],
                   pView[_PatchOffsets[i] + 2]);
    }
}


code:
#include <tchar.h>
#include <windows.h>
#include "Exception.hpp"
#include "ExceptionSystem.hpp"
#include "FileManipulation.hpp"
#include "PatchSolution.hpp"

#undef __BASE_FILE__
#define __BASE_FILE__ TEXT("_tmain.cpp")

#define PRINT_MESSAGE_LITERAL(m) _putts(TEXT(m))
#define PRINT_PTSTR(m) _putts(m)
#define PRINT_PCSTR(m)  puts(m)
#define PRINT_PCWSTR(m) _putws(m)

static void Welcome() {
    _putts(TEXT("***************************************************"));
    _putts(TEXT("*       Navicat Patcher by @DoubleLabyrinth       *"));
    _putts(TEXT("*                   Version: 3.2                  *"));
    _putts(TEXT("***************************************************"));
    _putts(TEXT(""));
    _putts(TEXT("Press Enter to continue or Ctrl + C to abort."));
    _gettchar();
}

static void Help() {
    _putts(TEXT("Usage:"));
    _putts(TEXT("    navicat-patcher.exe <Navicat Installation Path> [RSA-2048 PEM File Path]"));
    _putts(TEXT(""));
    _putts(TEXT("    <Navicat Installation Path>  The folder path where Navicat is installed."));
    _putts(TEXT("                                 This parameter must be specified."));
    _putts(TEXT(""));
    _putts(TEXT("    [RSA-2048 PEM File Path]     The path to an RSA-2048 private key file."));
    _putts(TEXT("                                 This parameter is optional."));
    _putts(TEXT("                                 If not specified, an RSA-2048 private key file"));
    _putts(TEXT("                                 named \"RegPrivateKey.pem\" will be generated."));
    _putts(TEXT(""));
    _putts(TEXT("Example:"));
    _putts(TEXT("    navicat-patcher.exe \"C:\\Program Files\\PremiumSoft\\Navicat Premium 12\""));
}

static void BackupFile(const TString& From, const TString& To) {
    if (::CopyFile(From.c_str(), To.c_str(), TRUE) == FALSE)
        throw SystemException(__BASE_FILE__, __LINE__, GetLastError(),
                              TEXT("CopyFile failed."));
}

static void LoadKey(RSACipher* pCipher, PTSTR pKeyFileName,
                    PatchSolution* pSolution0,
                    PatchSolution* pSolution1,
                    PatchSolution* pSolution2,
                    PatchSolution* pSolution3) {
    if (pKeyFileName) {
        _tprintf_s(TEXT("[*] Import RSA-2048 key from file %s\n"), pKeyFileName);

        pCipher->ImportKeyFromFile<RSAKeyType::PrivateKey, RSAKeyFormat::PEM>(TStringEncode(pKeyFileName, CP_UTF8));

        if (pSolution0 && !pSolution0->CheckKey(pCipher) ||
            pSolution1 && !pSolution1->CheckKey(pCipher) ||
            pSolution2 && !pSolution2->CheckKey(pCipher) ||
            pSolution3 && !pSolution3->CheckKey(pCipher))
            throw Exception(__BASE_FILE__, __LINE__,
                            TEXT("The RSA private key you provide cannot be used."));
    } else {
        _putts(TEXT("[*] Generating new RSA private key, it may take a long time."));

        do {
            pCipher->GenerateKey(2048);
        } while (pSolution0 && !pSolution0->CheckKey(pCipher) ||
                 pSolution1 && !pSolution1->CheckKey(pCipher) ||
                 pSolution2 && !pSolution2->CheckKey(pCipher) ||
                 pSolution3 && !pSolution3->CheckKey(pCipher));   // re-generate RSA key if one of 'CheckKey's return false

        pCipher->ExportKeyToFile<RSAKeyType::PrivateKey, RSAKeyFormat::NotSpecified>("RegPrivateKey.pem");
        _putts(TEXT("[*] New RSA-2048 private key has been saved to RegPrivateKey.pem."));
    }

    std::string PublicKeyString = pCipher->ExportKeyString<RSAKeyType::PublicKey, RSAKeyFormat::PEM>();

    _putts(TEXT("[*] Your RSA public key:"));
    printf_s("\n%s\n", PublicKeyString.c_str());
}

static void ExceptionReport(const Exception& e) noexcept {
    _tprintf_s(TEXT("[*] ERROR: %s\n"), e.Message().c_str());
    _tprintf_s(TEXT(" | - File: %s\n"), e.File().c_str());
    _tprintf_s(TEXT(" | - Line: %zu\n"), e.Line());
    if (e.HasErrorCode()) {
        _tprintf_s(TEXT(" | - ErrorCode: 0x%.8zx\n"), e.ErrorCode());
        _tprintf_s(TEXT(" | - ErrorString: %s\n"), e.ErrorString().c_str());
    }

    if (e.Hints().size() != 0) {
        _putts(TEXT(" | - Hints:"));
        for (auto& Hint : e.Hints())
            _tprintf_s(TEXT(" |   %s\n"), Hint.c_str());
    }

    _putts(TEXT(""));
}

int _tmain(int argc, PTSTR argv[]) {
    if (argc != 2 && argc != 3) {
        Help();
        return 0;
    }

    Welcome();

    TString InstallationPath;
    TString MainExeName;
    TString LibccDllName;
    ResourceObject<CppObjectTraits<RSACipher>> pCipher;
    ResourceObject<CppObjectTraits<FileObject>> MainExe;
    ResourceObject<CppObjectTraits<FileMapViewObject>> MainExeMapper;
    ResourceObject<CppObjectTraits<FileObject>> LibccDll;
    ResourceObject<CppObjectTraits<FileMapViewObject>> LibccDllMapper;
    ResourceObject<CppObjectTraits<PatchSolution>> pSolution0;
    ResourceObject<CppObjectTraits<PatchSolution>> pSolution1;
    ResourceObject<CppObjectTraits<PatchSolution>> pSolution2;
    ResourceObject<CppObjectTraits<PatchSolution>> pSolution3;

    try {
        DWORD Attribute = GetFileAttributes(argv[1]);
        if (Attribute == INVALID_FILE_ATTRIBUTES)
            throw SystemException(__BASE_FILE__, __LINE__, GetLastError(),
                                  TEXT("GetFileAttributes failed."));

        if ((Attribute & FILE_ATTRIBUTE_DIRECTORY) == 0)
            throw Exception(__BASE_FILE__, __LINE__,
                            TEXT("Path does not point to a directory."));

        InstallationPath = argv[1];
        if (InstallationPath.back() != TEXT('\\') && InstallationPath.back() != TEXT('/'))
            InstallationPath.push_back(TEXT('/'));  // for Linux compatible
    } catch (Exception& e) {
        e.Hints().emplace_back(TEXT("Are you sure the path you specified is correct?"));
        e.Hints().emplace_back(TEXT("The path you specified:"));
        e.Hints().emplace_back(argv[1]);
        ExceptionReport(e);
        return -1;
    }

    pCipher.TakeOver(new RSACipher());

    // -----------------
    //  Map files
    // -----------------
    do {
        try {
            _tprintf_s(TEXT("[*] Try to open libcc.dll   ...... "));
            LibccDll.TakeOver(FileObject::Open<FileObject*>(InstallationPath + TEXT("libcc.dll"),
                                                            GENERIC_READ | GENERIC_WRITE,
                                                            FILE_SHARE_READ));
            LibccDllMapper.TakeOver(FileMapViewObject::Create<FileMapViewObject*>(LibccDll->GetHandle(),
                                                                                PAGE_READWRITE));
            LibccDllMapper->Map(FILE_MAP_READ | FILE_MAP_WRITE);
            LibccDllName = TEXT("libcc.dll");
            _tprintf_s(TEXT("Succeeded\n"));
            break;
        } catch (Exception& e) {
            if (e.HasErrorCode() && e.ErrorCode() == ERROR_FILE_NOT_FOUND) {
                _tprintf_s(TEXT("Not found\n"));
                LibccDllMapper.Release();
                LibccDll.Release();
            } else {
                _tprintf_s(TEXT("Failed\n"));
                if (e.HasErrorCode() && e.ErrorCode() == ERROR_ACCESS_DENIED)
                    e.Hints().emplace_back(TEXT("Please re-run with Administrator privilege."));
                ExceptionReport(e);
                return -1;
            }
        }

        try {
            _tprintf_s(TEXT("[*] Try to open Navicat.exe ...... "));
            MainExe.TakeOver(FileObject::Open<FileObject*>(InstallationPath + TEXT("Navicat.exe"),
                                                           GENERIC_READ | GENERIC_WRITE,
                                                           FILE_SHARE_READ));
            MainExeMapper.TakeOver(FileMapViewObject::Create<FileMapViewObject*>(MainExe->GetHandle(),
                                                                                PAGE_READWRITE));
            MainExeMapper->Map(FILE_MAP_READ | FILE_MAP_WRITE);
            MainExeName = TEXT("Navicat.exe");
            _tprintf_s(TEXT("Succeeded\n"));
            break;
        } catch (Exception& e) {
            if (e.HasErrorCode() && e.ErrorCode() == ERROR_FILE_NOT_FOUND) {
                _tprintf_s(TEXT("Not found\n"));
                MainExeMapper.Release();
                MainExe.Release();
            } else {
                _tprintf_s(TEXT("Failed\n"));
                if (e.HasErrorCode() && e.ErrorCode() == ERROR_ACCESS_DENIED)
                    e.Hints().emplace_back(TEXT("Please re-run with Administrator privilege."));
                ExceptionReport(e);
                return -1;
            }
        }

        try {
            _tprintf_s(TEXT("[*] Try to open Modeler.exe ...... "));
            MainExe.TakeOver(FileObject::Open<FileObject*>(InstallationPath + TEXT("Modeler.exe"),
                                                           GENERIC_READ | GENERIC_WRITE,
                                                           FILE_SHARE_READ));
            MainExeMapper.TakeOver(FileMapViewObject::Create<FileMapViewObject*>(MainExe->GetHandle(),
                                                                                PAGE_READWRITE));
            MainExeMapper->Map(FILE_MAP_READ | FILE_MAP_WRITE);
            MainExeName = TEXT("Modeler.exe");
            _tprintf_s(TEXT("Succeeded\n"));
            break;
        } catch (Exception& e) {
            if (e.HasErrorCode() && e.ErrorCode() == ERROR_FILE_NOT_FOUND) {
                _tprintf_s(TEXT("Not found\n"));
                MainExeMapper.Release();
                MainExe.Release();
            } else {
                _tprintf_s(TEXT("Failed\n"));
                if (e.HasErrorCode() && e.ErrorCode() == ERROR_ACCESS_DENIED)
                    e.Hints().emplace_back(TEXT("Please re-run with Administrator privilege."));
                ExceptionReport(e);
                return -1;
            }
        }

        try {
            _tprintf_s(TEXT("[*] Try to open Rviewer.exe ...... "));
            MainExe.TakeOver(FileObject::Open<FileObject*>(InstallationPath + TEXT("Rviewer.exe"),
                                                           GENERIC_READ | GENERIC_WRITE,
                                                           FILE_SHARE_READ));
            MainExeMapper.TakeOver(FileMapViewObject::Create<FileMapViewObject*>(MainExe->GetHandle(),
                                                                                PAGE_READWRITE));
            MainExeMapper->Map(FILE_MAP_READ | FILE_MAP_WRITE);
            MainExeName = TEXT("Rviewer.exe");
            _tprintf_s(TEXT("Succeeded\n"));
            break;
        } catch (Exception& e) {
            if (e.HasErrorCode() && e.ErrorCode() == ERROR_FILE_NOT_FOUND) {
                _tprintf_s(TEXT("Not found\n"));
                MainExeMapper.Release();
                MainExe.Release();
            } else {
                _tprintf_s(TEXT("Failed\n"));
                if (e.HasErrorCode() && e.ErrorCode() == ERROR_ACCESS_DENIED)
                    e.Hints().emplace_back(TEXT("Please re-run with Administrator privilege."));
                ExceptionReport(e);
                return -1;
            }
        }

        ExceptionReport(Exception(__BASE_FILE__, __LINE__,
                                  TEXT("None of target files has been found."),
                                  TEXT("Are you sure the path you specified is correct?"),
                                  TEXT("The path you specified:"),
                                  argv[1]));
        return -1;
    } while (false);

    // -----------
    //  decide PatchSolutions
    // -----------

    if (MainExeMapper.IsValid()) {
        try {
            pSolution0.TakeOver(new PatchSolution0());
            pSolution0->SetFile(MainExeMapper->View<void>());
            if (pSolution0->FindPatchOffset() == false) {
                _putts(TEXT("[*] PatchSolution0 ...... Omitted"));
                pSolution0.Release();
            }
        } catch (Exception& e) {
            ExceptionReport(e);
            return -1;
        }
    } else {
        _putts(TEXT("[*] PatchSolution0 ...... Omitted"));
    }

    if (LibccDllMapper.IsValid()) {
        try {
            pSolution1.TakeOver(new PatchSolution1());
            pSolution2.TakeOver(new PatchSolution2());
            pSolution3.TakeOver(new PatchSolution3());

            pSolution1->SetFile(LibccDllMapper->View<void>());
            pSolution2->SetFile(LibccDllMapper->View<void>());
            pSolution3->SetFile(LibccDllMapper->View<void>());

            if (pSolution1->FindPatchOffset() == false) {
                _putts(TEXT("[*] PatchSolution1 ...... Omitted"));
                pSolution1.Release();
            }

            if (pSolution2->FindPatchOffset() == false) {
                _putts(TEXT("[*] PatchSolution2 ...... Omitted"));
                pSolution2.Release();
            }

            if (pSolution3->FindPatchOffset() == false) {
                _putts(TEXT("[*] PatchSolution3 ...... Omitted"));
                pSolution3.Release();
            }
        } catch (Exception& e) {
            ExceptionReport(e);
            return -1;
        }
    } else {
        _putts(TEXT("[*] PatchSolution1 ...... Omitted"));
        _putts(TEXT("[*] PatchSolution2 ...... Omitted"));
        _putts(TEXT("[*] PatchSolution3 ...... Omitted"));
    }

    if (pSolution0.IsValid() == false) {
        MainExeMapper.Release();
        MainExe.Release();
    }

    if (pSolution1.IsValid() == false && pSolution2.IsValid() == false && pSolution3.IsValid() == false) {
        LibccDllMapper.Release();
        LibccDll.Release();
    }

    if (pSolution0.IsValid() == false &&
        pSolution1.IsValid() == false &&
        pSolution2.IsValid() == false &&
        pSolution3.IsValid() == false)
    {
        ExceptionReport(Exception(__BASE_FILE__, __LINE__,
                                  TEXT("Cannot find Navicat official RSA public key."),
                                  TEXT("Are you sure your Navicat has not been patched/modified before?")));
        return -1;
    }

    // -------------
    //  LoadKey
    // -------------
    try {
        LoadKey(pCipher,
                argc == 3 ? argv[2] : nullptr,
                pSolution0,
                pSolution1,
                pSolution2,
                pSolution3);
    } catch (Exception& e) {
        ExceptionReport(e);
        return -1;
    }

    // -------------
    //  BackupFile
    // -------------
    try {
        if (MainExe.IsValid())
            BackupFile(InstallationPath + MainExeName,
                       InstallationPath + MainExeName + TEXT(".backup"));
    } catch (Exception& e) {
        if (e.HasErrorCode() && e.ErrorCode() == ERROR_FILE_EXISTS) {
            e.Hints().emplace_back(TStringFormat(TEXT("The backup of %s has been found."),
                                                 MainExeName.c_str()));
            e.Hints().emplace_back(TStringFormat(TEXT("Please remove %s.backup in Navicat installation folder if you're sure %s has not been patched."),
                                                 MainExeName.c_str(),
                                                 MainExeName.c_str()));
            e.Hints().emplace_back(TStringFormat(TEXT("Otherwise please restore %s by %s.backup and remove %s.backup then try again."),
                                                 MainExeName.c_str(),
                                                 MainExeName.c_str(),
                                                 MainExeName.c_str()));
        }
        ExceptionReport(e);
        return -1;
    }

    try {
        if (LibccDll.IsValid())
            BackupFile(InstallationPath + LibccDllName,
                       InstallationPath + LibccDllName + TEXT(".backup"));
    } catch (Exception& e) {
        if (e.HasErrorCode() && e.ErrorCode() == ERROR_FILE_EXISTS) {
            e.Hints().emplace_back(TStringFormat(TEXT("The backup of %s has been found."),
                                                 LibccDllName.c_str()));
            e.Hints().emplace_back(TStringFormat(TEXT("Please remove %s.backup in Navicat installation folder if you're sure %s has not been patched."),
                                                 LibccDllName.c_str(),
                                                 LibccDllName.c_str()));
            e.Hints().emplace_back(TStringFormat(TEXT("Otherwise please restore %s by %s.backup and remove %s.backup then try again."),
                                                 LibccDllName.c_str(),
                                                 LibccDllName.c_str(),
                                                 LibccDllName.c_str()));
        }
        ExceptionReport(e);
        return -1;
    }

    // -------------
    //  MakePatch
    // -------------
    try {
        if (pSolution0.IsValid()) {
            pSolution0->MakePatch(pCipher);
            _putts(TEXT(""));
        }
        if (pSolution1.IsValid()) {
            pSolution1->MakePatch(pCipher);
            _putts(TEXT(""));
        }
        if (pSolution2.IsValid()) {
            pSolution2->MakePatch(pCipher);
            _putts(TEXT(""));
        }
        if (pSolution3.IsValid()) {
            pSolution3->MakePatch(pCipher);
            _putts(TEXT(""));
        }
    } catch (Exception& e) {
        ExceptionReport(e);
        return -1;
    }

    _putts(TEXT("[*] Patch has been done successfully."));
    _putts(TEXT("[*] Have fun and enjoy~"));
    return 0;
}


code:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x64">
      <Configuration>Debug</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x64">
      <Configuration>Release</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <ProjectGuid>{30BE8CAB-6137-4441-9F64-599D0A6EEA0C}</ProjectGuid>
    <Keyword>Win32Proj</Keyword>
    <RootNamespace>navicatpatcher</RootNamespace>
    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
    <SpectreMitigation>false</SpectreMitigation>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
    <SpectreMitigation>false</SpectreMitigation>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
    <SpectreMitigation>false</SpectreMitigation>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
    <SpectreMitigation>false</SpectreMitigation>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Label="Shared">
    <Import Project="..\common\common.vcxitems" Label="Shared" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LinkIncremental>true</LinkIncremental>
    <IncludePath>$(SolutionDir)libraries\openssl\include;$(SolutionDir)libraries\capstone-win32\include;$(IncludePath)</IncludePath>
    <LibraryPath>$(SolutionDir)libraries\openssl\lib;$(SolutionDir)libraries\capstone-win32;$(LibraryPath)</LibraryPath>
    <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
    <IntDir>$(Platform)\$(Configuration)\</IntDir>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LinkIncremental>true</LinkIncremental>
    <IncludePath>$(SolutionDir)libraries\openssl\include64;$(SolutionDir)libraries\capstone-win64\include;$(IncludePath)</IncludePath>
    <LibraryPath>$(SolutionDir)libraries\openssl\lib64;$(SolutionDir)libraries\capstone-win64;$(LibraryPath)</LibraryPath>
    <ReferencePath>$(ReferencePath)</ReferencePath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LinkIncremental>false</LinkIncremental>
    <IncludePath>$(SolutionDir)libraries\openssl\include;$(SolutionDir)libraries\capstone-win32\include;$(IncludePath)</IncludePath>
    <LibraryPath>$(SolutionDir)libraries\openssl\lib;$(SolutionDir)libraries\capstone-win32;$(LibraryPath)</LibraryPath>
    <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
    <IntDir>$(Platform)\$(Configuration)\</IntDir>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LinkIncremental>false</LinkIncremental>
    <IncludePath>$(SolutionDir)libraries\openssl\include64;$(SolutionDir)libraries\capstone-win64\include;$(IncludePath)</IncludePath>
    <LibraryPath>$(SolutionDir)libraries\openssl\lib64;$(SolutionDir)libraries\capstone-win64;$(LibraryPath)</LibraryPath>
    <ReferencePath>$(ReferencePath)</ReferencePath>
  </PropertyGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>Disabled</Optimization>
      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <SDLCheck>true</SDLCheck>
      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
      <AdditionalOptions>
      </AdditionalOptions>
      <LanguageStandard>stdcpplatest</LanguageStandard>
      <UseFullPaths>true</UseFullPaths>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <IgnoreSpecificDefaultLibraries>
      </IgnoreSpecificDefaultLibraries>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <ClCompile>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>Disabled</Optimization>
      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <SDLCheck>true</SDLCheck>
      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
      <AdditionalOptions>
      </AdditionalOptions>
      <LanguageStandard>stdcpplatest</LanguageStandard>
      <UseFullPaths>true</UseFullPaths>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <IgnoreSpecificDefaultLibraries>
      </IgnoreSpecificDefaultLibraries>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <ClCompile>
      <WarningLevel>Level3</WarningLevel>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <Optimization>MaxSpeed</Optimization>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <SDLCheck>true</SDLCheck>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <AdditionalOptions>
      </AdditionalOptions>
      <LanguageStandard>stdcpplatest</LanguageStandard>
      <UseFullPaths>true</UseFullPaths>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>false</GenerateDebugInformation>
      <IgnoreSpecificDefaultLibraries>
      </IgnoreSpecificDefaultLibraries>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <ClCompile>
      <WarningLevel>Level3</WarningLevel>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <Optimization>MaxSpeed</Optimization>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <SDLCheck>true</SDLCheck>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <AdditionalOptions>
      </AdditionalOptions>
      <LanguageStandard>stdcpplatest</LanguageStandard>
      <UseFullPaths>true</UseFullPaths>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>false</GenerateDebugInformation>
      <IgnoreSpecificDefaultLibraries>
      </IgnoreSpecificDefaultLibraries>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup>
    <ClCompile Include="CapstoneDisassembler.cpp" />
    <ClCompile Include="Helper.cpp" />
    <ClCompile Include="ImageInterpreter.cpp" />
    <ClCompile Include="PatchSolution0.cpp" />
    <ClCompile Include="PatchSolution1.cpp" />
    <ClCompile Include="PatchSolution2.cpp" />
    <ClCompile Include="_tmain.cpp" />
    <ClCompile Include="PatchSolution3.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="CapstoneDisassembler.hpp" />
    <ClInclude Include="ExceptionCapstone.hpp" />
    <ClInclude Include="FileManipulation.hpp" />
    <ClInclude Include="Helper.hpp" />
    <ClInclude Include="ImageInterpreter.hpp" />
    <ClInclude Include="NavicatCrypto.hpp" />
    <ClInclude Include="PatchSolution.hpp" />
  </ItemGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
</Project>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Filter Include="源文件">
      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
    </Filter>
    <Filter Include="头文件">
      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
    </Filter>
    <Filter Include="资源文件">
      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
    </Filter>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="Helper.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="_tmain.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="PatchSolution3.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="PatchSolution0.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="PatchSolution2.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="PatchSolution1.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="CapstoneDisassembler.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
    <ClCompile Include="ImageInterpreter.cpp">
      <Filter>源文件</Filter>
    </ClCompile>
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="NavicatCrypto.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="PatchSolution.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="Helper.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="ImageInterpreter.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="CapstoneDisassembler.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="FileManipulation.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
    <ClInclude Include="ExceptionCapstone.hpp">
      <Filter>头文件</Filter>
    </ClInclude>
  </ItemGroup>
</Project>


code:
This mod is a rework of the orewood mod by desvox: https://forum.minetest.net/viewtopic.php?t=12093

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Code: CC0 (https://creativecommons.org/publicdomain/zero/1.0/).

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Textures: CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/) and CC0 (https://creativecommons.org/publicdomain/zero/1.0/). See detailed list in license.txt or mod.conf.
Anything not listed here is either an oversight (in which case, be sure to report in the forum post) or not included in the mod's texture folder (because if I'm NOT distributing them, I don't need to mention them. Right? Let me know if I'm wrong).

>>>>>List of textures derived from minetest game (under CC BY-SA 3.0 https://creativecommons.org/licenses/by-sa/3.0/):
"orewood_cover_mf.png" and "orewood_cover_mp.png" Derived from "default_papyrus.png" by Calinou;
"orewood_claypapy_brick.png" Derived from "default_clay_brick.png" by BlockMen and "default_papyrus.png" by Calinou;
"orewood_claywood_hot.png" Derived from "default_clay_brick.png" by BlockMen;
"orewood_claywood.png" Derived from "default_clay_lump.png" by Gambit;
"orewood_coalwood.png" Derived from "default_coal_lump.png" by Gambit;
"orewood_copperwood_hot.png" Derived from "default_copper_ingot.png" by Gambit;
"orewood_copperwood.png" Derived from "default_copper_lump.png" by Gambit;
"orewood_diamondwood.png" Derived from "default_diamond.png" by BlockMen;
"orewood_goldwood.png" Derived from "default_gold_lump.png" by Gambit;
"orewood_goldwood_hot.png" Derived from "default_gold_ingot.png" by BlockMen;
"orewood_ironpapy.png" Derived from "default_iron_lump.png" by Gambit and "default_papyrus.png" by Calinou;
"orewood_ironwood.png" Derived from "default_iron_lump.png" by Gambit;
"orewood_steelwood.png" Derived from "default_steel_ingot.png" by BlockMen;
"orewood_tinwood.png" Derived from "default_tin_lump.png" by kilbith;
"orewood_tinwood_hot.png" Derived from "default_tin_ingot.png" by kilbith;

>>>Those "old" textures MAY BE REMOVED in future versions.
Credits will be kept for a while even after removal.
(under CC BY-SA 3.0 https://creativecommons.org/licenses/by-sa/3.0/):
"OLDclaywood.png" Derived from "default_clay_lump.png" by Gambit;
"OLDcoalwood_jungle.png" Derived from "default_coal_lump.png" by Gambit;
"OLDcopperwood.png" Derived from "default_copper_lump.png" by Gambit;
"OLDdiamondwood_jungle.png" Derived from "default_diamond.png" by BlockMen;
"OLDgoldwood_jungle.png" Derived from "default_gold_lump.png" by Gambit;
"OLDironwood_jungle.png" Derived from "default_iron_lump.png" by Gambit;
"OLDtinwood_jungle.png" Derived from "default_tin_lump.png" by kilbith;
"claypapy.png" Derived from "default_clay_lump.png" by Gambit and "default_papyrus.png" by Calinou;
"coalpapy.png" Derived from "default_coal_lump.png" by Gambit and "default_papyrus.png" by Calinou;
"copperpapy.png" Derived from "default_copper_lump.png" by Gambit and "default_papyrus.png" by Calinou;
"copperpapy_ingot.png" Derived from "default_copper_ingot.png" by Gambit and "default_papyrus.png" by Calinou;
"diamondpapy.png" Derived from "default_diamond.png" by BlockMen and "default_papyrus.png" by Calinou;
"goldpapy.png" Derived from "default_gold_lump.png" by Gambit and "default_papyrus.png" by Calinou;
"goldpapy_ingot.png" Derived from "default_gold_ingot.png" by BlockMen and "default_papyrus.png" by Calinou;
"lavapapy.png" Derived from "default_lava.png" by Cisoun and "default_papyrus.png" by Calinou;
"mesepapy.png" Derived from "default_mese_crystal.png" by VanessaE and "default_papyrus.png" by Calinou;
"obsidianpapy.png" Derived from "default_obsidian.png" by jojoa1997 and "default_papyrus.png" by Calinou;
"riverpapy.png" Derived from "default_lava.png" by Cisoun and "default_papyrus.png" by Calinou;
"steelpapy_ingot.png" Derived from "default_steel_ingot.png" by BlockMen and "default_papyrus.png" by Calinou;
"tinpapy.png" Derived from "default_tin_lump.png" by kilbith and "default_papyrus.png" by Calinou;
"tinpapy_ingot.png" Derived from "default_tin_ingot.png" by kilbith and "default_papyrus.png" by Calinou;
"snowpapy.png" Derived from "default_snowball.png" by Gambit and "default_papyrus.png" by Calinou;

>>>>>List of textures under CC0 (https://creativecommons.org/publicdomain/zero/1.0/):
"orewood_dust_green.png"
"orewood_dust_red.png"
"orewood_dust_yellow.png"
"orewood_fluidwood.png"
"orewood_fluidwood_top.png"
"OLDfluidwood.png"
"OLDfluidwood_top.png"

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CC BY-SA 3.0 details:
https://creativecommons.org/licenses/by-sa/3.0/
BASICALY: you can copy, modify and distribute as long as you give credit, indicate the changes and use the same license
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CC0 details:
https://creativecommons.org/publicdomain/zero/1.0/
BASICALY: public domain


code:
Orewood Reborn Version 0.04
Compatible minetest versions: 5.0.0 and newer*
Incompatible games: any games that do NOT have the "default" mod
A mod that adds some kind of in-game crafting guide is recomended.

WARNING: THIS MOD IS A WORK IN PROGRESS (WIP).
Be sure to report any bugs and typos you find!

*Untested on older minetest versions.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
What this mod does:

Allows you to craft orewood.
Place orewood near water to grow papyr.
Place hot wood near lava to grow ingot papyr.
Fluid wood grows something different based on neighboring liquid and active mods.
Harvest papyr for resources.

Place any 4 wood in the mid edges and any 4 stone in the corners of the crafting grid with a block of the desired material in the mid slot to craft orewood.
Cook orewood to get hot wood.
Surround any orewood of a given mod with 8 blocks of the desired material to get fake wood.
Place 1 hot wood (midle slot) and 8 fake wood (surrounding slots) in crafting grid to get fluid wood.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Originaly made by desvox: https://forum.minetest.net/viewtopic.php?t=12093

Changes from the original:
Cooking orewood will give you hotwood (for ores that have ingots and for clay) instead of its resource.
New growables: tin, clay, obsidian, non-renewable liquids, ingots.
Textures were changed.
I may or may not have changed the rarity of some orewoods.
Hot wood and Fluid wood.
See "changelog.txt" for more details.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
How to install:
1) Find Minetest's "mod" folder
2) Extract the zip
3) Put the orewood_reborn folder in the mod folder
4) Run the Minetest Engine
5) Configure a world to enable the mod
6) Have fun.

Note when using other games than minetest_game:
Make sure the "default" mod (already available in minetest_game) is available in your current game as well.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
License:

Code: CC0
Textures: CC BY-SA 3.0 and CC0
See detailed texture list and credits in CREDITS.txt

CC BY-SA 3.0 details:
https://creativecommons.org/licenses/by-sa/3.0/
(BASICALY: you can copy, modify and distribute as long as you give credit, indicate the changes and use the same license)

CC0 details:
https://creativecommons.org/publicdomain/zero/1.0/
(BASICALY: public domain dedication)

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Special thanks:
To developers who leave some of their mods under licenses that allow people to use the code as a reference without need for them to change their mod's licenses;
To desvox for making the original mod that I adopted;
To you, for using it.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
If you have questions, suggestions, feedback or want to report problems/bugs, post a reply at the forum post:
https://forum.minetest.net/viewtopic.php?f=9&t=20589


code:
-- Orewood Reborn ABM Registry. I would like to thank desvox for making the orewood mod this one originated from.

-- Functions to sprout papy from logs

minetest.register_abm({
	nodenames = {"orewood:ironwood", "orewood:ironwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:ironwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:ironwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:ironpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:steelwood_hot"},
	neighbors = {"default:lava_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:steelwood_hot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:steelpapy_ingot"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:coalwood", "orewood:coalwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:coalwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:coalwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:coalpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:tinwood", "orewood:tinwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:tinwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:tinwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:tinpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:tinwood_hot"},
	neighbors = {"default:lava_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:tinwood_hot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:tinpapy_ingot"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:copperwood", "orewood:copperwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:copperwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:copperwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:copperpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:copperwood_hot"},
	neighbors = {"default:lava_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:copperwood_hot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:copperpapy_ingot"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:mesewood", "orewood:mesewood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:mesewood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:mesewood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:mesepapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:diamondwood", "orewood:diamondwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:diamondwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:diamondwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:diamondpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:goldwood", "orewood:goldwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:goldwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:goldwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:goldpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:goldwood_hot"},
	neighbors = {"default:lava_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:goldwood_hot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:goldpapy_ingot"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:obsidianwood", "orewood:obsidianwood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:obsidianwood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:obsidianwood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:obsidianpapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:claywood", "orewood:claywood_jungle"},
	neighbors = {"default:water_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:claywood" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	while node.name == "orewood:claywood_jungle" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:claypapy"})
	end,
})

minetest.register_abm({
	nodenames = {"orewood:claywood_hot"},
	neighbors = {"default:lava_source"},
	interval = 40,
	chance = 10,
	action = function(pos, node)
	local height = 0
	while node.name == "orewood:claywood_hot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:claypapy_brick"})
	end,
})

-- Functions to grow papy that has sprouted

minetest.register_abm({
	nodenames = {"orewood:ironpapy"},
	neighbors = {"orewood:ironwood", "orewood:ironwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:ironwood"
	and name ~= "orewood:ironwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:ironpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:ironpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:steelpapy_ingot"},
	neighbors = {"orewood:steelwood_hot"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:steelwood_hot"
	and name ~= "orewood:steelwood_hot" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:lava"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:steelpapy_ingot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:steelpapy_ingot"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:coalpapy"},
	neighbors = {"orewood:coalwood", "orewood:coalwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:coalwood"
	and name ~= "orewood:coalwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:coalpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:coalpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:tinpapy"},
	neighbors = {"orewood:tinwood", "orewood:tinwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:tinwood"
	and name ~= "orewood:tinwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:tinpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:tinpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:tinpapy_ingot"},
	neighbors = {"orewood:tinwood_hot"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:tinwood_hot"
	and name ~= "orewood:tinwood_hot" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:lava"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:tinpapy_ingot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:tinpapy_ingot"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:copperpapy"},
	neighbors = {"orewood:copperwood", "orewood:copperwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:copperwood"
	and name ~= "orewood:copperwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:copperpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:copperpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:copperpapy_ingot"},
	neighbors = {"orewood:copperwood_hot"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:copperwood_hot"
	and name ~= "orewood:copperwood_hot" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:lava"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:copperpapy_ingot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:copperpapy_ingot"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:obsidianpapy"},
	neighbors = {"orewood:obsidianwood", "orewood:obsidianwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:obsidianwood"
	and name ~= "orewood:obsidianwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:obsidianpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:obsidianpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:diamondpapy"},
	neighbors = {"orewood:diamondwood", "orewood:diamondwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:diamondwood"
	and name ~= "orewood:diamondwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:diamondpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:diamondpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:mesepapy"},
	neighbors = {"orewood:mesewood", "orewood:mesewood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:mesewood"
	and name ~= "orewood:mesewood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:mesepapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:mesepapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:claypapy"},
	neighbors = {"orewood:claywood", "orewood:claywood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:claywood"
	and name ~= "orewood:claywood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:claypapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:claypapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:claypapy_brick"},
	neighbors = {"orewood:claywood_hot"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:claywood_hot"
	and name ~= "orewood:claywood_hot" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:lava"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:claypapy_brick" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:claypapy_brick"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:goldpapy"},
	neighbors = {"orewood:goldwood", "orewood:goldwood_jungle"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:goldwood"
	and name ~= "orewood:goldwood_jungle" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:water"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:goldpapy" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:goldpapy"})
	return true
end,
})

minetest.register_abm({
	nodenames = {"orewood:goldpapy_ingot"},
	neighbors = {"orewood:goldwood_hot"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
	pos.y = pos.y-1
	local name = minetest.get_node(pos).name
	if name ~= "orewood:goldwood_hot"
	and name ~= "orewood:goldwood_hot" then
		return
	end
	if not minetest.find_node_near(pos, 3, {"group:lava"}) then
		return
	end
	pos.y = pos.y+1
	local height = 0
	while node.name == "orewood:goldpapy_ingot" and height < 4 do
		height = height+1
		pos.y = pos.y+1
		node = minetest.get_node(pos)
	end
	if height == 4
	or node.name ~= "air" then
		return
	end
	minetest.set_node(pos, {name="orewood:goldpapy_ingot"})
	return true
end,
})


code:
changelog:
V0.04:
>Added crafting recipes for coalwood, obsidianwood, claywood and NON-river water;
>Added lua files: fluid, moreorescomp, glooptestcomp, techniccomp;
>Disabled "defaultgen.lua" untill a certain problem is solved;
>Removed lua files: recipeCHEAP, recipeHARD;
>>Changed "README.txt" to reflect the changes;
>>Changed crafting recipes to something more balanced;
>>>Fakewood can now be crafted and be more useful;
>>>Made fluidwood more expensive;
>Moved fluidwood ABMs from "abm.lua" to "fluid.lua";
>Made fluidwood able to sprout, but NOT grow a random papyr near NON-river water (the orepapy will always be 1-node tall);
>>snowpapy will still grow to the 4-node-tall limit;
>Added compatibility with glooptest, quartz, technic_worldgen, blox;
>>Gave fluidwood a chance to sprout and grow 3 new papyr nodes near lava when using "technic_worldgen" mod;
>Moved most of the code from "compatibility.lua" to lua files named after the compatible mods;
>Changed the papyr textures to overlayed ones;
>>Updated screenshot to reflect the changes;
>Removed textures: "silverpapy.png", "silverpapy_ingot.png", "mithrilpapy.png", "mithrilpapy_ingot.png";
>Renamed some of the textures and edited "license.txt" to reflect changes;

Problems:
-Can't generate natural orewood in minetest 5.0.0 on worldgen;

--------------------------------------------------------------------------------------------------------------------------------------
V0.03:
>No longer using different textures for normal wood and jungle wood ores;
>Changed fluidwood textures slightly;
>Renamed unused textures and moved them to "old textures" folder;
>Made (hideous) papyr textures for moreores (without even bothering to collect pallette samples);
>Edited "license.txt" to reflect the changes;
>Added some code to "compatibility.lua" (FINALY);
>Added lua files: defaultgen; defaultgenWEAK; recipe; recipeCHEAP; recipeHARD;
>>Edited README.txt to help people who don't know how to open a lua file;
>Moved worldgen code from init.lua to defaultgen.lua;
>Rearranged the code in defaultgen.lua;
>Changed scarcity so that jungle trees and normal trees have the same chance to generate ores:
>>(name:previous tree chance/previous jungle chance-new chance)
>>clay:10/9-9; coal:6/5-6; copper:7/5-8; diamond:10/9-10; gold:10/9-9; iron:7/5-7; mese: 10/9-10; obsidian:12/10-9; tin:8/6-7;
>Added crafting recipes for some wooden ores;
>Added fake wooden ore nodes for expensive crafting;
>Added compatibility with moreores (no worldgen yet);

Problems:
-Attempted to implement that "register_ore" thing, but the game crashed;
-Attempted to make "default" an optional dependency, but the mod refused to live without it;
-Attempted to implement compatibility with MineClone2, but failed to make "default" an optional dependency.

--------------------------------------------------------------------------------------------------------------------------------------
V0.02:
>Added snowpapy. It grows from fluidwood near NON-river water;
>Improved README.txt;
>Removed a lot of information from mod.conf (for now);
>Changed "tinwood.png" and "tinwood_jungle.png" a bit;
>Changed "goldwood.png" and "goldwood_jungle.png" a bit;
>Added lua files: amb; compatibility; crafting; nodes;
>Moved bits of code from init.lua to the matching lua files (see init.lua for details);
>Moved some bits of code arround for organization;

Problems:
-Settings were added but had to be removed because I did something wrong;

--------------------------------------------------------------------------------------------------------------------------------------
To do list:

Add compatibility with underch, caverealms and other underground-based mods;
Add ABMs to grow eyes when fluidwood is near slime source (underch mod);
Figure out how to implement ores on worldgen in minetest 5.0.0;
Figure out how to add description and dependencies to mod.conf;
Figure out how to implement settings;
Add setting to make orewood spawn on planted trees;
Clean the code up (or try to);
Undo the last three above if something explodes (again);
Add compatibility with more mods;
Improve the screenshot;
Improve "license.txt" and "README.txt";
Add textures for mese wood and obsidian wood (or not);
Remember the other things I wanted to add/remove/change;
Add other people's suggestions to this list;

--------------------------------------------------------------------------------------------------------------------------------------
Older versions:

V0.01: posted in the forums.
(see "Changes from the original" in README.txt or forum post for details).