diff --git a/libs/vkd3d-shader/fx.c b/libs/vkd3d-shader/fx.c
index 4fbce3937070aac397905acbd00fd7ca7d5b8d31..914c365b8f643660988e7e30a45a8813c0714806 100644
--- a/libs/vkd3d-shader/fx.c
+++ b/libs/vkd3d-shader/fx.c
@@ -25,6 +25,14 @@ static inline size_t put_u32_unaligned(struct vkd3d_bytecode_buffer *buffer, uin
     return bytecode_put_bytes_unaligned(buffer, &value, sizeof(value));
 }
 
+enum fx_2_type_constants
+{
+    /* Assignment types */
+    FX_2_ASSIGNMENT_CODE_BLOB = 0x0,
+    FX_2_ASSIGNMENT_PARAMETER = 0x1,
+    FX_2_ASSIGNMENT_ARRAY_SELECTOR = 0x2,
+};
+
 enum state_property_component_type
 {
     FX_BOOL,
@@ -281,6 +289,15 @@ static void set_status(struct fx_write_context *fx, int status)
         fx->status = status;
 }
 
+static void fx_print_string(struct vkd3d_string_buffer *buffer, const char *prefix,
+        const char *s, size_t len)
+{
+    if (len)
+        --len; /* Trim terminating null. */
+    vkd3d_string_buffer_printf(buffer, "%s", prefix);
+    vkd3d_string_buffer_print_string_escaped(buffer, s, len);
+}
+
 static uint32_t write_string(const char *string, struct fx_write_context *fx)
 {
     return fx->ops->write_string(string, fx);
@@ -725,6 +742,28 @@ static const struct rhs_named_value fx_2_lighttype_values[] =
     { NULL }
 };
 
+static const struct rhs_named_value fx_2_address_values[] =
+{
+    { "WRAP", 1 },
+    { "MIRROR", 2 },
+    { "CLAMP", 3 },
+    { "BORDER", 4 },
+    { "MIRROR_ONCE", 5 },
+    { NULL }
+};
+
+static const struct rhs_named_value fx_2_filter_values[] =
+{
+    { "NONE", 0 },
+    { "POINT", 1 },
+    { "LINEAR", 2 },
+    { "ANISOTROPIC", 3 },
+    { "PYRAMIDALQUAD", 6 },
+    { "GAUSSIANQUAD", 7 },
+    { "CONVOLUTIONMONO", 8 },
+    { NULL }
+};
+
 static const struct fx_2_state
 {
     const char *name;
@@ -895,6 +934,38 @@ fx_2_states[] =
 
     { "VertexShader",      HLSL_CLASS_SCALAR, FX_VERTEXSHADER, 1, 1, 146 },
     { "PixelShader",       HLSL_CLASS_SCALAR, FX_PIXELSHADER,  1, 1, 147 },
+
+    { "VertexShaderConstantF", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 148 },
+    { "VertexShaderConstantB", HLSL_CLASS_SCALAR, FX_BOOL,  1, ~0u-1, 149 },
+    { "VertexShaderConstantI", HLSL_CLASS_SCALAR, FX_UINT,  1, ~0u-1, 150 },
+    { "VertexShaderConstant",  HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 151 },
+    { "VertexShaderConstant1", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 152 },
+    { "VertexShaderConstant2", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 153 },
+    { "VertexShaderConstant3", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 154 },
+    { "VertexShaderConstant4", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 155 },
+
+    { "PixelShaderConstantF", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 156 },
+    { "PixelShaderConstantB", HLSL_CLASS_SCALAR, FX_BOOL,  1, ~0u-1, 157 },
+    { "PixelShaderConstantI", HLSL_CLASS_SCALAR, FX_UINT,  1, ~0u-1, 158 },
+    { "PixelShaderConstant",  HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 159 },
+    { "PixelShaderConstant1", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 160 },
+    { "PixelShaderConstant2", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 161 },
+    { "PixelShaderConstant3", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 162 },
+    { "PixelShaderConstant4", HLSL_CLASS_SCALAR, FX_FLOAT, 1, ~0u-1, 163 },
+
+    { "Texture",           HLSL_CLASS_SCALAR, FX_TEXTURE, 1, 1, 164 },
+    { "AddressU",          HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 165, fx_2_address_values },
+    { "AddressV",          HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 166, fx_2_address_values },
+    { "AddressW",          HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 167, fx_2_address_values },
+    { "BorderColor",       HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 168 },
+    { "MagFilter",         HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 169, fx_2_filter_values },
+    { "MinFilter",         HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 170, fx_2_filter_values },
+    { "MipFilter",         HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 171, fx_2_filter_values },
+    { "MipMapLodBias",     HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 172 },
+    { "MaxMipLevel",       HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 173 },
+    { "MaxAnisotropy",     HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 174 },
+    { "SRBTexture",        HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 175 },
+    { "ElementIndex",      HLSL_CLASS_SCALAR, FX_UINT,    1, 1, 176 },
 };
 
 static void write_fx_2_pass(struct hlsl_ir_var *var, struct fx_write_context *fx)
