diff --git a/include/niflib.h b/include/niflib.h
index 654f1d17ceaa2c1897082b9a805dd29f7295553b..e55ca36dc775781b0431670bef0e2cf23da91d1b 100644
--- a/include/niflib.h
+++ b/include/niflib.h
@@ -72,7 +72,8 @@ class NiControllerSequence;
 enum NifGame {
 	KF_MW = 0, /*!< keyframe files: NiSequenceStreamHelper header, .kf extension */
 	KF_DAOC = 1, /*!< keyframe files: NiNode header, .kfa extension */
-	KF_CIV4 = 2 /*!< keyframe files: NiControllerSequence header, .kf extension */
+	KF_CIV4 = 2, /*!< keyframe files: NiControllerSequence header, .kf extension */
+   KF_FFVT3R = 3, /*!< keyframe files: NiControllerSequence header, .kf extension */
 };
 
 /*! Export options. */
diff --git a/include/obj/NiSequence.h b/include/obj/NiSequence.h
index 006c1295070c73d784bebb27394dd1caa49c3a1a..6b56fbb1de4d4ec52812ae5e988242ca2dcdfc57 100644
--- a/include/obj/NiSequence.h
+++ b/include/obj/NiSequence.h
@@ -59,6 +59,45 @@ public:
 	NIFLIB_API virtual const Type & GetType() const;
 
 	//--BEGIN MISC CUSTOM CODE--//
+
+   // Name of this object. This is also the name of the action associated with this
+   // file. For instance, if the original NIF file is called "demon.nif" and this
+   // animation file contains an attack sequence, then the file would be called
+   // "demon_attack1.kf" and this field would contain the string "attack1".
+   // \return The current value.
+   string GetName() const;
+
+   // Name of this object. This is also the name of the action associated with this
+   // file. For instance, if the original NIF file is called "demon.nif" and this
+   // animation file contains an attack sequence, then the file would be called
+   // "demon_attack1.kf" and this field would contain the string "attack1".
+   // \param[in] value The new value.
+   void SetName( const string & value );
+
+   // Name of following referenced NiTextKeyExtraData class.
+   // \return The current value.
+   string GetTextKeysName() const;
+
+   // Name of following referenced NiTextKeyExtraData class.
+   // \param[in] value The new value.
+   void SetTextKeysName( const string & value );
+
+   // Link to NiTextKeyExtraData.
+   // \return The current value.
+   Ref<NiTextKeyExtraData > GetTextKeys() const;
+
+   // Link to NiTextKeyExtraData.
+   // \param[in] value The new value.
+   void SetTextKeys( Ref<NiTextKeyExtraData > value );
+
+   // Refers to controlled objects.
+   // \return The current value.
+   vector<ControllerLink > GetControlledBlocks() const;
+
+   // Refers to controlled objects.
+   // \param[in] value The new value.
+   void SetControlledBlocks( const vector<ControllerLink >& value );
+
 	//--END CUSTOM CODE--//
 protected:
 	/*!
diff --git a/src/niflib.cpp b/src/niflib.cpp
index 62cff53c0055d0773527aa6b428ceff9916148c3..35ebf44520e8ab61c93c5e1a17e9dd06bdab621b 100644
--- a/src/niflib.cpp
+++ b/src/niflib.cpp
@@ -28,6 +28,7 @@ All rights reserved.  Please see niflib.h for license. */
 #include "../include/obj/NiTransformInterpolator.h"
 #include "../include/obj/NiTransformController.h"
 #include "../include/obj/NiTransformData.h"
+#include "../include/obj/NiMultiTargetTransformController.h"
 #include "../include/obj/NiStringExtraData.h"
 #include "../include/obj/NiExtraData.h"
 #include "../include/obj/bhkRigidBody.h"
@@ -797,7 +798,37 @@ static void SplitNifTree( NiObject* root_object, NiObjectRef& xnif_root, list<Ni
 				}
 				mgr->ClearSequences();
 			}
