Эх сурвалжийг харах

Line entities now are initialized and functional.

Iñigo Valentin 3 жил өмнө
parent
commit
442672f7bd

+ 4 - 0
V-Gears-Installer/include/ff7DataInstaller.h

@@ -227,7 +227,11 @@ class ModelsAndAnimationsDb{
  * The data installer.
  */
 class FF7DataInstaller{
+
     public:
+
+        static float LINE_SCALE_FACTOR;
+
         /**
          * Installer constructor
          *

+ 40 - 5
V-Gears-Installer/src/ff7DataInstaller.cpp

@@ -53,6 +53,8 @@
 #include <QtCore/QDir>
 #include "common/make_unique.h"
 
+float FF7DataInstaller::LINE_SCALE_FACTOR = 0.0078124970964f;
+
 FF7DataInstaller::FF7DataInstaller(
   std::string input_dir, std::string output_dir,
   std::function<void(std::string)> write_output_line
@@ -764,7 +766,39 @@ static void FF7PcFieldToQGearsField(
                 element->LinkEndChild(xml_entity_point.release());
             }
         }
+
+        // Get lines. Add them to a list, so they aren't processed later as regular entities.
+        std::cout << "Processing lines..." << std::endl;
+        std::vector<std::string> line_entities;
+        for (SUDM::FF7::Field::Line line : decompiled.lines){
+            std::unique_ptr<TiXmlElement> xml_entity_trigger(new TiXmlElement("entity_trigger"));
+            line_entities.push_back(line.name);
+            xml_entity_trigger->SetAttribute("name", line.name);
+            xml_entity_trigger->SetAttribute(
+              "point1",
+              std::to_string(line.ax * FF7DataInstaller::LINE_SCALE_FACTOR)
+                + " " + std::to_string(line.ay * FF7DataInstaller::LINE_SCALE_FACTOR)
+                + " " + std::to_string(line.az * FF7DataInstaller::LINE_SCALE_FACTOR)
+            );
+            xml_entity_trigger->SetAttribute(
+              "point2",
+              std::to_string(line.bx * FF7DataInstaller::LINE_SCALE_FACTOR)
+                + " " + std::to_string(line.by * FF7DataInstaller::LINE_SCALE_FACTOR)
+                + " " + std::to_string(line.bz * FF7DataInstaller::LINE_SCALE_FACTOR)
+            );
+            xml_entity_trigger->SetAttribute("enabled", "true");
+            element->LinkEndChild(xml_entity_trigger.release());
+        }
+
+        // Get entities.
         for (const auto& it : decompiled.entities){
+            // If the entity has been added as a line, skip.
+            if (line_entities.size() > 0){
+                if (*std::find(line_entities.begin(), line_entities.end(), it.first) == it.first) {
+                    continue;
+                }
+            }
+
             const int char_id = it.second;
             if (char_id != -1){
                 const QGears::ModelListFile::ModelDescription& desc
@@ -804,7 +838,6 @@ static void FF7PcFieldToQGearsField(
                 element->LinkEndChild(xml_entity_script.release());
             }
             else{
-                // TODO: Lines go here.
                 std::unique_ptr<TiXmlElement> xml_entity_script(new TiXmlElement("entity_script"));
                 xml_entity_script->SetAttribute("name", it.first);
                 element->LinkEndChild(xml_entity_script.release());
@@ -1223,7 +1256,9 @@ void FF7DataInstaller::CollectionFieldSpawnAndScaleFactors(){
     if (iterator_counter_ < flevel_file_list_->size()){
         auto resource_name = (*flevel_file_list_)[iterator_counter_];
         // Exclude things that are not fields.
-        if (IsAFieldFile(resource_name) /*&& IsTestField(resourceName)*/){
+        //if (IsAFieldFile(resource_name) /*&& IsTestField(resource_name)*/){
+        // TODO: DEBUG: Only testing fields.
+        if (IsAFieldFile(resource_name) && IsTestField(resource_name)){
             conversion_step_ ++;
             if (conversion_step_ == 1){
                 field_ = QGears::LZSFLevelFileManager::GetSingleton().load(
@@ -1262,11 +1297,11 @@ void FF7DataInstaller::ConvertFieldsIteration(){
         auto resource_name = (*flevel_file_list_)[iterator_counter_];
         // Exclude things that are not fields.
         if (IsAFieldFile(resource_name)){
-            if (/*IsTestField(resource_name) &&*/ !WillCrash(resource_name)){
+            //if (/*IsTestField(resource_name) &&*/ !WillCrash(resource_name)){
             // TODO: DEBUG: Only test fields
-            //if (IsTestField(resource_name) && !WillCrash(resource_name)){
+            if (IsTestField(resource_name) && !WillCrash(resource_name)){
                 //write_output_line("Converting field " + resource_name);
-                //std::cout << " - Converting field: " << resource_name << std::endl;
+                std::cout << " - Converting field: " << resource_name << std::endl;
                 CreateDir(FieldMapDir() + "/" + resource_name);
                 QGears::FLevelFilePtr field
                   = QGears::LZSFLevelFileManager::GetSingleton().load(

+ 1 - 1
lib/SUDM/decompiler/control_flow.cpp

@@ -34,7 +34,7 @@
 #define GET(vertex) (boost::get(boost::vertex_name, _g, vertex))
 #define GET_EDGE(edge) (boost::get(boost::edge_attribute, _g, edge))
 
-ControlFlow::ControlFlow(InstVec& insts, Engine& engine) 
+ControlFlow::ControlFlow(InstVec& insts, Engine& engine)
   : mInsts(insts), 
     mEngine(engine)
 {

+ 2 - 1
lib/SUDM/decompiler/decompiler_engine.h

@@ -59,7 +59,8 @@ public:
 	 * @param endIt Index of the instruction immediately after the function, similar to end() on STL containers.
 	 */
 //	Function(InstIterator startIt, InstIterator endIt) : _startIt(startIt), _endIt(endIt) {}
-    Function(uint32 startAddr, uint32 endAddr) : mStartAddr(startAddr), mEndAddr(endAddr) {}
+    Function(uint32 startAddr, uint32 endAddr) : mStartAddr(startAddr), mEndAddr(endAddr){
+    }
 };
 
 /**

+ 138 - 141
lib/SUDM/decompiler/ff7_field/ff7_field_disassembler.cpp

@@ -1,3 +1,4 @@
+#include <vector>
 #include "ff7_field_disassembler.h"
 #include "ff7_field_engine.h"
 #include "decompiler_engine.h"
@@ -176,82 +177,71 @@ static int FindId(uint32 startAddr, uint32 endAddr, const InstVec& insts)
 }
 
 void FF7::FF7Disassembler::AddFunc(
-  std::string entityName, size_t entityIndex, size_t scriptIndex,
-  uint32 nextScriptEntryPoint, const bool isStart, bool isEnd,
-  bool toReturnOnly, std::string funcName
+  std::string entity_name, size_t entity_index, size_t script_index, uint32 next_script_entry_point,
+  const bool is_start, bool is_end, bool to_return_only, std::string func_name
 ){
 
-    const auto kScriptEntryPoint = mStream->Position();
-
-    // Read each block of opcodes up to a return
-    const size_t oldNumInstructions = _insts.size();
-
-    auto func = StartFunction(scriptIndex);
-    if (toReturnOnly)
-    {
-        // Read opcodes to the end or bail at the first return
-        ReadOpCodesToPositionOrReturn(nextScriptEntryPoint + kSectionPointersSize);
-        auto streamPos = mStream->Position();
-        const size_t endPos = nextScriptEntryPoint + kSectionPointersSize;
-        if (streamPos != endPos)
-        {
-            // Can't be the end if there is more data
-            isEnd = false;
-        }
+    bool is_line = false;
+    std::vector<float> point_a = {0, 0, 0};
+    std::vector<float> point_b = {0, 0, 0};
+    const auto SCRIPT_ENTRY_POINT = mStream->Position();
+
+    // Read each block of opcodes up to a return.
+    const size_t old_num_instructions = _insts.size();
+    auto func = StartFunction(script_index);
+    if (to_return_only){
+        // Read opcodes to the end or bail at the first return.
+        is_line = ReadOpCodesToPositionOrReturn(
+          next_script_entry_point + kSectionPointersSize, point_a, point_b
+        );
+        auto stream_pos = mStream->Position();
+        const size_t endPos = next_script_entry_point + kSectionPointersSize;
+        // Can't be the end if there is more data:
+        if (stream_pos != endPos) is_end = false;
     }
-    else
-    {
-        while (mStream->Position() != nextScriptEntryPoint + kSectionPointersSize)
-        {
-            // Keep going till we have all of the script, i.e if we bail at a return then call
-            // again till we have everything
-            ReadOpCodesToPositionOrReturn(nextScriptEntryPoint + kSectionPointersSize);
+    else{
+        // Keep going till we have all of the script, i.e if we bail at a
+        // return then call again till we have everything
+        while (mStream->Position() != next_script_entry_point + kSectionPointersSize){
+            is_line = ReadOpCodesToPositionOrReturn(
+              next_script_entry_point + kSectionPointersSize, point_a, point_b
+            );
         }
     }
 
-    std::string metaData;
-    if (isStart && isEnd)
-    {
-        metaData = "start_end_";
-    }
-    else if (isStart)
-    {
-        metaData = "start_";
-    }
-    else if (isEnd)
-    {
-        metaData = "end_";
-    }
-
+    // Read metadata, mark start and/or end.
+    std::string meta_data;
+    if (is_start && is_end) meta_data = "start_end_";
+    else if (is_start) meta_data = "start_";
+    else if (is_end) meta_data = "end_";
 
-    const size_t newNumInstructions = _insts.size();
-    func->mNumInstructions = newNumInstructions - oldNumInstructions;
+    const size_t new_num_instructions = _insts.size();
+    func->mNumInstructions = new_num_instructions - old_num_instructions;
     func->mEndAddr = _insts.back()->_address;
-    if (!funcName.empty())
-    {
-        func->_name = funcName;
-    }
-
+    if (!func_name.empty()) func->_name = func_name;
     int id = FindId(func->mStartAddr, func->mEndAddr, _insts);
-    // If there is no ID check if there was an ID for this entity in any of its other functions and use that instead
-    if (id == -1)
-    {
-        for (auto& func : mEngine->_functions)
-        {
-            FunctionMetaData metaData(func.second._metadata);
-            if (metaData.EntityName() == entityName && metaData.CharacterId() != -1)
-            {
-                id = metaData.CharacterId();
+
+    // If there is no ID check if there was an ID for this entity in any of
+    // its other functions and use that instead.
+    if (id == -1){
+        for (auto& func : mEngine->_functions){
+            FunctionMetaData func_meta_data(func.second._metadata);
+            if (func_meta_data.EntityName() == entity_name && func_meta_data.CharacterId() != -1){
+                id = func_meta_data.CharacterId();
                 break;
             }
         }
     }
 
-    metaData += std::to_string(id) + "_" + entityName;
-    func->_metadata = metaData;
+    meta_data += std::to_string(id) + "_" + entity_name;
+    func->_metadata = meta_data;
+
+    mEngine->_functions[SCRIPT_ENTRY_POINT] = *func;
+    mEngine->AddEntityFunction(entity_name, entity_index, func->_name, script_index);
+
+    // If the entity is a line, mark it as so.
+    if (is_line) mEngine->MarkEntityAsLine(entity_index, true, point_a, point_b);
 
-    mEngine->_functions[kScriptEntryPoint] = *func;
-    mEngine->AddEntityFunction(entityName, entityIndex, func->_name, scriptIndex);
 
 }
 
@@ -290,9 +280,6 @@ void FF7::FF7Disassembler::DisassembleIndivdualScript(std::string entityName,
         // Read the init script, which means stop at the first return
         AddFunc(entityName, entityIndex, scriptIndex, nextScriptEntryPoint, isStart, isEnd, true, "on_start");
 
-        // TODO: Delete the last return in the "init" function before
-        // concatenating the main function starting with.
-
         // Not at the end of this script? Then the remaining data is the "main" script
         auto streamPos = mStream->Position();
         if (streamPos != endPos)
@@ -334,54 +321,17 @@ std::map<std::string, const FF7::TInstructRecord*> FF7::FieldInstructions()
     return mnemonicToInstructionRecords;
 }
 
-void FF7::FF7Disassembler::ReadOpCodesToPositionOrReturn(size_t endPos)
-{
-    /* Need all opcodes in the array before this will work
-    // Convert the array to a map that we can query on by opcode
-    std::map<unsigned int, const TInstructRecord*> opcodeToInstructionRecords;
-    for (size_t i = 0; i < boost::size(kOpcodes); i++)
-    {
-        opcodeToInstructionRecords[kOpcodes[i].mOpCode] = &kOpcodes[i];
-    }
-
-    while (mStream->Position() < endPos)
-    {
-        // See if the next data is a 1 byte opcode
-        uint16 opcode = mStream->ReadU8();
-        auto it = opcodeToInstructionRecords.find(opcode);
-        _address++;
-        if (it == std::end(opcodeToInstructionRecords))
-        {
-            // No, is it a 2 byte opcode?
-            opcode = (mStream->ReadU8() << 8) + opcode;
-            _address++;
-            it = opcodeToInstructionRecords.find(opcode);
-            if (it == std::end(opcodeToInstructionRecords))
-            {
-                // There are no instructions bigger than 2 bytes, so fail
-                throw UnknownSubOpcodeException(_address, opcode);
-            }
-        }
-
-        InstPtr inst = it->second->mFactoryFunc();
-        inst->_opcode = opcode;
-        inst->_address = _address;
-        inst->_stackChange = 0;
-        inst->_name = it->second->mMnemonic;
-        readParams(inst, it->second->mArgumentFormat);
-        _insts.push_back(inst);
-    }
-    */
+bool FF7::FF7Disassembler::ReadOpCodesToPositionOrReturn(
+  size_t end_pos, std::vector<float>& point_a, std::vector<float>& point_b
+){
 
+    bool is_line = false;
     std::vector<unsigned int> exitAddrs;
-
-    while (mStream->Position() < endPos)
-    {
+    while (mStream->Position() < end_pos){
         uint8 opcode = mStream->ReadU8();
         uint32 full_opcode = 0;
         std::string opcodePrefix;
-        switch (opcode)
-        {
+        switch (opcode){
             // Flow
             OPCODE(eOpcodes::IFUB, "IFUB", FF7CondJumpInstruction, 0, "NBBBB");
             OPCODE(eOpcodes::RET, "RET", FF7ControlFlowInstruction, 0, "");
@@ -549,21 +499,60 @@ void FF7::FF7Disassembler::ReadOpCodesToPositionOrReturn(size_t endPos)
                 opcode = this->mStream->ReadU8();
                 switch (opcode)
                 {
-                    OPCODE(eKawaiOpcodes::EYETX, "EYETX", FF7ModelInstruction, 0, parameters.c_str()); // was BBBB
-                    OPCODE(eKawaiOpcodes::TRNSP, "TRNSP", FF7ModelInstruction, 0, parameters.c_str()); // was B
-                    OPCODE(eKawaiOpcodes::AMBNT, "AMBNT", FF7ModelInstruction, 0, parameters.c_str()); // was BBBBBBB
-                    OPCODE(eKawaiOpcodes::Unknown03, "Unknown03", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown04, "Unknown04", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown05, "Unknown05", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::LIGHT, "LIGHT", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown07, "Unknown07", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown08, "Unknown08", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown09, "Unknown09", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::SBOBJ, "SBOBJ", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown0B, "Unknown0B", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::Unknown0C, "Unknown0C", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::SHINE, "SHINE", FF7ModelInstruction, 0, parameters.c_str());
-                    OPCODE(eKawaiOpcodes::RESET, "RESET", FF7ModelInstruction, 0, parameters.c_str());
+                    OPCODE(
+                      eKawaiOpcodes::EYETX, "EYETX", FF7ModelInstruction, 0, parameters.c_str()
+                    ); // was BBBB
+                    OPCODE(
+                      eKawaiOpcodes::TRNSP, "TRNSP", FF7ModelInstruction, 0, parameters.c_str()
+                    ); // was B
+                    OPCODE(
+                      eKawaiOpcodes::AMBNT, "AMBNT", FF7ModelInstruction, 0, parameters.c_str()
+                    ); // was BBBBBBB
+                    OPCODE(
+                      eKawaiOpcodes::Unknown03, "Unknown03",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown04, "Unknown04",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown05, "Unknown05",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::LIGHT, "LIGHT",FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown07, "Unknown07",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown08, "Unknown08",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown09, "Unknown09",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::SBOBJ, "SBOBJ",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown0B, "Unknown0B",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::Unknown0C, "Unknown0C",
+                      FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::SHINE, "SHINE", FF7ModelInstruction, 0, parameters.c_str()
+                    );
+                    OPCODE(
+                      eKawaiOpcodes::RESET, "RESET", FF7ModelInstruction, 0, parameters.c_str()
+                    );
                 default:
                     throw UnknownSubOpcodeException(this->_address, opcode);
                 }
@@ -628,9 +617,27 @@ void FF7::FF7Disassembler::ReadOpCodesToPositionOrReturn(size_t endPos)
             OPCODE(eOpcodes::SLIP, "SLIP", FF7WalkmeshInstruction, 0, "B");
             OPCODE(eOpcodes::UC, "UC", FF7WalkmeshInstruction, 0, "B");
             OPCODE(eOpcodes::IDLCK, "IDLCK", FF7WalkmeshInstruction, 0, "wB");
-            OPCODE(eOpcodes::LINE, "LINE", FF7WalkmeshInstruction, 0, "ssssss");
             OPCODE(eOpcodes::LINON, "LINON", FF7WalkmeshInstruction, 0, "B");
             OPCODE(eOpcodes::SLINE, "SLINE", FF7WalkmeshInstruction, 0, "NNNssssss");
+            //OPCODE(eOpcodes::LINE, "LINE", FF7WalkmeshInstruction, 0, "ssssss");
+            // LINE is done like this to save opcode params to the function out params.
+            case eOpcodes::LINE:
+                is_line = true;
+                full_opcode = (full_opcode << 8) + eOpcodes::LINE;
+                this->_insts.push_back(new FF7WalkmeshInstruction());
+                (this->_insts.back())->_opcode = full_opcode;
+                (this->_insts.back())->_address = this->_address;
+                (this->_insts.back())->_stackChange = 0;
+                (this->_insts.back())->_name = opcodePrefix + std::string("LINE");
+                (this->_insts.back())->_codeGenData = "";
+                this->readParams((this->_insts.back()), "ssssss");
+                point_a[0] = this->_insts.back()->_params[0]->getSigned();
+                point_a[1] = this->_insts.back()->_params[1]->getSigned();
+                point_a[2] = this->_insts.back()->_params[2]->getSigned();
+                point_b[0] = this->_insts.back()->_params[3]->getSigned();
+                point_b[1] = this->_insts.back()->_params[4]->getSigned();
+                point_b[2] = this->_insts.back()->_params[5]->getSigned();
+                break;
 
             // Backgnd
             OPCODE(eOpcodes::BGPDH, "BGPDH", FF7BackgroundInstruction, 0, "NBs");
@@ -694,29 +701,19 @@ void FF7::FF7Disassembler::ReadOpCodesToPositionOrReturn(size_t endPos)
             OPCODE(eOpcodes::GETX, "GETX", FF7UncategorizedInstruction, 0, "BBBBBB");
             OPCODE(eOpcodes::SEARCHX, "SEARCHX", FF7UncategorizedInstruction, 0, "BBBBBBBBBB");
 
-        default:
-            throw UnknownOpcodeException(this->_address, opcode);
+            default:
+                throw UnknownOpcodeException(this->_address, opcode);
         }
         INC_ADDR;
 
-        // Are we within an "if" statement tracking
+        // Is it within an "if" statement tracking?
         InstPtr i = this->_insts.back();
-        if (i->isCondJump())
-        {
-            exitAddrs.push_back(i->getDestAddress());
-        }
+        if (i->isCondJump()) exitAddrs.push_back(i->getDestAddress());
         if (!exitAddrs.empty())
-        {
-            if (i->_address == exitAddrs.back())
-            {
-                exitAddrs.pop_back();
-            }
-        }
+            if (i->_address == exitAddrs.back()) exitAddrs.pop_back();
 
-        // Only bail if its the first RET that isn't within an "if" block
-        if (full_opcode == eOpcodes::RET && exitAddrs.empty())
-        {
-            return;
-        }
+        // Only bail if its the first RET that isn't within an "if" block.
+        if (full_opcode == eOpcodes::RET && exitAddrs.empty()) return is_line;
     }
+    return is_line;
 }

+ 35 - 6
lib/SUDM/decompiler/ff7_field/ff7_field_disassembler.h

@@ -318,22 +318,51 @@ namespace FF7
     private:
         void DisassembleIndivdualScript(std::string entityName,
             size_t entityIndex,
-            size_t scriptIndex,
             size_t scriptEntryPoint,
+            size_t scriptIndex,
             uint32 nextScriptEntryPoint,
             bool isStart,
             bool isEnd);
 
         /**
-         * Adds a function to
+         * Adds a function to the engine.
+         *
+         * @param entity_name[in] Name of the entity that owns the function.
+         * @param entity_index[in] Index of the entity that owns the function.
+         * @param script_index[in] Index of the script within the entity.
+         * @param next_script_entry_point[in] Start position of the next
+         * script (or the ending position of this one, plus one).
+         * @param is_start[in] @todo Understand and document.
+         * @param is_end[in] @todo Understand and document.
+         * @param to_return_only[in] True to read the script only until the
+         * first return.
+         * @param func_name[in] Name of the function.
          */
         void AddFunc(
-          std::string entityName, size_t entityIndex, size_t scriptIndex,
-          uint32 nextScriptEntryPoint, const bool isStart, const bool isEnd,
-          bool toReturnOnly, std::string funcName
+          std::string entity_name, size_t entity_index, size_t script_index,
+          uint32 next_script_entry_point, const bool is_start, const bool is_end,
+          bool to_return_only, std::string func_name
+        );
+
+        /**
+         * Reads opcodes from a script, and detects lines.
+         *
+         * It stops when it reaches the ending position or a RET opcode,
+         * whatever comes first. It also detects if one of the read opcodes
+         * LINE, which means that the entity is a line
+         *
+         * @param end_pos[in] The last position to read. Opcodes will be read
+         * until this, or until a RET is found.
+         * @param point_a[out] If a LINE opcode is found, the first point will
+         * be saved here.
+         * @param point_b[out] If a LINE opcode is found, the second point
+         * will be saved here.
+         * @return True if LINE opcode found, false if not.
+         */
+        bool ReadOpCodesToPositionOrReturn(
+          size_t end_pos, std::vector<float>& point_a, std::vector<float>& point_b
         );
 
-        void ReadOpCodesToPositionOrReturn(size_t endPos);
         std::unique_ptr<Function> StartFunction(size_t scriptIndex);
 
         FF7FieldEngine* mEngine;

+ 95 - 4
lib/SUDM/decompiler/ff7_field/ff7_field_engine.cpp

@@ -93,14 +93,32 @@ std::map<std::string, int> FF7::FF7FieldEngine::GetEntities() const
                 it->second = meta.CharacterId();
             }
         }
-        else
-        {
+        else{
+            // TODO: Don't add lines.
             r[meta.EntityName()] = meta.CharacterId();
         }
     }
     return r;
 }
 
+std::vector<SUDM::FF7::Field::Line> FF7::FF7FieldEngine::GetLineList() const{
+    std::vector<SUDM::FF7::Field::Line> lines;
+    for (auto entity: mEntityIndexMap){
+        if (entity.second.is_line_ == true){
+            SUDM::FF7::Field::Line line;
+            line.name = entity.second.Name();
+            line.ax = entity.second.ax;
+            line.ay = entity.second.ay;
+            line.az = entity.second.az;
+            line.bx = entity.second.bx;
+            line.by = entity.second.by;
+            line.bz = entity.second.bz;
+            lines.push_back(line);
+        }
+    }
+    return lines;
+}
+
 void FF7::FF7FieldEngine::AddEntityFunction(const std::string& entityName, size_t entityIndex, const std::string& funcName, size_t funcIndex)
 {
     auto it = mEntityIndexMap.find(entityIndex);
@@ -116,6 +134,22 @@ void FF7::FF7FieldEngine::AddEntityFunction(const std::string& entityName, size_
     }
 }
 
+void FF7::FF7FieldEngine::MarkEntityAsLine(
+  size_t entity_index, bool line,
+  std::vector<float> point_a, std::vector<float> point_b
+){
+    auto it = mEntityIndexMap.find(entity_index);
+    if (it != std::end(mEntityIndexMap)){
+        (*it).second.is_line_ = line;
+        (*it).second.ax = point_a[0];
+        (*it).second.ay = point_a[1];
+        (*it).second.az = point_a[2];
+        (*it).second.bx = point_b[0];
+        (*it).second.by = point_b[1];
+        (*it).second.bz = point_b[2];
+    }
+}
+
 void FF7::FF7FieldEngine::RemoveExtraneousReturnStatements(InstVec& insts, Graph g)
 {
     for (auto& f : _functions)
@@ -1752,7 +1786,6 @@ void FF7::FF7ModelInstruction::processSOLID(CodeGenerator* codeGen, const std::s
 }
 
 void FF7::FF7ModelInstruction::processOFST(CodeGenerator* codeGen, const std::string& entity){
-    // IVV TODO
     FF7SimpleCodeGenerator* cg = static_cast<FF7SimpleCodeGenerator*>(codeGen);
     float x = atoi(
       FF7CodeGeneratorHelpers::FormatValueOrVariable(
@@ -1815,7 +1848,8 @@ void FF7::FF7WalkmeshInstruction::processInst(Function& func, ValueStack&, Engin
         break;
 
     case eOpcodes::LINE:
-        WriteTodo(codeGen, md.EntityName(), "LINE");
+        // The entity is a line!
+        processLINE(codeGen, md.EntityName());
         break;
 
     case eOpcodes::LINON:
@@ -1836,6 +1870,63 @@ void FF7::FF7WalkmeshInstruction::processUC(CodeGenerator* codeGen)
     codeGen->addOutputLine((boost::format("entity_manager:player_lock(%1%)") % FF7CodeGeneratorHelpers::FormatBool(_params[0]->getUnsigned())).str());
 }
 
+void FF7::FF7WalkmeshInstruction::processLINE(CodeGenerator* codeGen, const std::string& entity){
+    float xa = _params[0]->getSigned();
+    float ya = _params[1]->getSigned();
+    float za = _params[2]->getSigned();
+    float xb = _params[3]->getSigned();
+    float yb = _params[4]->getSigned();
+    float zb = _params[5]->getSigned();
+    // Scale down. TODO: Why this number?
+    xa *= 0.00781249709639;
+    ya *= 0.00781249709639;
+    za *= 0.00781249709639;
+    xb *= 0.00781249709639;
+    yb *= 0.00781249709639;
+    zb *= 0.00781249709639;
+    // TODO: Don't translate the opcode, but add gateway functions.
+    codeGen->addOutputLine(
+      "-- LINE (" + std::to_string(xa) + ", " + std::to_string(ya) + ", " + std::to_string(za)
+      + ")-(" + std::to_string(xb) + ", " +std::to_string(yb) + ", " + std::to_string(zb) + ")"
+    );
+
+    // HACK:
+    //   on_enter_line executes on_update
+    //   on_move_to_line executes on_interact
+    //   on_cross_line executes script_4
+    //   on_leave_line executes script_5
+    // Indentation will be all wrong
+    codeGen->addOutputLine("do return 0 end");
+    codeGen->addOutputLine("end,");
+    codeGen->addOutputLine("");
+    codeGen->addOutputLine("on_enter_line = function(self, entity)");
+    codeGen->addOutputLine(
+      "    script:request(Script.ENTITY, \"" + entity + "\", \"on_update\", 6)"
+    );
+    codeGen->addOutputLine("    do return 0 end");
+    codeGen->addOutputLine("end,");
+    codeGen->addOutputLine("on_move_to_line = function(self, entity)");
+    codeGen->addOutputLine(
+      "    script:request(Script.ENTITY, \"" + entity + "\", \"on_interact\", 6)"
+    );
+    codeGen->addOutputLine("    do return 0 end");
+    codeGen->addOutputLine("end,");
+    codeGen->addOutputLine("on_cross_line = function(self, entity)");
+    codeGen->addOutputLine(
+      "    script:request(Script.ENTITY, \"" + entity + "\", \"script_4\", 6)"
+    );
+    codeGen->addOutputLine("    do return 0 end");
+    codeGen->addOutputLine("end,");
+    codeGen->addOutputLine("on_leave_line = function(self, entity)");
+    codeGen->addOutputLine(
+      "    script:request(Script.ENTITY, \"" + entity + "\", \"script_5\", 6)"
+    );
+    //codeGen->addOutputLine("    do return 0 end");
+    //codeGen->addOutputLine("end,");
+
+
+}
+
 void FF7::FF7BackgroundInstruction::processInst(Function& func, ValueStack&, Engine* /*engine*/, CodeGenerator *codeGen)
 {
     //FF7::FF7FieldEngine& eng = static_cast<FF7::FF7FieldEngine&>(*engine);

+ 93 - 1
lib/SUDM/decompiler/ff7_field/ff7_field_engine.h

@@ -79,7 +79,7 @@ namespace FF7{
                      *
                      * @param name[in] Entity name.
                      */
-                    Entity(const std::string& name): mName(name){}
+                    Entity(const std::string& name): mName(name), is_line_(false){}
 
                     /**
                      * Retrieves the entity name.
@@ -122,6 +122,26 @@ namespace FF7{
                         mFunctions[index] = name;
                     }
 
+                    /**
+                     * Indicates if the entity is a line.
+                     * @TODO: Make private.
+                     */
+                    bool is_line_;
+
+                    /**
+                     * The first point of the line.
+                     * @TODO: Make private.
+                     */
+                    std::vector<float> point_a_;
+
+                    /**
+                     * The first point of the line.
+                     * @TODO: Make private.
+                     */
+                    std::vector<float> point_b_;
+
+                    float ax, ay, az, bx, by, bz;
+
                 private:
 
                     /**
@@ -196,6 +216,22 @@ namespace FF7{
              */
             std::map<std::string, int> GetEntities() const;
 
+            /**
+             * Retrieves all line entities in the map.
+             *
+             * @param A list of line entities.
+             */
+            std::vector<SUDM::FF7::Field::Line> GetLineList() const;
+
+            /**
+             * Retrieves all entities in the map.
+             *
+             * @return A map of entities, with the name and index.
+             */
+            std::map<size_t, Entity> GetEntityIndexMap() const{
+                return mEntityIndexMap;
+            }
+
             /**
              * Adds a function to an entity.
              *
@@ -209,6 +245,22 @@ namespace FF7{
               const std::string& funcName, size_t funcIndex
             );
 
+            /**
+             * Marks an entity as a line.
+             *
+             * @param entity_index Index of the entity.
+             * @param line[in] True to mark the entity as a line, false to
+             * unmark it.
+             * @param point_a[in] First point of the line. Can be null if line
+             * is false.
+             * @param point_b[in] Second point of the line. Can be null if
+             * line is false.
+             */
+            void MarkEntityAsLine(
+                size_t entity_index, bool line,
+                std::vector<float> point_a, std::vector<float> point_b
+            );
+
             /**
              * Retrieves an entity.
              *
@@ -827,6 +879,9 @@ namespace FF7{
          * offset can be set; the greater the number, the slower the object
          * moves to its target offset. Script execution may also be halted
          * until the gradual offset has been completed. For this, see OFSTW.
+         *
+         * @param codeGen The code generator.
+         * @param entity[in] The entity name.
          */
         void processOFST(CodeGenerator* codegen, const std::string& entity);
     };
@@ -835,8 +890,45 @@ namespace FF7{
     {
     public:
         virtual void processInst(Function& func, ValueStack &stack, Engine *engine, CodeGenerator *codeGen) override;
+
     private:
         void processUC(CodeGenerator* codeGen);
+
+        /**
+         * Processes a LINE opcode.
+         *
+         * Opcode: 0xD0
+         * Short name: LINE
+         * Long name: Line definition
+         *
+         * Memory layout (7 bytes)
+         * |0xD0|XA|YA|ZA|XB|YB|ZB|
+         *
+         * Arguments:
+         * - const Short XA: X-coordinate of the first point of the line.
+         * - const Short YA: Y-coordinate of the first point of the line.
+         * - const Short ZA: Z-coordinate of the first point of the line.
+         * - const Short XB: X-coordinate of the second point of the line.
+         * - const Short YB: Y-coordinate of the second point of the line.
+         * - const Short ZB: Z-coordinate of the second point of the line.
+         *
+         * Defines a line on the walkmesh that, when crossed by a playable
+         * character, causes one of the entity's scripts to be executed. These
+         * are similar to the triggers in Section 8. All the lines in the
+         * current field can be turned on or off by using the LINON opcode.
+         *
+         * There are generally 6 scripts (other than the init and main) if the entity is a LINE.
+         * - script index 2 -> S1 - [OK].
+         * - script index 3 -> S2 - Move.
+         * - script index 4 -> S3 - Move.
+         * - script index 5 -> S4 - Go.
+         * - script index 6 -> S5 - Go 1x.
+         * - script index 7 -> S6 - Go away.
+         *
+         * @param codeGen The code generator.
+         * @param entity[in] The entity name.
+         */
+        void processLINE(CodeGenerator* codeGen, const std::string& entity);
     };
 
     class FF7BackgroundInstruction : public KernelCallInstruction

+ 17 - 0
lib/SUDM/decompiler/sudm.cpp

@@ -50,6 +50,18 @@ namespace SUDM
 
                 //disassembler->dumpDisassembly(std::cout);
 
+                // Check if the script contains the opcode LINE.
+                /*bool is_line = false;
+                for(InstPtr inst : insts){
+                    if ("LINE" == inst->_name){
+                        std::cout << " INSTRUCTION: " << inst->_name << " (" << scriptName << ")" << std::endl;
+                        disassembler->dumpDisassembly(std::cout);
+                        std::cout << " INSTRUCTION END" << std::endl;
+                        is_line = true;
+                        break;
+                    }
+                }*/
+
                 // Create CFG
                 auto controlFlow = std::make_unique<ControlFlow>(insts, engine);
                 controlFlow->createGroups();
@@ -67,6 +79,11 @@ namespace SUDM
                 cg->generate(insts, graph);
                 ds.luaScript = textToPrepend + output.str() + textToAppend;
                 ds.entities = engine.GetEntities();
+                for (auto line : engine.GetLineList()){
+                    ds.lines.push_back(line);
+                }
+
+                ds.lines = engine.GetLineList();
                 return ds;
             }
         }

+ 11 - 1
lib/SUDM/decompiler/sudm.h

@@ -50,10 +50,20 @@ namespace SUDM
             // Entities list, with entity type of
             // entity_script
             // entity_model, which somehow links to model loader
+
+            struct Line{
+                std::string name;
+                std::vector<float> point_a;
+                float ax, ay, az;
+                float bx, by, bz;
+            };
+
             struct DecompiledScript
             {
                 std::string luaScript;
                 std::map<std::string, int> entities;
+                //std::map<size_t, FF7FieldEngine::Entity> entity_map;
+                std::vector<Line> lines;
             };
 
             float ScaleFactor(const std::vector<unsigned char>& scriptBytes);
@@ -74,4 +84,4 @@ namespace SUDM
                 std::string textToPrepend = "");
         }
     }
-}
+}