From 35ddfcfbf500952adbc112fd3895479cb601238f Mon Sep 17 00:00:00 2001 From: Nicolay Korslund Date: Mon, 22 Feb 2010 21:00:38 +0100 Subject: [PATCH] Finished GMST --- esm/esm_reader.hpp | 36 ++-- esm/loadgmst.hpp | 248 +++++++++++++++++++++++++++ esm/records.hpp | 3 + old_d_version/esm/loadgmst.d | 313 ----------------------------------- 4 files changed, 269 insertions(+), 331 deletions(-) create mode 100644 esm/loadgmst.hpp delete mode 100644 old_d_version/esm/loadgmst.d diff --git a/esm/esm_reader.hpp b/esm/esm_reader.hpp index 1212e9387..94be36fe2 100644 --- a/esm/esm_reader.hpp +++ b/esm/esm_reader.hpp @@ -22,6 +22,24 @@ enum Version VER_13 = 0x3fa66666 }; +enum FileType + { + FT_ESP = 0, // Plugin + FT_ESM = 1, // Master + FT_ESS = 32 // Savegame + }; + +// Used to mark special files. The original ESM files are given +// special treatment in a few places, most noticably in loading and +// filtering out "dirtly" GMST entries correctly. +enum SpecialFile + { + SF_Other, + SF_Morrowind, + SF_Tribunal, + SF_Bloodmoon + }; + /* A structure used for holding fixed-length strings. In the case of LEN=4, it can be more efficient to match the string as a 32 bit number, therefore the struct is implemented as a union with an int. @@ -103,24 +121,6 @@ public: typedef std::vector MasterList; - enum FileType - { - FT_ESP = 0, // Plugin - FT_ESM = 1, // Master - FT_ESS = 32 // Savegame - }; - - // Used to mark special files. The original ESM files are given - // special treatment in a few places, most noticably in loading and - // filtering out "dirtly" GMST entries correctly. - enum SpecialFile - { - SF_Other, - SF_Morrowind, - SF_Tribunal, - SF_Bloodmoon - }; - /************************************************************************* * * Information retrieval diff --git a/esm/loadgmst.hpp b/esm/loadgmst.hpp new file mode 100644 index 000000000..4f456280d --- /dev/null +++ b/esm/loadgmst.hpp @@ -0,0 +1,248 @@ +#ifndef _ESM_GMST_H +#define _ESM_GMST_H + +#include "esm_reader.hpp" +#include "defs.hpp" + +namespace ESM { + +/* + * Game setting, with automatic cleaning of "dirty" entries. + * + */ + +struct GameSetting +{ + std::string id; + + // One of these is used depending on the variable type + std::string str; + int i; + float f; + VarType type; + + // Set to true if this is a 'dirty' entry which should be ignored + bool dirty; + + /* + These functions check if this game setting is one of the "dirty" + GMST records found in many mods. These are due to a serious bug in + the official TES3 editor. It only occurs in the newer editor + versions that came with Tribunal and Bloodmoon, and only if a + modder tries to make a mod without loading the corresponding + expansion master file. For example, if you have Tribunal installed + and try to make a mod without loading Tribunal.esm, the editor + will insert these GMST records as a replacement for the entries it + cannot find in the ESMs. + + The values of these "dirty" records differ in general from their + values as defined in Tribunal.esm and Bloodmoon.esm, and are + always set to the same "default" values. Most of these values are + nonsensical, ie. changing the "Seller Max" string to "Max Sale", + or change the stats of werewolves to useless values like 1. Some + of them break certain spell effects. + + It is most likely that these values are just leftover values from + an early stage of development that are inserted as default values + by the editor code. They are supposed to be overridden when the + correct esm file is loaded. When it isn't loaded however, you get + stuck with the initial value, and this gets written to every mod + by the editor, for some reason. + + Bethesda themselves have fallen for this bug. If you install both + Tribunal and Bloodmoon, the updated Tribunal.esm will contain the + dirty GMST settings from Bloodmoon, and Bloodmoon.esm will contain + some of the dirty settings from Tribunal. In other words, this bug + affects the game EVEN IF YOU DO NOT USE ANY MODS! + + The guys at Bethesda are well aware of this bug (and many others), + as the mod community and fan base complained about them for a long + time. But unfortunately it was never fixed. + + There are several tools available to help modders remove these + records from their files, but not all modders use them, and they + really shouldn't have to. In this file we choose instead to reject + all the corrupt values at load time. + + These functions checks if the current game setting is one of the + "dirty" ones as described above. TODO: I have not checked this + against other sources yet, do that later. Currently recognizes 22 + values for tribunal and 50 for bloodmoon. Legitimate GMSTs in mods + (setting values other than the default "dirty" ones) are not + affected and will work correctly. + */ + + // Some handy macros +#define cI(s,x) { if(id == (s)) return (i == (x)); } +#define cF(s,x) { if(id == (s)) return (f == (x)); } +#define cS(s,x) { if(id == (s)) return (str == (x)); } + + /* + Checks for dirty tribunal values. These will be ignored if found + in any file except when they are found in "Tribunal.esm". + */ + bool isDirtyTribunal() + { + /* + Here, id contains the game setting name, and we check the + setting for certain values. If it matches, this is a "dirty" + entry. The correct entry (as defined in Tribunal and Bloodmoon + esms) are given in the comments. Many of the values are correct, + and are marked as 'same'. We still ignore them though, as they + are still in the wrong file and might override custom values + from other mods. + */ + + // Strings + cS("sProfitValue", "Profit Value"); // 'Profit:' + cS("sEditNote", "Edit Note"); // same + cS("sDeleteNote", "Delete Note?"); // same + cS("sMaxSale", "Max Sale"); // 'Seller Max' + cS("sMagicFabricantID", "Fabricant"); // 'Fabricant_summon' + cS("sTeleportDisabled", + "Teleportation magic does not work here.");// same + cS("sLevitateDisabled", + "Levitation magic does not work here."); // same + cS("sCompanionShare", "Companion Share"); // 'Share' + cS("sCompanionWarningButtonOne", + "Let the mercenary quit."); // same + cS("sCompanionWarningButtonTwo", + "Return to Companion Share display."); // same + cS("sCompanionWarningMessage", + "Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value."); + // 'Your mercenary is poorer now than when he contracted with + // you. Your mercenary will quit if you do not give him gold + // or goods to bring his Profit to a positive value.' + // [The difference here is "Profit Value" -> "Profit"] + + // Strings that matches the id + cS("sEffectSummonFabricant", id);// 'Summon Fabricant' + return false; + } + + // Bloodmoon variant + bool isDirtyBloodmoon() + { + // Strings + cS("sWerewolfPopup", "Werewolf"); // same + cS("sWerewolfRestMessage", + "You cannot rest in werewolf form."); // same + cS("sWerewolfRefusal", + "You cannot do this as a werewolf."); // same + cS("sWerewolfAlarmMessage", + "You have been detected changing from a werewolf state."); + // 'You have been detected as a known werewolf.' + + // Strings that matches the id + cS("sMagicCreature01ID", id); // 'BM_wolf_grey_summon' + cS("sMagicCreature02ID", id); // 'BM_bear_black_summon' + cS("sMagicCreature03ID", id); // 'BM_wolf_bone_summon' + cS("sMagicCreature04ID", id); // same + cS("sMagicCreature05ID", id); // same + cS("sEffectSummonCreature01", id); // 'Calf Wolf' + cS("sEffectSummonCreature02", id); // 'Calf Bear' + cS("sEffectSummonCreature03", id); // 'Summon Bonewolf' + cS("sEffectSummonCreature04", id); // same + cS("sEffectSummonCreature05", id); // same + + // Integers + cI("iWereWolfBounty", 10000); // 1000 + cI("iWereWolfFightMod", 100); // same + cI("iWereWolfFleeMod", 100); // same + cI("iWereWolfLevelToAttack", 20); // same + + // Floats + cF("fFleeDistance", 3000); // same + cF("fCombatDistanceWerewolfMod", 0.3); // same + cF("fWereWolfFatigue", 400); // same + cF("fWereWolfEnchant", 1); // 0 + cF("fWereWolfArmorer", 1); // 0 + cF("fWereWolfBlock", 1); // 0 + cF("fWereWolfSneak", 1); // 95 + cF("fWereWolfDestruction", 1); // 0 + cF("fWereWolfEndurance", 150); // same + cF("fWereWolfConjuration", 1); // 0 + cF("fWereWolfRestoration", 1); // 0 + cF("fWereWolfAthletics", 150); // 50 + cF("fWereWolfLuck", 1); // 25 + cF("fWereWolfSilverWeaponDamageMult", 1.5); // 2 + cF("fWereWolfMediumArmor", 1); // 0 + cF("fWereWolfShortBlade", 1); // 0 + cF("fWereWolfAcrobatics", 150); // 80 + cF("fWereWolfSpeechcraft", 1); // 0 + cF("fWereWolfAlteration", 1); // 0 + cF("fWereWolfIllusion", 1); // 0 + cF("fWereWolfLongBlade", 1); // 0 + cF("fWereWolfMarksman", 1); // 0 + cF("fWereWolfHandtoHand", 100); // same + cF("fWereWolfIntellegence", 1); // 0 + cF("fWereWolfAlchemy", 1); // 0 + cF("fWereWolfUnarmored", 100); // same + cF("fWereWolfAxe", 1); // 0 + cF("fWereWolfRunMult", 1.5); // 1.3 + cF("fWereWolfMagicka", 100); // same + cF("fWereWolfAgility", 150); // same + cF("fWereWolfBluntWeapon", 1); // 0 + cF("fWereWolfSecurity", 1); // 0 + cF("fWereWolfPersonality", 1); // 0 + cF("fWereWolfMerchantile", 1); // 0 + cF("fWereWolfHeavyArmor", 1); // 0 + cF("fWereWolfSpear", 1); // 0 + cF("fWereWolfStrength", 150); // same + cF("fWereWolfHealth", 2); // same + cF("fWereWolfMysticism", 1); // 0 + cF("fWereWolfLightArmor", 1); // 0 + cF("fWereWolfWillPower", 1); // 0 + cF("fWereWolfSpeed", 150); // 90 + return false; + } + +#undef cI +#undef cF +#undef cS + + void load(ESMReader &esm) + { + dirty = false; + + // We are apparently allowed to be empty + if(!esm.hasMoreSubs()) + { + type = VT_None; + return; + } + + // Load some data + esm.getSubName(); + NAME n = esm.retSubName(); + if(n == "STRV") + { + str = esm.getHString(); + type = VT_String; + } + else if(n == "INTV") + { + esm.getHT(i); + type = VT_Int; + } + else if(n == "FLTV") + { + esm.getHT(f); + type = VT_Float; + } + else + esm.fail("Unwanted subrecord type"); + + int spf = esm.getSpecial(); + + // Check if this is one of the dirty values mentioned above. If it + // is, we set the dirty flag. This will ONLY work if you've set + // the 'id' string correctly before calling load(). + + if( ( spf != SF_Tribunal && isDirtyTribunal() ) || + ( spf != SF_Bloodmoon && isDirtyBloodmoon() ) ) + dirty = true; + } +}; +} +#endif diff --git a/esm/records.hpp b/esm/records.hpp index d34f22bc9..27e2e3bb1 100644 --- a/esm/records.hpp +++ b/esm/records.hpp @@ -17,6 +17,7 @@ #include "loadench.hpp" #include "loadfact.hpp" #include "loadglob.hpp" +#include "loadgmst.hpp" #include "loadingr.hpp" #include "loadligh.hpp" #include "loadlocks.hpp" @@ -62,6 +63,7 @@ enum RecNameInts REC_GMST = 0x54534d47, REC_INFO = 0x4f464e49, REC_INGR = 0x52474e49, + REC_LAND = 0x0, REC_LEVC = 0x4356454c, REC_LEVI = 0x4956454c, REC_LIGH = 0x4847494c, @@ -71,6 +73,7 @@ enum RecNameInts REC_MISC = 0x4353494d, REC_NPC_ = 0x5f43504e, REC_NPCC = 0x4343504e, + REC_PGRC = 0x0, REC_PROB = 0x424f5250, REC_RACE = 0x45434152, REC_REGN = 0x4e474552, diff --git a/old_d_version/esm/loadgmst.d b/old_d_version/esm/loadgmst.d deleted file mode 100644 index e48f06fc4..000000000 --- a/old_d_version/esm/loadgmst.d +++ /dev/null @@ -1,313 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.snaptoad.com/ - - This file (loadgmst.d) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - 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 - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -module esm.loadgmst; -import esm.imports; - -/* - * Game setting - */ - -// TODO: It's likely that we don't need this struct any longer, given -// that game settings are now stored in Monster code. We will still -// use the loading code and the dirty value cleaning code of course, -// but there's no longer any need to store it in a separate lookup -// list, since Monster variables can be looked up just as fast. -struct GameSetting -{ - LoadState state; - char[] id; - - union - { - char[] str; - int i; - float f; - } - VarType type; - - // These functions check if this game setting is one of the "dirty" - // GMST records found in many mods. These are due to a serious bug - // in the official TES3 editor. It only occurs in the newer editor - // versions that came with Tribunal and Bloodmoon, and only if a - // modder tries to make a mod without loading the corresponding - // expansion master file. For example, if you have Tribunal - // installed and try to make a mod without loading Tribunal.esm, the - // editor will insert these GMST records. - - // The values of these "dirty" records differ in general from their - // values as defined in Tribunal.esm and Bloodmoon.esm, and are - // always set to the same "default" values. Most of these values are - // nonsensical, ie. changing the "Seller Max" string to "Max Sale", - // or change the stats of werewolves to useless values like 1. Some - // of them break certain spell effects. - - // It is most likely that these values are just leftover values from - // an early stage of development that are inserted as default values - // by the editor code. They are supposed to be overridden when the - // correct esm file is loaded. When it isn't loaded, you get stuck - // with the initial value, and this gets written to every mod for - // some reason. - - // Bethesda themselves have fallen for this bug. If you install both - // Tribunal and Bloodmoon, the updated Tribunal.esm will contain the - // dirty GMST settings from Bloodmoon, and Bloodmoon.esm will - // contain some of the dirty settings from Tribunal. In other words, - // this bug affects the game EVEN IF YOU DO NOT USE ANY MODS! - - // The guys at Bethesda are well aware of this bug (and many - // others), as the mod community and fan base complained about them - // for a long time. But it was never fixed. - - // There are several programs available to help modders remove these - // records from their files, but not all modders use them, and they - // really shouldn't have to. In this file we choose instead to - // reject all the corrupt values at load time. - - // These functions checks if the current game setting is one of the - // "dirty" ones as described above. TODO: I have not checked this - // against other sources yet, do that later. Currently recognizes 22 - // values for tribunal and 50 for bloodmoon. Legitimate GMSTs in - // mods (setting values other than the default "dirty" ones) are not - // affected and will work correctly. - - // Checks for dirty tribunal values. These will be ignored if found - // in any file except when they are found in "Tribunal.esm". - bool isDirtyTribunal() - { - bool result = false; - void cI(int ii) { if(ii == i) result = true; } - void cF(float ff) { if(ff == f) result = true; } - void cS(char[] ss) { if(ss == str) result = true; } - - // Here, id contains the game setting name, and we check the - // setting for certain values. If it matches, this is a "dirty" - // entry. The correct entry (as defined in Tribunal and Bloodmoon - // esms) are given in the comments. Many of the values are - // correct, and are marked as 'same'. We still ignore them though, - // as they are still in the wrong file and might override custom - // values from other mods. - - switch(id) - { - // Strings - case "sProfitValue": cS("Profit Value"); break; // 'Profit:' - case "sEditNote": cS("Edit Note"); break; // same - case "sDeleteNote": cS("Delete Note?"); break; // same - case "sMaxSale": cS("Max Sale"); break; // 'Seller Max' - case "sMagicFabricantID": cS("Fabricant"); break; // 'Fabricant_summon' - case "sTeleportDisabled": cS("Teleportation magic does not work here."); - break; // same - case "sLevitateDisabled": cS("Levitation magic does not work here."); - break; // same - case "sCompanionShare": cS("Companion Share"); break; // 'Share' - case "sCompanionWarningButtonOne": cS("Let the mercenary quit."); - break; // same - case "sCompanionWarningButtonTwo": cS("Return to Companion Share display."); break; // same - case "sCompanionWarningMessage": cS("Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value."); break; - // 'Your mercenary is poorer now than when he contracted with - // you. Your mercenary will quit if you do not give him gold - // or goods to bring his Profit to a positive value.' - // [The difference here is "Profit Value" -> "Profit"} - - // Strings that matches the id - case "sEffectSummonFabricant": // 'Summon Fabricant' - cS(id); - break; - - default: - } - return result; - } - - // Bloodmoon variant - bool isDirtyBloodmoon() - { - bool result = false; - void cI(int ii) { if(ii == i) result = true; } - void cF(float ff) { if(ff == f) result = true; } - void cS(char[] ss) { if(ss == str) result = true; } - - switch(id) - { - // Strings - case "sWerewolfPopup": cS("Werewolf"); break; // same - case "sWerewolfRestMessage": cS("You cannot rest in werewolf form."); - break; // same - case "sWerewolfRefusal": cS("You cannot do this as a werewolf."); - break; // same - case "sWerewolfAlarmMessage": cS("You have been detected changing from a werewolf state."); break; - // 'You have been detected as a known werewolf.' - - // Strings that matches the id - case "sMagicCreature01ID": // 'BM_wolf_grey_summon' - case "sMagicCreature02ID": // 'BM_bear_black_summon' - case "sMagicCreature03ID": // 'BM_wolf_bone_summon' - case "sMagicCreature04ID": // same - case "sMagicCreature05ID": // same - case "sEffectSummonCreature01": // 'Calf Wolf' - case "sEffectSummonCreature02": // 'Calf Bear' - case "sEffectSummonCreature03": // 'Summon Bonewolf' - case "sEffectSummonCreature04": // same - case "sEffectSummonCreature05": // same - cS(id); - break; - - // Integers - case "iWereWolfBounty": cI(10000); break; // 1000 - case "iWereWolfFightMod": cI(100); break; // same - case "iWereWolfFleeMod": cI(100); break; // same - case "iWereWolfLevelToAttack": cI(20); break; // same - - // Floats - case "fFleeDistance": cF(3000); break; // same - case "fCombatDistanceWerewolfMod": cF(0.3); break; // same - case "fWereWolfFatigue": cF(400); break; // same - case "fWereWolfEnchant": cF(1); break; // 0 - case "fWereWolfArmorer": cF(1); break; // 0 - case "fWereWolfBlock": cF(1); break; // 0 - case "fWereWolfSneak": cF(1); break; // 95 - case "fWereWolfDestruction": cF(1); break; // 0 - case "fWereWolfEndurance": cF(150); break; // same - case "fWereWolfConjuration": cF(1); break; // 0 - case "fWereWolfRestoration": cF(1); break; // 0 - case "fWereWolfAthletics": cF(150); break; // 50 - case "fWereWolfLuck": cF(1); break; // 25 - case "fWereWolfSilverWeaponDamageMult": cF(1.5); break; // 2 - case "fWereWolfMediumArmor": cF(1); break; // 0 - case "fWereWolfShortBlade": cF(1); break; // 0 - case "fWereWolfAcrobatics": cF(150); break; // 80 - case "fWereWolfSpeechcraft": cF(1); break; // 0 - case "fWereWolfAlteration": cF(1); break; // 0 - case "fWereWolfIllusion": cF(1); break; // 0 - case "fWereWolfLongBlade": cF(1); break; // 0 - case "fWereWolfMarksman": cF(1); break; // 0 - case "fWereWolfHandtoHand": cF(100); break; // same - case "fWereWolfIntellegence": cF(1); break; // 0 - case "fWereWolfAlchemy": cF(1); break; // 0 - case "fWereWolfUnarmored": cF(100); break; // same - case "fWereWolfAxe": cF(1); break; // 0 - case "fWereWolfRunMult": cF(1.5); break; // 1.3 - case "fWereWolfMagicka": cF(100); break; // same - case "fWereWolfAgility": cF(150); break; // same - case "fWereWolfBluntWeapon": cF(1); break; // 0 - case "fWereWolfSecurity": cF(1); break; // 0 - case "fWereWolfPersonality": cF(1); break; // 0 - case "fWereWolfMerchantile": cF(1); break; // 0 - case "fWereWolfHeavyArmor": cF(1); break; // 0 - case "fWereWolfSpear": cF(1); break; // 0 - case "fWereWolfStrength": cF(150); break; // same - case "fWereWolfHealth": cF(2); break; // same - case "fWereWolfMysticism": cF(1); break; // 0 - case "fWereWolfLightArmor": cF(1); break; // 0 - case "fWereWolfWillPower": cF(1); break; // 0 - case "fWereWolfSpeed": cF(150); break; // 90 - - default: - } - return result; - } - - char[] toString() - { - if(type == VarType.Int) return format(i); - else if(type == VarType.Float) return format(f); - else if(type == VarType.String) return str; - else if(type == VarType.None) return "(no value)"; - assert(0); - } - - void load() - { - // We are apparently allowed not to have any value at all. - if(!esFile.hasMoreSubs()) - { - if(state == LoadState.Previous) - writefln("Warning: Overwriting game setting %s with void value", id); - type = VarType.None; - return; - } - - // If this is not the first record of this type to be loaded, then - // we do a little "trick" to avoid overwriting the current data - // with one of the dirty GMSTs. - if(state == LoadState.Previous) - { - // Load the data in a temporary game setting instead - GameSetting g; - g.state = LoadState.Unloaded; - g.id = id; - g.load(); - - // Only copy it if it was valid - if(g.type != VarType.Ignored) - { - // Don't allow a change of type, unless the setting we are - // overwriting is an ignored value. - if(g.type != type && type != VarType.Ignored) - esFile.fail(format("GMST changed type from %d to %d", - cast(int)type, cast(int)g.type)); - - str = g.str; // This will copy all the data no matter what - // type it is - } - return; - } - - // Actually load some data - esFile.getSubName(); - switch(esFile.retSubName()) - { - case "STRV": - str = esFile.getHString(); - type = VarType.String; - break; - case "INTV": - i = esFile.getHInt(); - type = VarType.Int; - break; - case "FLTV": - f = esFile.getHFloat(); - type = VarType.Float; - break; - default: - esFile.fail("Unwanted subrecord type"); - } - - SpecialFile spf = esFile.getSpecial(); - - // Check if this is one of the dirty values mentioned above. If it - // is, we set the VarType to Ignored. This leaves the posibility - // that this becomes the final var type, for example if you load a - // plugin with tribunal-gmst settings without loading tribunal - // first. (Then there would only exist dirty values for this - // settings, no "real" value.) But this is not likely a problem, - // since these empty values will never be used and we can check - // for them in any case. - - if( ( spf != SpecialFile.Tribunal && isDirtyTribunal() ) || - ( spf != SpecialFile.Bloodmoon && isDirtyBloodmoon() ) ) - type = VarType.Ignored; - } -} -ListID!(GameSetting) gameSettings;