-		} else {
+      } else if (kf_type == KF_FFVT3R) {
+
+         // Construct the Nif file without transform controllers ...
+         xnif_root = CloneNifTree( root_object, info.version, info.userVersion );
+
+         // Delete all NiMultiTargetTransformController
+         list<NiObjectRef> nodes = GetAllObjectsByType( xnif_root, NiMultiTargetTransformController::TYPE );
+         for ( list<NiObjectRef>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
+            if ( NiMultiTargetTransformControllerRef ctrl = DynamicCast<NiMultiTargetTransformController>(*it) ) {
+               if (NiNodeRef target = DynamicCast<NiNode>(ctrl->GetTarget())) {
+                  target->RemoveController(ctrl);
+               }
+            }
+         }
+
+         list<NiObjectRef> mgrs = GetAllObjectsByType( xnif_root, NiControllerManager::TYPE );
+         for ( list<NiObjectRef>::iterator it = mgrs.begin(); it != mgrs.end(); ++it) {
+            NiControllerManagerRef mgr = DynamicCast<NiControllerManager>(*it);
+            if ( mgr == NULL ) {
+               continue;
+            }
+            NiObjectNETRef target = mgr->GetTarget();
+            target->RemoveController( StaticCast<NiTimeController>(mgr) );
+            vector<NiControllerSequenceRef> seqs = mgr->GetControllerSequences();
+            for (vector<NiControllerSequenceRef>::iterator itr = seqs.begin(); itr != seqs.end(); ++itr) {
+               xkf_roots.push_back( StaticCast<NiObject>(*itr) );
+            }
+            mgr->ClearSequences();
+         }
+
+      } else {
 			throw runtime_error("KF splitting for the requested game is not yet implemented.");
 		}
 	} else {
@@ -853,6 +884,26 @@ void WriteFileGroup( string const & file_name, NiObject * root_object, const Nif
             WriteNifTree( path, StaticCast<NiObject>(seq), info );
          }         
       }
+   } else if (kf_type == KF_FFVT3R) {
+
+      NiObjectRef xnif_root;
+      list<NiObjectRef> xkf_roots;
+      Kfm kfm; // dummy
+      SplitNifTree( root_object, xnif_root, xkf_roots, kfm, kf_type, info );
+      if ( export_files == EXPORT_NIF || export_files == EXPORT_NIF_KF || export_files == EXPORT_NIF_KF_MULTI ) {
+         WriteNifTree( file_name_path + file_name_base + ".nif", xnif_root, info );
+      }
+      if ( export_files == EXPORT_NIF_KF || export_files == EXPORT_KF ) {
+         WriteNifTree( file_name_path + file_name_base + ".kf", xkf_roots, info );
+      } else if ( export_files == EXPORT_NIF_KF_MULTI || export_files == EXPORT_KF_MULTI ) {
+         for ( list<NiObjectRef>::iterator it = xkf_roots.begin(); it != xkf_roots.end(); ++it ) {
+            NiControllerSequenceRef seq = DynamicCast<NiControllerSequence>(*it);
+            if (seq == NULL)
+               continue;
+            string path = file_name_path + file_name_base + "_" + CreateFileName(seq->GetTargetName()) + "_" + CreateFileName(seq->GetName()) + ".kf";
+            WriteNifTree( path, StaticCast<NiObject>(seq), info );
+         }         
+      }
    } else
 		throw runtime_error("Not yet implemented.");
 };
diff --git a/src/obj/NiControllerSequence.cpp b/src/obj/NiControllerSequence.cpp
index b3ad3e98482ffa7bf82015741440a1f40ccc9c8b..c8d64389cb657d02d73673e239a57f594ac31fc0 100644
--- a/src/obj/NiControllerSequence.cpp
+++ b/src/obj/NiControllerSequence.cpp
@@ -339,7 +339,9 @@ void NiControllerSequence::SetControllerData(const vector<ControllerLink>& value
 
 
 Ref<NiTextKeyExtraData> NiControllerSequence::GetTextKeyExtraData() const {
-	return textKeys;
+   if (textKeys != NULL)
+	   return textKeys;
+   return NiSequence::GetTextKeys();
 }
 
 
diff --git a/src/obj/NiSequence.cpp b/src/obj/NiSequence.cpp
index d7a916b0ff973a5cfe7285efaae52b6a6099f07c..3507645e514a0bb416eeca50da55721f73992103 100644
--- a/src/obj/NiSequence.cpp
+++ b/src/obj/NiSequence.cpp
@@ -358,4 +358,36 @@ std::list<NiObjectRef> NiSequence::GetRefs() const {
 }
 
 //--BEGIN MISC CUSTOM CODE--//
+string NiSequence::GetName() const {
+   return name;
+}
+
+void NiSequence::SetName( const string & value ) {
+   name = value;
+}
+
+string NiSequence::GetTextKeysName() const {
+   return textKeysName;
+}
+
+void NiSequence::SetTextKeysName( const string & value ) {
+   textKeysName = value;
+}
+
+Ref<NiTextKeyExtraData > NiSequence::GetTextKeys() const {
+   return textKeys;
+}
+
+void NiSequence::SetTextKeys( Ref<NiTextKeyExtraData > value ) {
+   textKeys = value;
+}
+
+vector<ControllerLink > NiSequence::GetControlledBlocks() const {
+   return controlledBlocks;
+}
+
+void NiSequence::SetControlledBlocks( const vector<ControllerLink >& value ) {
+   controlledBlocks = value;
+}
+
 //--END CUSTOM CODE--//