/*
* V-Gears
* Copyright (C) 2022 V-Gears Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include "decompiler/field/FieldEngine.h"
#include "decompiler/field/FieldCodeGenerator.h"
#include "decompiler/field/FieldDisassembler.h"
#include "decompiler/field/instruction/FieldNoOperationInstruction.h"
/**
* @todo Understand and document
*/
#define GET(vertex) (boost::get(boost::vertex_name, graph, vertex))
/*
* TODO: OpCodes which need implementing.
*
* BLINK
* XYI
* CMOVE
* MOVA
* TURA
* ANIMW
* FMOVE
* ANIME2
* ANIM_1
* CANIM1 ?
* CANM_1
* TURN
* DIRA
* GETDIR
* GETAXY
* TALKR
* ANIMB
* TURNW
*/
FieldEngine::Entity::Entity(const std::string& name, size_t index):
name_(name), index_(index), is_line_(false){}
std::string FieldEngine::Entity::GetName() const{return name_;}
size_t FieldEngine::Entity::GetIndex() const{return index_;}
std::string FieldEngine::Entity::FunctionByIndex(size_t index) const{
auto it = functions_.find(index);
if (it == std::end(functions_)) throw DecompilerException();
return it->second;
}
void FieldEngine::Entity::AddFunction(const std::string& name, size_t index){
functions_[index] = name;
}
void FieldEngine::Entity::MarkAsLine(
bool line, std::vector point_a, std::vector point_b
){
is_line_ = line;
point_a_.clear();
point_b_.clear();
if (line){
if (point_a.size() >= 3 && point_b.size() >= 3){
point_a_.push_back(point_a[0]);
point_a_.push_back(point_a[1]);
point_a_.push_back(point_a[2]);
point_b_.push_back(point_b[0]);
point_b_.push_back(point_b[1]);
point_b_.push_back(point_b[2]);
}
// TODO: Notify on else.
}
// TODO: These are not getting to the final script.
// Maybe this can be removed?
AddFunction("on_approach", 1);
AddFunction("on_cross", 2);
AddFunction("on_near", 3);
AddFunction("on_leave", 4);
}
bool FieldEngine::Entity::IsLine(){return is_line_;}
std::vector FieldEngine::Entity::GetLinePointA(){return point_a_;}
std::vector FieldEngine::Entity::GetLinePointB(){return point_b_;}
FieldEngine::FieldEngine(FieldScriptFormatter& formatter, std::string script_name) :
formatter_(formatter), script_name_(script_name), scale_factor_(1.0f)
{SetOutputStackEffect(false);}
std::unique_ptr FieldEngine::GetDisassembler(
InstVec &insts, const std::vector& raw_script_data
){
auto ret = std::make_unique(formatter_, this, insts, raw_script_data);
scale_factor_ = ret->GetScaleFactor();
return std::move(ret);
}
std::unique_ptr FieldEngine::GetDisassembler(InstVec &insts){
auto ret = std::make_unique(formatter_, this, insts);
scale_factor_ = ret->GetScaleFactor();
return std::move(ret);
}
std::unique_ptr FieldEngine::GetCodeGenerator(
const InstVec& insts, std::ostream &output
){
// The broken version:
//return std::make_unique(this, insts, output);
// The not-as-nice-but-at-least-it-works version:
return std::make_unique(this, insts, output, formatter_);
}
void FieldEngine::PostCFG(InstVec& insts, Graph graph){
// In FF7 some scripts ends with an infinite loop to "keep it alive"
// in V-Gears this isn't required so they can be removed.
//RemoveTrailingInfiniteLoops(insts, graph);
// This could generate bad code, but it always seems to follow that pattern that if the last
// instruction is an uncond jump back into the script then it simply nests all of those blocks
// in an infinite loop.
//MarkInfiniteLoopGroups(insts, graph);
// Scripts end with a "return". This isn't required so strip them out.
//RemoveExtraneousReturnStatements(insts, graph);
}
bool FieldEngine::UsePureGrouping() const{return false;}
std::map FieldEngine::GetEntities() const{
std::map r;
for (auto& f : functions){
const Function& func = f.second;
FunctionMetaData meta(func.metadata);
auto it = r.find(meta.GetEntityName());
if (it != std::end(r)){
// Try to find a function in this entity has that has a char id.
// don't overwrite a valid char id with a "blank" one.
if (it->second == -1) it->second = meta.GetCharacterId();
}
// TODO: Don't add line entities here:
else r[meta.GetEntityName()] = meta.GetCharacterId();
}
return r;
}
std::vector FieldEngine::GetEntityList() const{
std::vector entities;
for (auto entity: entity_index_map_){
if (entity.second.IsLine() == false){
FieldDecompiler::FieldEntity ent;
ent.name = entity.second.GetName();
ent.index = entity.second.GetIndex();
// Get character ID.
ent.char_id = -1;
std::map r;
for (auto& f : functions){
const Function& func = f.second;
FunctionMetaData meta(func.metadata);
if (meta.GetEntityName() == ent.name){
ent.char_id = meta.GetCharacterId();
break;
}
}
entities.push_back(ent);
}
}
return entities;
}
std::vector FieldEngine::GetLineList() const{
std::vector lines;
for (auto entity: entity_index_map_){
if (entity.second.IsLine() == true){
FieldDecompiler::Line line;
line.name = entity.second.GetName();
line.point_a = entity.second.GetLinePointA();
line.point_b = entity.second.GetLinePointB();
lines.push_back(line);
}
}
return lines;
}
void FieldEngine::AddEntityFunction(
const std::string& entity_name, size_t entity_index,
const std::string& func_name, size_t func_index
){
auto it = entity_index_map_.find(entity_index);
if (it != std::end(entity_index_map_)) (*it).second.AddFunction(func_name, func_index);
else{
Entity e(entity_name, entity_index);
e.AddFunction(func_name, func_index);
entity_index_map_.insert(std::make_pair(entity_index, e));
}
}
void FieldEngine::MarkEntityAsLine(
size_t entity_index, bool line, std::vector point_a, std::vector point_b
){
auto it = entity_index_map_.find(entity_index);
if (it != std::end(entity_index_map_)){
(*it).second.MarkAsLine(line, point_a, point_b);
}
}
bool FieldEngine::EntityIsLine(size_t entity_index){
auto it = entity_index_map_.find(entity_index);
if (it != std::end(entity_index_map_)) return (*it).second.IsLine();
return false;
}
const FieldEngine::Entity& FieldEngine::EntityByIndex(size_t index) const{
auto it = entity_index_map_.find(index);
if (it == std::end(entity_index_map_)) throw DecompilerException();
return it->second;
}
float FieldEngine::GetScaleFactor() const {return scale_factor_;}
const std::string& FieldEngine::GetScriptName() const {return script_name_;}
void FieldEngine::RemoveExtraneousReturnStatements(InstVec& insts, Graph graph){
for (auto& f : functions){
Function& func = f.second;
for (auto it = insts.begin(); it != insts.end(); it ++){
// Is it the last instruction in the function, and is it a return statement?
if ((*it)->GetAddress() == func.end_addr){
if ((*it)->GetOpcode() == OPCODES::RET){
// Set new end address to be before the NOP.
func.end_addr = (*(it - 1))->GetAddress();
func.num_instructions --;
Instruction* nop = new FieldNoOperationInstruction();
nop->SetOpcode(OPCODES::NOP);
nop->SetAddress((*it)->GetAddress());
(*it).reset(nop);
break;
}
}
}
}
}
void FieldEngine::RemoveTrailingInfiniteLoops(InstVec& insts, Graph graph){
for (auto& f : functions){
Function& func = f.second;
for (auto it = insts.begin(); it != insts.end(); it ++){
// Is it the last instruction in the function, a jump, and a jumping to itself?
if ((*it)->GetAddress() == func.end_addr){
if ((*it)->IsJump() && (*it)->GetDestAddress() == (*it)->GetAddress()){
// Set new end address to be before the NOP
func.end_addr = (*(it - 1))->GetAddress();
func.num_instructions --;
Instruction* nop = new FieldNoOperationInstruction();
nop->SetOpcode(OPCODES::NOP);
nop->SetAddress((*it)->GetAddress());
(*it).reset(nop);
break;
}
}
}
}
}
void FieldEngine::MarkInfiniteLoopGroups(InstVec& insts, Graph graph){
for (auto& f : functions){
Function& func = f.second;
for (auto it = insts.begin(); it != insts.end(); it ++){
if ((*it)->GetAddress() == func.end_addr){
// Note: This is a "best effort heuristic", so quite a few loops will
// still end up as goto's. This could potentially generate invalid code too.
if ((*it)->IsUncondJump()){
// Then assume its an infinite do { } while(true)
// loop that wraps part of the script.
VertexRange vr = boost::vertices(graph);
for (VertexIterator v = vr.first; v != vr.second; ++ v){
GroupPtr gr = GET(*v);
if ((*gr->start)->GetAddress() == func.end_addr){
// Then assume its an infinite do { } while(true) loop
// that wraps part of the script.
gr->type = GROUP_TYPE_DO_WHILE;
}
}
}
break;
}
}
}
}