@@ -3452,13 +3523,12 @@ static void parse_fx_print_indent(struct fx_parser *parser)
     vkd3d_string_buffer_printf(&parser->buffer, "%*s", 4 * parser->indent, "");
 }
 
-static const char *fx_2_get_string(struct fx_parser *parser, uint32_t offset)
+static const char *fx_2_get_string(struct fx_parser *parser, uint32_t offset, uint32_t *size)
 {
     const char *ptr;
-    uint32_t size;
 
-    fx_parser_read_unstructured(parser, &size, offset, sizeof(size));
-    ptr = fx_parser_get_unstructured_ptr(parser, offset + 4, size);
+    fx_parser_read_unstructured(parser, size, offset, sizeof(*size));
+    ptr = fx_parser_get_unstructured_ptr(parser, offset + 4, *size);
 
     if (!ptr)
     {
@@ -3624,13 +3694,14 @@ static void fx_parse_fx_2_parameter(struct fx_parser *parser, uint32_t offset)
         uint32_t element_count;
     } var;
     const char *name;
+    uint32_t size;
 
     fx_parser_read_unstructured(parser, &var, offset, sizeof(var));
 
     fx_parse_fx_2_type(parser, offset);
 
-    name = fx_2_get_string(parser, var.name);
-    vkd3d_string_buffer_printf(&parser->buffer, " %s", name);
+    name = fx_2_get_string(parser, var.name, &size);
+    fx_print_string(&parser->buffer, " ", name, size);
     if (var.element_count)
         vkd3d_string_buffer_printf(&parser->buffer, "[%u]", var.element_count);
 }
@@ -3644,6 +3715,40 @@ static bool is_fx_2_sampler(uint32_t type)
             || type == D3DXPT_SAMPLERCUBE;
 }
 
