diff --git a/dat/spfx.xml b/dat/spfx.xml
new file mode 100644
index 0000000..c3ae373
--- /dev/null
+++ b/dat/spfx.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<spfxs>
+ <spfx name="ExpS">
+  <gfx x="6" y="5">exps</gfx>
+  <anim>400</anim>
+ </spfx>
+ <spfx name="ExpM">
+  <gfx x="6" y="5">expm</gfx>
+  <anim>450</anim>
+ </spfx>
+ <spfx name="ExpL">
+  <gfx x="6" y="5">expl</gfx>
+  <anim>500</anim>
+ </spfx>
+ <spfx name="cargo">
+  <gfx x="6" y="6">cargo</gfx>
+  <ttl>15000</ttl>
+  <anim>5000</anim>
+ </spfx>
+ <spfx name="EmpS">
+  <gfx x="6" y="5">emps</gfx>
+  <anim>400</anim>
+ </spfx>
+ <spfx name="EmpM">
+  <gfx x="6" y="5">empm</gfx>
+  <anim>450</anim>
+ </spfx>
+ <spfx name="ShiS">
+  <gfx x="6" y="5">shis</gfx>
+  <anim>400</anim>
+ </spfx>
+ <spfx name="ShiM">
+  <gfx x="6" y="5">shim</gfx>
+  <anim>450</anim>
+ </spfx>
+ <spfx name="PlaS">
+  <gfx x="6" y="5">plas</gfx>
+  <anim>400</anim>
+ </spfx>
+ <spfx name="PlaM">
+  <gfx x="6" y="5">plam</gfx>
+  <anim>450</anim>
+ </spfx>
+</spfxs>
diff --git a/src/spfx.c b/src/spfx.c
index 6f0a492..d1006bb 100644
--- a/src/spfx.c
+++ b/src/spfx.c
@@ -16,13 +16,22 @@
 #include "opengl.h"
 #include "pause.h"
 #include "rng.h"
+#include "ldata.h"
+#include "lxml.h"
 #include "spfx.h"
 
-#define SPFX_GFX          "../gfx/spfx/"  /**< Graphics location. */
-#define SPFX_CHUNK        32              /**< Chunk to allocate when needed. */
-#define SHAKE_VEL_MOD     0.0008          /**< Shake modifier. */
+#define SPFX_XML_ID       "spfxs"           /**< XML Document tag. */
+#define SPFX_XML_TAG      "spfx"            /**< SPFX XML node tag. */
 
-#define HAPTIC_UPDATE_INTERVAL 0.1        /**< Time between haptic updates. */
+#define SPFX_DATA         "../dat/spfx.xml" /**< Location of the spfx datafile. */
+#define SPFX_GFX_PRE      "../gfx/spfx/"    /**< Location of the graphic. */
+#define SPFX_GFX_SUF      ".png"            /**< Suffix of graphics. */
+
+#define CHUNK_SIZE        32                /**< Chunk size to allocate spfx bases. */
+#define SPFX_CHUNK        32                /**< Chunk to allocate when needed. */
+#define SHAKE_VEL_MOD     0.0008            /**< Shake modifier. */
+
+#define HAPTIC_UPDATE_INTERVAL 0.1          /**< Time between haptic updates. */
 
 /* Special hardcoded effects.. */
 
@@ -81,7 +90,7 @@ static int   spfx_nstack_back  = 0;       /**< Number of special effects in the
 static int   spfx_mstack_back  = 0;       /**< Memory allocated for special effects in back. */
 
 /* General. */
-static int spfx_base_load(char* name, int ttl, int anim, char* gfx, int sx, int sy);
+static int spfx_base_parse(SPFX_Base* tmp, const xmlNodePtr parent);
 static void spfx_base_free(SPFX_Base* effect);
 static void spfx_destroy(SPFX* layer, int* nlayer, int spfx);
 static void spfx_update_layer(SPFX* layer, int* nlayer, const double dt);
@@ -90,28 +99,42 @@ static int spfx_hapticInit(void);
 static void spfx_hapticRumble(double mod);
 
 /**
- * @brief Load an SPFX_Base into the stack based on some params.
- *    @param name Name of the spfx.
- *    @param ttl Time to live of the spfx.
- *    @param gfx Name of the graphic effect to use.
- *    @param sx Number of x sprites in the graphic.
- *    @param sy Number of y sprites in the graphic.
+ * @brief Parse an xml node containing an spfx.
+ *    @param tmp Address to load sfx into.
+ *    @param parent XML node containing the spfx data.
  *    @return 0 on success.
  */
