From f7ba1dbfc89196e3bf59c5a90d4f356bf49dfa35 Mon Sep 17 00:00:00 2001
From: scrawl <scrawl@baseoftrash.de>
Date: Tue, 28 Oct 2014 16:07:37 +0100
Subject: [PATCH] Add error handling for getPcRank and similar defines (Fixes
 #2071)

---
 apps/openmw/mwscript/interpretercontext.cpp |   9 +
 components/interpreter/defines.cpp          | 279 ++++++++++----------
 2 files changed, 155 insertions(+), 133 deletions(-)

diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp
index 52021839d..d8d13a921 100644
--- a/apps/openmw/mwscript/interpretercontext.cpp
+++ b/apps/openmw/mwscript/interpretercontext.cpp
@@ -311,6 +311,9 @@ namespace MWScript
 
     std::string InterpreterContext::getNPCRank() const
     {
+        if (getReferenceImp().getClass().getNpcStats(getReferenceImp()).getFactionRanks().empty())
+            throw std::runtime_error("getNPCRank(): NPC is not in a faction");
+
         const std::map<std::string, int>& ranks = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks();
         std::map<std::string, int>::const_iterator it = ranks.begin();
 
@@ -347,6 +350,9 @@ namespace MWScript
         MWBase::World *world = MWBase::Environment::get().getWorld();
         MWWorld::Ptr player = world->getPlayerPtr();
 
+        if (getReferenceImp().getClass().getNpcStats(getReferenceImp()).getFactionRanks().empty())
+            throw std::runtime_error("getPCRank(): NPC is not in a faction");
+
         std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first;
 
         const std::map<std::string, int>& ranks = player.getClass().getNpcStats (player).getFactionRanks();
@@ -374,6 +380,9 @@ namespace MWScript
         MWBase::World *world = MWBase::Environment::get().getWorld();
         MWWorld::Ptr player = world->getPlayerPtr();
 
+        if (getReferenceImp().getClass().getNpcStats(getReferenceImp()).getFactionRanks().empty())
+            throw std::runtime_error("getPCNextRank(): NPC is not in a faction");
+
         std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first;
 
         const std::map<std::string, int>& ranks = player.getClass().getNpcStats (player).getFactionRanks();
diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp
index 6f3b5bb5a..94916ee85 100644
--- a/components/interpreter/defines.cpp
+++ b/components/interpreter/defines.cpp
@@ -4,10 +4,12 @@
 #include <sstream>
 #include <string>
 #include <vector>
+#include <iostream>
 
 namespace Interpreter{
 
-    bool Check(const std::string& str, const std::string& escword, unsigned int* i, unsigned int* start){
+    bool check(const std::string& str, const std::string& escword, unsigned int* i, unsigned int* start)
+    {
         bool retval = str.find(escword) == 0;
         if(retval){
             (*i) += escword.length();
@@ -18,170 +20,181 @@ namespace Interpreter{
 
     std::vector<std::string> globals;
 
-    bool longerStr(const std::string& a, const std::string& b){
+    bool longerStr(const std::string& a, const std::string& b)
+    {
         return a.length() > b.length();
     }
 
-    std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){
+    std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context)
+    {
         unsigned int start = 0;
         std::ostringstream retval;
-        for(unsigned int i = 0; i < text.length(); i++){
-            if(text[i] == eschar){
+        for(unsigned int i = 0; i < text.length(); i++)
+        {
+            if(text[i] == eschar)
+            {
                 retval << text.substr(start, i - start);
                 std::string temp = text.substr(i+1, 100);
                 transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
                 
-                bool found;
-            
-                if(     (found = Check(temp, "actionslideright", &i, &start))){
-                    retval << context.getActionBinding("#{sRight}");
-                }
-                else if((found = Check(temp, "actionreadymagic", &i, &start))){
-                    retval << context.getActionBinding("#{sReady_Magic}");
-                }
-                else if((found = Check(temp, "actionprevweapon", &i, &start))){
-                    retval << "PLACEHOLDER_ACTION_PREV_WEAPON";
-                }
-                else if((found = Check(temp, "actionnextweapon", &i, &start))){
-                    retval << "PLACEHOLDER_ACTION_PREV_WEAPON";
-                }
-                else if((found = Check(temp, "actiontogglerun", &i, &start))){
-                    retval << context.getActionBinding("#{sAuto_Run}");
-                }
-                else if((found = Check(temp, "actionslideleft", &i, &start))){
-                    retval << context.getActionBinding("#{sLeft}");
-                }
-                else if((found = Check(temp, "actionreadyitem", &i, &start))){
-                    retval << context.getActionBinding("#{sReady_Weapon}");
-                }
-                else if((found = Check(temp, "actionprevspell", &i, &start))){
-                    retval << "PLACEHOLDER_ACTION_PREV_SPELL";
-                }
-                else if((found = Check(temp, "actionnextspell", &i, &start))){
-                    retval << "PLACEHOLDER_ACTION_NEXT_SPELL";
-                }
-                else if((found = Check(temp, "actionrestmenu", &i, &start))){
-                    retval << context.getActionBinding("#{sRestKey}");
-                }
-                else if((found = Check(temp, "actionmenumode", &i, &start))){
-                    retval << context.getActionBinding("#{sInventory}");
-                }
-                else if((found = Check(temp, "actionactivate", &i, &start))){
-                    retval << context.getActionBinding("#{sActivate}");
-                }
-                else if((found = Check(temp, "actionjournal", &i, &start))){
-                    retval << context.getActionBinding("#{sJournal}");
-                }
-                else if((found = Check(temp, "actionforward", &i, &start))){
-                    retval << context.getActionBinding("#{sForward}");
-                }
-                else if((found = Check(temp, "pccrimelevel", &i, &start))){
-                    retval << context.getPCBounty();
-                }
-                else if((found = Check(temp, "actioncrouch", &i, &start))){
-                    retval << context.getActionBinding("#{sCrouch_Sneak}");
-                }
-                else if((found = Check(temp, "actionjump", &i, &start))){
-                    retval << context.getActionBinding("#{sJump}");
-                }
-                else if((found = Check(temp, "actionback", &i, &start))){
-                    retval << context.getActionBinding("#{sBack}");
-                }
-                else if((found = Check(temp, "actionuse", &i, &start))){
-                    retval << context.getActionBinding("#{sUse}");
-                }
-                else if((found = Check(temp, "actionrun", &i, &start))){
-                    retval << context.getActionBinding("#{sRun}");
-                }
-                else if((found = Check(temp, "pcclass", &i, &start))){
-                    retval << context.getPCClass();
-                }
-                else if((found = Check(temp, "pcrace", &i, &start))){
-                    retval << context.getPCRace();
-                }
-                else if((found = Check(temp, "pcname", &i, &start))){
-                    retval << context.getPCName();
-                }
-                else if((found = Check(temp, "cell", &i, &start))){
-                    retval << context.getCurrentCellName();
-                }
-
-                else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox
-                    if(     (found = Check(temp, "faction", &i, &start))){
-                        retval << context.getNPCFaction();
+                bool found = false;
+                try
+                {
+                    if(     (found = check(temp, "actionslideright", &i, &start))){
+                        retval << context.getActionBinding("#{sRight}");
                     }
-                    else if((found = Check(temp, "nextpcrank", &i, &start))){
-                        retval << context.getPCNextRank();
+                    else if((found = check(temp, "actionreadymagic", &i, &start))){
+                        retval << context.getActionBinding("#{sReady_Magic}");
                     }
-                    else if((found = Check(temp, "pcnextrank", &i, &start))){
-                        retval << context.getPCNextRank();
+                    else if((found = check(temp, "actionprevweapon", &i, &start))){
+                        retval << "PLACEHOLDER_ACTION_PREV_WEAPON";
                     }
-                    else if((found = Check(temp, "pcrank", &i, &start))){
-                        retval << context.getPCRank();
+                    else if((found = check(temp, "actionnextweapon", &i, &start))){
+                        retval << "PLACEHOLDER_ACTION_PREV_WEAPON";
                     }
-                    else if((found = Check(temp, "rank", &i, &start))){
-                        retval << context.getNPCRank();
+                    else if((found = check(temp, "actiontogglerun", &i, &start))){
+                        retval << context.getActionBinding("#{sAuto_Run}");
                     }
-                    
-                    else if((found = Check(temp, "class", &i, &start))){
-                        retval << context.getNPCClass();
+                    else if((found = check(temp, "actionslideleft", &i, &start))){
+                        retval << context.getActionBinding("#{sLeft}");
                     }
-                    else if((found = Check(temp, "race", &i, &start))){
-                        retval << context.getNPCRace();
+                    else if((found = check(temp, "actionreadyitem", &i, &start))){
+                        retval << context.getActionBinding("#{sReady_Weapon}");
                     }
-                    else if((found = Check(temp, "name", &i, &start))){
-                        retval << context.getNPCName();
+                    else if((found = check(temp, "actionprevspell", &i, &start))){
+                        retval << "PLACEHOLDER_ACTION_PREV_SPELL";
                     }
-                }
-                else { // In messagebox or book, not dialogue
-
-                    /* empty outside dialogue */
-                    if(     (found = Check(temp, "faction", &i, &start)));
-                    else if((found = Check(temp, "nextpcrank", &i, &start)));
-                    else if((found = Check(temp, "pcnextrank", &i, &start)));
-                    else if((found = Check(temp, "pcrank", &i, &start)));
-                    else if((found = Check(temp, "rank", &i, &start)));
-                    
-                    /* uses pc in messageboxes */
-                    else if((found = Check(temp, "class", &i, &start))){
+                    else if((found = check(temp, "actionnextspell", &i, &start))){
+                        retval << "PLACEHOLDER_ACTION_NEXT_SPELL";
+                    }
+                    else if((found = check(temp, "actionrestmenu", &i, &start))){
+                        retval << context.getActionBinding("#{sRestKey}");
+                    }
+                    else if((found = check(temp, "actionmenumode", &i, &start))){
+                        retval << context.getActionBinding("#{sInventory}");
+                    }
+                    else if((found = check(temp, "actionactivate", &i, &start))){
+                        retval << context.getActionBinding("#{sActivate}");
+                    }
+                    else if((found = check(temp, "actionjournal", &i, &start))){
+                        retval << context.getActionBinding("#{sJournal}");
+                    }
+                    else if((found = check(temp, "actionforward", &i, &start))){
+                        retval << context.getActionBinding("#{sForward}");
+                    }
+                    else if((found = check(temp, "pccrimelevel", &i, &start))){
+                        retval << context.getPCBounty();
+                    }
+                    else if((found = check(temp, "actioncrouch", &i, &start))){
+                        retval << context.getActionBinding("#{sCrouch_Sneak}");
+                    }
+                    else if((found = check(temp, "actionjump", &i, &start))){
+                        retval << context.getActionBinding("#{sJump}");
+                    }
+                    else if((found = check(temp, "actionback", &i, &start))){
+                        retval << context.getActionBinding("#{sBack}");
+                    }
+                    else if((found = check(temp, "actionuse", &i, &start))){
+                        retval << context.getActionBinding("#{sUse}");
+                    }
+                    else if((found = check(temp, "actionrun", &i, &start))){
+                        retval << context.getActionBinding("#{sRun}");
+                    }
+                    else if((found = check(temp, "pcclass", &i, &start))){
                         retval << context.getPCClass();
                     }
-                    else if((found = Check(temp, "race", &i, &start))){
+                    else if((found = check(temp, "pcrace", &i, &start))){
                         retval << context.getPCRace();
                     }
-                    else if((found = Check(temp, "name", &i, &start))){
+                    else if((found = check(temp, "pcname", &i, &start))){
                         retval << context.getPCName();
                     }
-                }
-                
-                /* Not a builtin, try global variables */    
-                if(!found){
-                    /* if list of globals is empty, grab it and sort it by descending string length */
-                    if(globals.empty()){
-                        globals = context.getGlobals();
-                        sort(globals.begin(), globals.end(), longerStr);
+                    else if((found = check(temp, "cell", &i, &start))){
+                        retval << context.getCurrentCellName();
                     }
 
-                    for(unsigned int j = 0; j < globals.size(); j++){
-                        if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name
-                            std::string temp = text.substr(i+1, globals[j].length());
-                            transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
+                    else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox
+                        if(     (found = check(temp, "faction", &i, &start))){
+                            retval << context.getNPCFaction();
+                        }
+                        else if((found = check(temp, "nextpcrank", &i, &start))){
+                            retval << context.getPCNextRank();
+                        }
+                        else if((found = check(temp, "pcnextrank", &i, &start))){
+                            retval << context.getPCNextRank();
+                        }
+                        else if((found = check(temp, "pcrank", &i, &start))){
+                            retval << context.getPCRank();
+                        }
+                        else if((found = check(temp, "rank", &i, &start))){
+                            retval << context.getNPCRank();
                         }
 
-                        if((found = Check(temp, globals[j], &i, &start))){
-                            char type = context.getGlobalType(globals[j]);
+                        else if((found = check(temp, "class", &i, &start))){
+                            retval << context.getNPCClass();
+                        }
+                        else if((found = check(temp, "race", &i, &start))){
+                            retval << context.getNPCRace();
+                        }
+                        else if((found = check(temp, "name", &i, &start))){
+                            retval << context.getNPCName();
+                        }
+                    }
+                    else { // In messagebox or book, not dialogue
 
-                            switch(type){
-                                case 's': retval << context.getGlobalShort(globals[j]);  break;
-                                case 'l': retval << context.getGlobalLong(globals[j]); break; 
-                                case 'f': retval << context.getGlobalFloat(globals[j]); break;
+                        /* empty outside dialogue */
+                        if(     (found = check(temp, "faction", &i, &start)));
+                        else if((found = check(temp, "nextpcrank", &i, &start)));
+                        else if((found = check(temp, "pcnextrank", &i, &start)));
+                        else if((found = check(temp, "pcrank", &i, &start)));
+                        else if((found = check(temp, "rank", &i, &start)));
+
+                        /* uses pc in messageboxes */
+                        else if((found = check(temp, "class", &i, &start))){
+                            retval << context.getPCClass();
+                        }
+                        else if((found = check(temp, "race", &i, &start))){
+                            retval << context.getPCRace();
+                        }
+                        else if((found = check(temp, "name", &i, &start))){
+                            retval << context.getPCName();
+                        }
+                    }
+
+                    /* Not a builtin, try global variables */
+                    if(!found){
+                        /* if list of globals is empty, grab it and sort it by descending string length */
+                        if(globals.empty()){
+                            globals = context.getGlobals();
+                            sort(globals.begin(), globals.end(), longerStr);
+                        }
+
+                        for(unsigned int j = 0; j < globals.size(); j++){
+                            if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name
+                                std::string temp = text.substr(i+1, globals[j].length());
+                                transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
+                            }
+
+                            if((found = check(temp, globals[j], &i, &start))){
+                                char type = context.getGlobalType(globals[j]);
+
+                                switch(type){
+                                    case 's': retval << context.getGlobalShort(globals[j]);  break;
+                                    case 'l': retval << context.getGlobalLong(globals[j]); break;
+                                    case 'f': retval << context.getGlobalFloat(globals[j]); break;
+                                }
+                                break;
                             }
-                            break;
                         }
                     }
                 }
-                
-                /* Not found */
+                catch (std::exception& e)
+                {
+                    std::cerr << "Failed to replace escape character, with the following error: " << e.what() << std::endl;
+                    std::cerr << "Full text below: " << std::endl << text << std::endl;
+                }
+
+                // Not found, or error
                 if(!found){
                     /* leave unmodified */
                     i += 1;