+static void fx_parse_fx_2_assignment(struct fx_parser *parser, const struct fx_assignment *entry);
+
+static void parse_fx_2_sampler(struct fx_parser *parser, uint32_t element_count,
+        uint32_t offset)
+{
+    struct fx_assignment entry;
+    uint32_t count;
+
+    element_count = max(element_count, 1);
+
+    vkd3d_string_buffer_printf(&parser->buffer, "\n");
+    for (uint32_t i = 0; i < element_count; ++i)
+    {
+        fx_parser_read_unstructured(parser, &count, offset, sizeof(count));
+        offset += sizeof(count);
+
+        parse_fx_start_indent(parser);
+        parse_fx_print_indent(parser);
+        vkd3d_string_buffer_printf(&parser->buffer, "{\n");
+        parse_fx_start_indent(parser);
+        for (uint32_t j = 0; j < count; ++j, offset += sizeof(entry))
+        {
+            fx_parser_read_unstructured(parser, &entry, offset, sizeof(entry));
+
+            parse_fx_print_indent(parser);
+            fx_parse_fx_2_assignment(parser, &entry);
+        }
+        parse_fx_end_indent(parser);
+        parse_fx_print_indent(parser);
+        vkd3d_string_buffer_printf(&parser->buffer, "},\n");
+        parse_fx_end_indent(parser);
+    }
+}
+
 static void fx_parse_fx_2_initial_value(struct fx_parser *parser, uint32_t param, uint32_t value)
 {
     struct fx_2_var
@@ -3672,8 +3777,7 @@ static void fx_parse_fx_2_initial_value(struct fx_parser *parser, uint32_t param
     if (var.class == D3DXPC_OBJECT)
     {
         if (is_fx_2_sampler(var.type))
-            fx_parser_error(parser, VKD3D_SHADER_ERROR_FX_NOT_IMPLEMENTED,
-                    "Parsing sampler initializers is not supported.");
+            parse_fx_2_sampler(parser, var.element_count, value);
         else
             parse_fx_2_object_value(parser, var.element_count, var.type, value);
     }
@@ -3714,24 +3818,22 @@ static void fx_parse_fx_2_annotations(struct fx_parser *parser, uint32_t count)
     vkd3d_string_buffer_printf(&parser->buffer, ">");
 }
 
-static void fx_parse_fx_2_assignment(struct fx_parser *parser)
+static void fx_parse_fx_2_assignment(struct fx_parser *parser, const struct fx_assignment *entry)
 {
     const struct rhs_named_value *named_value = NULL;
     const struct fx_2_state *state = NULL;
-    struct fx_assignment entry;
 
-    fx_parser_read_u32s(parser, &entry, sizeof(entry));
-    if (entry.id <= ARRAY_SIZE(fx_2_states))
+    if (entry->id <= ARRAY_SIZE(fx_2_states))
     {
-        state = &fx_2_states[entry.id];
+        state = &fx_2_states[entry->id];
 
         vkd3d_string_buffer_printf(&parser->buffer, "%s", state->name);
         if (state->array_size > 1)
-            vkd3d_string_buffer_printf(&parser->buffer, "[%u]", entry.lhs_index);
+            vkd3d_string_buffer_printf(&parser->buffer, "[%u]", entry->lhs_index);
     }
     else
     {
-        vkd3d_string_buffer_printf(&parser->buffer, "<unrecognized state %u>", entry.id);
+        vkd3d_string_buffer_printf(&parser->buffer, "<unrecognized state %u>", entry->id);
     }
     vkd3d_string_buffer_printf(&parser->buffer, " = ");
 
@@ -3740,7 +3842,7 @@ static void fx_parse_fx_2_assignment(struct fx_parser *parser)
         const struct rhs_named_value *ptr = state->values;
         uint32_t value;
 
-        fx_parser_read_unstructured(parser, &value, entry.value, sizeof(value));
+        fx_parser_read_unstructured(parser, &value, entry->value, sizeof(value));
 
         while (ptr->name)
         {
@@ -3757,13 +3859,27 @@ static void fx_parse_fx_2_assignment(struct fx_parser *parser)
     {
         vkd3d_string_buffer_printf(&parser->buffer, "%s /* %u */", named_value->name, named_value->value);
     }
-    else if (state && (state->type == FX_UINT || state->type == FX_FLOAT))
+    else if (state)
     {
-        uint32_t offset = entry.type;
-        unsigned int size;
+        if (state->type == FX_UINT || state->type == FX_FLOAT)
+        {
+            uint32_t offset = entry->type;
+            unsigned int size;
 
-        size = fx_get_fx_2_type_size(parser, &offset);
-        parse_fx_2_numeric_value(parser, entry.value, size, entry.type);
+            size = fx_get_fx_2_type_size(parser, &offset);
+            parse_fx_2_numeric_value(parser, entry->value, size, entry->type);
+        }
+        else if (state->type == FX_VERTEXSHADER || state->type == FX_PIXELSHADER)
+        {
+            uint32_t id;
+
+            fx_parser_read_unstructured(parser, &id, entry->value, sizeof(id));
+            vkd3d_string_buffer_printf(&parser->buffer, "<object id %u>", id);
+        }
+        else
+        {
+            vkd3d_string_buffer_printf(&parser->buffer, "<ignored>");
+        }
     }
     else
     {
@@ -3787,16 +3903,17 @@ static void fx_parse_fx_2_technique(struct fx_parser *parser)
         uint32_t assignment_count;
     } pass;
     const char *name;
+    uint32_t size;
 
     if (parser->failed)
         return;
 
     fx_parser_read_u32s(parser, &technique, sizeof(technique));
 
-    name = fx_2_get_string(parser, technique.name);
+    name = fx_2_get_string(parser, technique.name, &size);
 
     parse_fx_print_indent(parser);
-    vkd3d_string_buffer_printf(&parser->buffer, "technique %s", name);
+    fx_print_string(&parser->buffer, "technique ", name, size);
     fx_parse_fx_2_annotations(parser, technique.annotation_count);
 
     vkd3d_string_buffer_printf(&parser->buffer, "\n");
@@ -3807,10 +3924,10 @@ static void fx_parse_fx_2_technique(struct fx_parser *parser)
     for (uint32_t i = 0; i < technique.pass_count; ++i)
     {
         fx_parser_read_u32s(parser, &pass, sizeof(pass));
-        name = fx_2_get_string(parser, pass.name);
+        name = fx_2_get_string(parser, pass.name, &size);
 
         parse_fx_print_indent(parser);
-        vkd3d_string_buffer_printf(&parser->buffer, "pass %s", name);
+        fx_print_string(&parser->buffer, "pass ", name, size);
         fx_parse_fx_2_annotations(parser, pass.annotation_count);
 
         vkd3d_string_buffer_printf(&parser->buffer, "\n");
@@ -3820,8 +3937,11 @@ static void fx_parse_fx_2_technique(struct fx_parser *parser)
         parse_fx_start_indent(parser);
         for (uint32_t j = 0; j < pass.assignment_count; ++j)
         {
+            struct fx_assignment entry;
+
             parse_fx_print_indent(parser);
-            fx_parse_fx_2_assignment(parser);
+            fx_parser_read_u32s(parser, &entry, sizeof(entry));
+            fx_parse_fx_2_assignment(parser, &entry);
         }
         parse_fx_end_indent(parser);
 
@@ -3858,10 +3978,62 @@ static void fx_2_parse_parameters(struct fx_parser *parser, uint32_t count)
         vkd3d_string_buffer_printf(&parser->buffer, "\n");
 }
 
+static void fx_parse_shader_blob(struct fx_parser *parser, enum vkd3d_shader_source_type source_type,
+        const void *data, uint32_t data_size)
+{
+    struct vkd3d_shader_compile_info info = { 0 };
+    struct vkd3d_shader_code output;
+    const char *p, *q, *end;
+    int ret;
+
+    static const struct vkd3d_shader_compile_option options[] =
+    {
+        {VKD3D_SHADER_COMPILE_OPTION_API_VERSION, VKD3D_SHADER_API_VERSION_1_15},
+    };
+
+    info.type = VKD3D_SHADER_STRUCTURE_TYPE_COMPILE_INFO;
+    info.source.code = data;
+    info.source.size = data_size;
+    info.source_type = source_type;
+    info.target_type = VKD3D_SHADER_TARGET_D3D_ASM;
+    info.options = options;
+    info.option_count = ARRAY_SIZE(options);
+    info.log_level = VKD3D_SHADER_LOG_INFO;
+
+    if ((ret = vkd3d_shader_compile(&info, &output, NULL)) < 0)
+    {
+        fx_parser_error(parser, VKD3D_SHADER_ERROR_FX_INVALID_DATA,
+                "Failed to disassemble shader blob.");
+        return;
+    }
+    parse_fx_print_indent(parser);
+    vkd3d_string_buffer_printf(&parser->buffer, "asm {\n");
+
+    parse_fx_start_indent(parser);
+
+    end = (const char *)output.code + output.size;
+    for (p = output.code; p < end; p = q)
+    {
+        if (!(q = memchr(p, '\n', end - p)))
+            q = end;
+        else
+            ++q;
+
+        parse_fx_print_indent(parser);
+        vkd3d_string_buffer_printf(&parser->buffer, "%.*s", (int)(q - p), p);
+    }
+
+    parse_fx_end_indent(parser);
+    parse_fx_print_indent(parser);
+    vkd3d_string_buffer_printf(&parser->buffer, "}");
+
+    vkd3d_shader_free_shader_code(&output);
+}
+
 static void fx_parse_fx_2_data_blob(struct fx_parser *parser)
 {
     uint32_t id, size;
-    const char *str;
+    const void *data;
 
     id = fx_parser_read_u32(parser);
     size = fx_parser_read_u32(parser);
@@ -3882,13 +4054,24 @@ static void fx_parse_fx_2_data_blob(struct fx_parser *parser)
             case D3DXPT_VERTEXSHADER:
                 vkd3d_string_buffer_printf(&parser->buffer, "%s object %u size %u bytes%s\n",
                         fx_2_types[type], id, size, size ? ":" : ",");
-                if (size && type == D3DXPT_STRING)
+
+                if (size)
                 {
-                    parse_fx_start_indent(parser);
-                    parse_fx_print_indent(parser);
-                    str = fx_parser_get_ptr(parser, size);
-                    vkd3d_string_buffer_printf(&parser->buffer, "\"%.*s\"\n", size, str);
-                    parse_fx_end_indent(parser);
+                    data = fx_parser_get_ptr(parser, size);
+
+                    if (type == D3DXPT_STRING)
+                    {
+                        parse_fx_start_indent(parser);
+                        parse_fx_print_indent(parser);
+                        fx_print_string(&parser->buffer, "\"", (const char *)data, size);
+                        vkd3d_string_buffer_printf(&parser->buffer, "\"");
+                        parse_fx_end_indent(parser);
+                    }
+                    else if (type == D3DXPT_PIXELSHADER || type == D3DXPT_VERTEXSHADER)
+                    {
+                        fx_parse_shader_blob(parser, VKD3D_SHADER_SOURCE_D3D_BYTECODE, data, size);
+                    }
+                    vkd3d_string_buffer_printf(&parser->buffer, "\n");
                 }
                 break;
             default:
@@ -3903,9 +4086,117 @@ static void fx_parse_fx_2_data_blob(struct fx_parser *parser)
     fx_parser_skip(parser, align(size, 4));
 }
 
+static void fx_dump_blob(struct fx_parser *parser, const void *blob, uint32_t size)
+{
+    const uint32_t *data = blob;
+    unsigned int i, j, n;
+
+    size /= sizeof(*data);
+    i = 0;
+    while (i < size)
+    {
+        parse_fx_print_indent(parser);
+        n = min(size - i, 8);
+        for (j = 0; j < n; ++j)
+            vkd3d_string_buffer_printf(&parser->buffer, "0x%08x,", data[i + j]);
+        i += n;
+        vkd3d_string_buffer_printf(&parser->buffer, "\n");
+    }
+}
+
+static void fx_parse_fx_2_array_selector(struct fx_parser *parser, uint32_t size)
+{
+    const uint8_t *end = parser->ptr + size;
+    uint32_t name_size, blob_size = 0;
+    const void *blob = NULL;
+    const char *name;
+
+    name_size = fx_parser_read_u32(parser);
+    name = fx_parser_get_ptr(parser, name_size);
+    fx_parser_skip(parser, name_size);
+
+    if (!name || (uint8_t *)name >= end)
+        fx_parser_error(parser, VKD3D_SHADER_ERROR_FX_INVALID_DATA,
+                "Malformed name entry in the array selector.");
+
+    if (parser->ptr <= end)
+    {
+        blob_size = end - parser->ptr;
+        blob = fx_parser_get_ptr(parser, blob_size);
+        fx_parser_skip(parser, blob_size);
+    }
+    else
+    {
+        fx_parser_error(parser, VKD3D_SHADER_ERROR_FX_INVALID_DATA,
+                "Malformed blob entry in the array selector.");
+    }
+
+    if (name)
+    {
+        fx_print_string(&parser->buffer, "array \"", name, name_size);
+        vkd3d_string_buffer_printf(&parser->buffer, "\"\n");
+    }
+    if (blob)
+    {
+        parse_fx_print_indent(parser);
+        vkd3d_string_buffer_printf(&parser->buffer, "selector blob size %u\n", blob_size);
+        fx_dump_blob(parser, blob, blob_size);
+    }
+}
+
+static void fx_parse_fx_2_complex_state(struct fx_parser *parser)
+{
+    struct
+    {
+        uint32_t technique;
+        uint32_t index;
+        uint32_t element;
+        uint32_t state;
+        uint32_t assignment_type;
+    } state;
+    const char *data;
+    uint32_t size;
+
+    fx_parser_read_u32s(parser, &state, sizeof(state));
+
+    if (state.technique == ~0u)
+    {
+        vkd3d_string_buffer_printf(&parser->buffer, "parameter %u[%u], state %u =\n",
+                state.index, state.element, state.state);
+    }
+    else
+    {
+        vkd3d_string_buffer_printf(&parser->buffer, "technique %u, pass %u, state %u =\n",
+                state.technique, state.index, state.state);
+    }
+
+    size = fx_parser_read_u32(parser);
+
+    parse_fx_print_indent(parser);
+
+    if (state.assignment_type == FX_2_ASSIGNMENT_PARAMETER)
+    {
+        data = fx_parser_get_ptr(parser, size);
+        fx_print_string(&parser->buffer, "parameter \"", data, size);
+        vkd3d_string_buffer_printf(&parser->buffer, "\"\n");
+        fx_parser_skip(parser, align(size, 4));
+    }
+    else if (state.assignment_type == FX_2_ASSIGNMENT_ARRAY_SELECTOR)
+    {
+        fx_parse_fx_2_array_selector(parser, size);
+    }
+    else
+    {
+        vkd3d_string_buffer_printf(&parser->buffer, "blob size %u\n", size);
+        data = fx_parser_get_ptr(parser, size);
+        fx_dump_blob(parser, data, size);
+        fx_parser_skip(parser, align(size, 4));
+    }
+}
+
 static void fx_2_parse(struct fx_parser *parser)
 {
-    uint32_t i, size, parameter_count, technique_count, blob_count;
+    uint32_t i, size, parameter_count, technique_count, blob_count, state_count;
 
     fx_parser_skip(parser, sizeof(uint32_t)); /* Version */
     size = fx_parser_read_u32(parser);
@@ -3931,13 +4222,20 @@ static void fx_2_parse(struct fx_parser *parser)
         fx_parse_fx_2_technique(parser);
 
     blob_count = fx_parser_read_u32(parser);
-    fx_parser_read_u32(parser); /* Resource count */
+    state_count = fx_parser_read_u32(parser);
 
     vkd3d_string_buffer_printf(&parser->buffer, "object data {\n");
     parse_fx_start_indent(parser);
     for (i = 0; i < blob_count; ++i)
         fx_parse_fx_2_data_blob(parser);
     parse_fx_end_indent(parser);
+    vkd3d_string_buffer_printf(&parser->buffer, "}\n\n");
+
+    vkd3d_string_buffer_printf(&parser->buffer, "state data {\n");
+    parse_fx_start_indent(parser);
+    for (i = 0; i < state_count; ++i)
+        fx_parse_fx_2_complex_state(parser);
+    parse_fx_end_indent(parser);
     vkd3d_string_buffer_printf(&parser->buffer, "}\n");
 }
 
@@ -4157,17 +4455,8 @@ static void fx_parse_buffers(struct fx_parser *parser)
 
 static void fx_4_parse_shader_blob(struct fx_parser *parser, unsigned int object_type, const struct fx_5_shader *shader)
 {
-    struct vkd3d_shader_compile_info info = { 0 };
-    struct vkd3d_shader_code output;
     const void *data = NULL;
-    const char *p, *q, *end;
     uint32_t data_size;
-    int ret;
-
-    static const struct vkd3d_shader_compile_option options[] =
-    {
-        {VKD3D_SHADER_COMPILE_OPTION_API_VERSION, VKD3D_SHADER_API_VERSION_1_15},
-    };
 
     if (!shader->offset)
     {
@@ -4183,42 +4472,8 @@ static void fx_4_parse_shader_blob(struct fx_parser *parser, unsigned int object
     if (!data)
         return;
 
-    info.type = VKD3D_SHADER_STRUCTURE_TYPE_COMPILE_INFO;
-    info.source.code = data;
-    info.source.size = data_size;
-    info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF;
-    info.target_type = VKD3D_SHADER_TARGET_D3D_ASM;
-    info.options = options;
-    info.option_count = ARRAY_SIZE(options);
-    info.log_level = VKD3D_SHADER_LOG_INFO;
-
-    if ((ret = vkd3d_shader_compile(&info, &output, NULL)) < 0)
-    {
-        fx_parser_error(parser, VKD3D_SHADER_ERROR_FX_INVALID_DATA,
-                "Failed to disassemble shader blob.");
-        return;
-    }
-    parse_fx_print_indent(parser);
-    vkd3d_string_buffer_printf(&parser->buffer, "asm {\n");
-
-    parse_fx_start_indent(parser);
-
-    end = (const char *)output.code + output.size;
-    for (p = output.code; p < end; p = q)
-    {
-        if (!(q = memchr(p, '\n', end - p)))
-            q = end;
-        else
-            ++q;
-
-        parse_fx_print_indent(parser);
-        vkd3d_string_buffer_printf(&parser->buffer, "%.*s", (int)(q - p), p);
-    }
-
-    parse_fx_end_indent(parser);
+    fx_parse_shader_blob(parser, VKD3D_SHADER_SOURCE_DXBC_TPF, data, data_size);
 
-    parse_fx_print_indent(parser);
-    vkd3d_string_buffer_printf(&parser->buffer, "}");
     if (object_type == FX_4_OBJECT_TYPE_GEOMETRY_SHADER_SO && shader->sodecl[0])
     {
         vkd3d_string_buffer_printf(&parser->buffer, "\n/* Stream output declaration: \"%s\" */",
@@ -4235,8 +4490,6 @@ static void fx_4_parse_shader_blob(struct fx_parser *parser, unsigned int object
         if (shader->sodecl_count)
             vkd3d_string_buffer_printf(&parser->buffer, "\n/* Rasterized stream %u */", shader->rast_stream);
     }
-
-    vkd3d_shader_free_shader_code(&output);
 }
 
 static void fx_4_parse_shader_initializer(struct fx_parser *parser, unsigned int object_type)
diff --git a/libs/vkd3d-shader/spirv.c b/libs/vkd3d-shader/spirv.c
index 455d00ed6c730b307dbe8765dc662a0bd082649b..6493a6e10d027ddd7f7d4421b59e0298d570cd07 100644
--- a/libs/vkd3d-shader/spirv.c
+++ b/libs/vkd3d-shader/spirv.c
@@ -348,49 +348,12 @@ static void spirv_parser_print_instruction_offset(struct spirv_parser *parser,
             parser->colours.comment, offset * sizeof(uint32_t), parser->colours.reset, suffix);
 }
 
-static char get_escape_char(char c)
-{
-    switch (c)
-    {
-        case '"':
-        case '\\':
-            return c;
-        case '\t':
-            return 't';
-        case '\n':
-            return 'n';
-        case '\v':
-            return 'v';
-        case '\f':
-            return 'f';
-        case '\r':
-            return 'r';
-        default:
-            return 0;
-    }
-}
-
 static void spirv_parser_print_string_literal(struct spirv_parser *parser, struct vkd3d_string_buffer *buffer,
         const char *prefix, const char *s, size_t len, const char *suffix)
 {
-    size_t start, i;
-    char c;
-
     vkd3d_string_buffer_printf(buffer, "%s\"%s", prefix, parser->colours.literal);
-    for (i = 0, start = 0; i < len; ++i)
-    {
-        if ((c = get_escape_char(s[i])))
-        {
-            vkd3d_string_buffer_printf(buffer, "%.*s\\%c", (int)(i - start), &s[start], c);
-            start = i + 1;
-        }
-        else if (!isprint(s[i]))
-        {
-            vkd3d_string_buffer_printf(buffer, "%.*s\\%03o", (int)(i - start), &s[start], (uint8_t)s[i]);
-            start = i + 1;
-        }
-    }
-    vkd3d_string_buffer_printf(buffer, "%.*s%s\"%s", (int)(len - start), &s[start], parser->colours.reset, suffix);
+    vkd3d_string_buffer_print_string_escaped(buffer, s, len);
+    vkd3d_string_buffer_printf(buffer, "%s\"%s", parser->colours.reset, suffix);
 }
 
 static const struct spirv_parser_enumerant *spirv_parser_get_enumerant(
diff --git a/libs/vkd3d-shader/vkd3d_shader_main.c b/libs/vkd3d-shader/vkd3d_shader_main.c
index a2c5d9fb237c5cc54a4158b3ad48e6dd92b83315..fdc6029171622cad52cd027b35d75cb2cb59e3b0 100644
--- a/libs/vkd3d-shader/vkd3d_shader_main.c
+++ b/libs/vkd3d-shader/vkd3d_shader_main.c
@@ -163,6 +163,60 @@ int vkd3d_string_buffer_print_f64(struct vkd3d_string_buffer *buffer, double d)
     return ret;
 }
 
+static char get_escape_char(char c)
+{
+    switch (c)
+    {
+        case '"':
+        case '\\':
+            return c;
+        case '\t':
+            return 't';
+        case '\n':
+            return 'n';
+        case '\v':
+            return 'v';
+        case '\f':
+            return 'f';
+        case '\r':
+            return 'r';
+        default:
+            return 0;
+    }
+}
+
+int vkd3d_string_buffer_print_string_escaped(struct vkd3d_string_buffer *buffer, const char *s, size_t len)
+{
+    size_t content_size, start, i;
+    int ret;
+    char c;
+
+    content_size = buffer->content_size;
+    for (i = 0, start = 0; i < len; ++i)
+    {
+        if ((c = get_escape_char(s[i])))
+        {
+            if ((ret = vkd3d_string_buffer_printf(buffer, "%.*s\\%c", (int)(i - start), &s[start], c)) < 0)
+                goto fail;
+            start = i + 1;
+        }
+        else if (!isprint(s[i]))
+        {
+            if ((ret = vkd3d_string_buffer_printf(buffer, "%.*s\\%03o",
+                    (int)(i - start), &s[start], (uint8_t)s[i])) < 0)
+                goto fail;
+            start = i + 1;
+        }
+    }
+    if ((ret = vkd3d_string_buffer_printf(buffer, "%.*s", (int)(len - start), &s[start])) < 0)
+        goto fail;
+    return ret;
+
+fail:
+    buffer->content_size = content_size;
+    return ret;
+}
+
 void vkd3d_string_buffer_trace_(const struct vkd3d_string_buffer *buffer, const char *function)
 {
     vkd3d_shader_trace_text_(buffer->buffer, buffer->content_size, function);
diff --git a/libs/vkd3d-shader/vkd3d_shader_private.h b/libs/vkd3d-shader/vkd3d_shader_private.h
index 53a9f844793183c8a82a8ab02e957c0be2d6991c..b766b0e72880b1af34a37b012b261ceb03edc5eb 100644
--- a/libs/vkd3d-shader/vkd3d_shader_private.h
+++ b/libs/vkd3d-shader/vkd3d_shader_private.h
@@ -1565,6 +1565,7 @@ void vkd3d_string_buffer_clear(struct vkd3d_string_buffer *buffer);
 void vkd3d_string_buffer_truncate(struct vkd3d_string_buffer *buffer, size_t size);
 int vkd3d_string_buffer_print_f32(struct vkd3d_string_buffer *buffer, float f);
 int vkd3d_string_buffer_print_f64(struct vkd3d_string_buffer *buffer, double d);
+int vkd3d_string_buffer_print_string_escaped(struct vkd3d_string_buffer *buffer, const char *s, size_t len);
 int vkd3d_string_buffer_printf(struct vkd3d_string_buffer *buffer, const char *format, ...) VKD3D_PRINTF_FUNC(2, 3);
 void vkd3d_string_buffer_release(struct vkd3d_string_buffer_cache *list, struct vkd3d_string_buffer *buffer);
 #define vkd3d_string_buffer_trace(buffer) \