-static int spfx_base_load(char* name, int ttl, int anim, char* gfx, int sx, int sy) {
-  SPFX_Base* cur;
-  char buf[PATH_MAX];
+static int spfx_base_parse(SPFX_Base* tmp, const xmlNodePtr parent) {
+  xmlNodePtr node;
 
-  /* Create new effect. */
-  spfx_effects = realloc(spfx_effects, ++spfx_neffects*sizeof(SPFX_Base));
-  cur = &spfx_effects[spfx_neffects-1];
+  /* Clear data. */
+  memset(tmp, 0, sizeof(SPFX_Base));
 
-  /* Fill it with the data. */
-  cur->name = strdup(name);
-  cur->anim = (double)anim / 1000.;
-  cur->ttl  = (double)ttl / 1000.;
-  snprintf(buf, PATH_MAX, SPFX_GFX"%s", gfx);
-  cur->gfx = gl_newSprite(buf, sx, sy, 0);
+  /* Get the name (mallocs). */
+  tmp->name = xml_nodeProp(parent, "name");
+
+  /* Extract the data. */
+  node = parent->xmlChildrenNode;
+  do {
+    xmlr_float(node, "anim", tmp->anim);
+    xmlr_float(node, "ttl", tmp->ttl);
+    if(xml_isNode(node, "gfx"))
+      tmp->gfx = xml_parseTexture(node,
+          SPFX_GFX_PRE"%s"SPFX_GFX_SUF, 6, 5, 0);
+  } while(xml_nextNode(node));
+
+  /* Convert from ms to s. */
+  tmp->anim /= 1000.;
+  tmp->ttl  /= 1000.;
+  if(tmp->ttl == 0.)
+    tmp->ttl = tmp->anim;
+
+#define MELEMENT(o,s) \
+  if(o) WARN("SPFX '%s' missing/invalid '"s"' element", tmp->name) /**< Define to help check for data errors. */
+  MELEMENT(tmp->anim  == 0.,   "anim");
+  MELEMENT(tmp->ttl   == 0.,   "ttl");
+  MELEMENT(tmp->gfx   == NULL,  "gfx");
+#undef ELEMENT
 
   return 0;
 }
@@ -121,8 +144,14 @@ static int spfx_base_load(char* name, int ttl, int anim, char* gfx, int sx, int
  *    @param effect SPFX_Base to free.
  */
 static void spfx_base_free(SPFX_Base* effect) {
-  if(effect->name != NULL)  free(effect->name);
-  if(effect->gfx != NULL)   gl_freeTexture(effect->gfx);
+  if(effect->name != NULL)  {
+    free(effect->name);
+    effect->name = NULL;
+  }
+  if(effect->gfx != NULL) {
+    gl_freeTexture(effect->gfx);
+    effect->gfx = NULL;
+  }
 }
 
 /**
@@ -146,22 +175,47 @@ int spfx_get(char* name) {
  * @todo Make spfx not hardcoded.
  */
 int spfx_load(void) {
-  /* Standard explosion effects. */
-  spfx_base_load("ExpS", 400, 400, "exps.png", 6, 5);
-  spfx_base_load("ExpM", 450, 450, "expm.png", 6, 5);
-  spfx_base_load("ExpL", 500, 500, "expl.png", 6, 5);
-  /* Cargo Rotation. */
-  spfx_base_load("cargo", 15000, 5000, "cargo.png", 6, 6);
-  /* EMP Blasts. */
-  spfx_base_load("EmpS", 400, 400, "emps.png", 6, 5);
-  spfx_base_load("EmpM", 450, 450, "empm.png", 6, 5);
-  /* Shield hits. */
-  spfx_base_load("ShiS", 400, 400, "shis.png", 6, 5);
-  spfx_base_load("ShiM", 450, 450, "shim.png", 6, 5);
-  /* Plasma hits. */
-  spfx_base_load("PlaS", 400, 400, "plas.png", 6, 5);
-  spfx_base_load("PlaM", 450, 450, "plam.png", 6, 5);
+  int mem;
+  uint32_t bufsize;
+  char* buf;
+  xmlNodePtr node;
+  xmlDocPtr doc;
 
+  /* Load and read the data. */
+  buf = ldata_read(SPFX_DATA, &bufsize);
+  doc = xmlParseMemory(buf, bufsize);
+
+  /* Check to see if document exists. */
+  node = doc->xmlChildrenNode;
+  if(!xml_isNode(node, SPFX_XML_ID)) {
+    ERR("Malformed '"SPFX_DATA"' file: missing root element '"SPFX_XML_ID"'");
+    return -1;
+  }
+
+  /* Check to see if it's populated. */
+  node = node->xmlChildrenNode; /* First system node. */
+  if(node == NULL) {
+    ERR("Malformed '"SPFX_DATA"' file: does not contain elements");
+    return -1;
+  }
+
+  /* First pass, loads up ammunition. */
+  mem = 0;
+  do {
+    if(xml_isNode(node, SPFX_XML_TAG)) {
+      spfx_neffects++;
+      if(spfx_neffects > mem) {
+        mem += CHUNK_SIZE;
+        spfx_effects = realloc(spfx_effects, sizeof(SPFX_Base)*mem);
+      }
+      spfx_base_parse(&spfx_effects[spfx_neffects-1], node);
+    }
+  } while(xml_nextNode(node));
+
+  /* Shrink back to minimum - shouldn't change ever. */
+  spfx_effects = realloc(spfx_effects, sizeof(SPFX_Base) * spfx_neffects);
+
+  /* Now initialize force feedback. */
   spfx_hapticInit();
 
   return 0;