diff --git a/hw/xbox/nv2a/nv2a.c b/hw/xbox/nv2a/nv2a.c
index aa75cdaa38..d862bd951c 100644
--- a/hw/xbox/nv2a/nv2a.c
+++ b/hw/xbox/nv2a/nv2a.c
@@ -487,6 +487,7 @@ static const VMStateDescription vmstate_nv2a = {
         VMSTATE_UINT64(pgraph.dma_vertex_a, NV2AState),
         VMSTATE_UINT64(pgraph.dma_vertex_b, NV2AState),
         VMSTATE_UINT32(pgraph.primitive_mode, NV2AState),
+        VMSTATE_UINT32_ARRAY(pgraph.vertex_state_shader_v0, NV2AState, 4),
         VMSTATE_UINT32_2DARRAY(pgraph.program_data, NV2AState, NV2A_MAX_TRANSFORM_PROGRAM_LENGTH, VSH_TOKEN_SIZE),
         VMSTATE_UINT32_2DARRAY(pgraph.vsh_constants, NV2AState, NV2A_VERTEXSHADER_CONSTANTS, 4),
         VMSTATE_BOOL_ARRAY(pgraph.vsh_constants_dirty, NV2AState, NV2A_VERTEXSHADER_CONSTANTS),
diff --git a/hw/xbox/nv2a/nv2a_int.h b/hw/xbox/nv2a/nv2a_int.h
index a09ed29e0b..c46a42db05 100644
--- a/hw/xbox/nv2a/nv2a_int.h
+++ b/hw/xbox/nv2a/nv2a_int.h
@@ -331,6 +331,7 @@ typedef struct PGRAPHState {
 
     bool enable_vertex_program_write;
 
+    uint32_t vertex_state_shader_v0[4];
     uint32_t program_data[NV2A_MAX_TRANSFORM_PROGRAM_LENGTH][VSH_TOKEN_SIZE];
     bool program_data_dirty;
 
diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h
index 40e324f558..252b89c83e 100644
--- a/hw/xbox/nv2a/nv2a_regs.h
+++ b/hw/xbox/nv2a/nv2a_regs.h
@@ -1241,6 +1241,8 @@
 #   define NV097_SET_SHADER_STAGE_PROGRAM                     0x00001E70
 #   define NV097_SET_DOT_RGBMAPPING                           0X00001E74
 #   define NV097_SET_SHADER_OTHER_STAGE_INPUT                 0x00001E78
+#   define NV097_SET_TRANSFORM_DATA                           0x00001E80
+#   define NV097_LAUNCH_TRANSFORM_PROGRAM                     0x00001E90
 #   define NV097_SET_TRANSFORM_EXECUTION_MODE                 0x00001E94
 #       define NV097_SET_TRANSFORM_EXECUTION_MODE_MODE            0x00000003
 #       define NV097_SET_TRANSFORM_EXECUTION_MODE_RANGE_MODE      0xFFFFFFFC
diff --git a/hw/xbox/nv2a/pgraph.c b/hw/xbox/nv2a/pgraph.c
index 4fd01b781c..9f7ccfb33d 100644
--- a/hw/xbox/nv2a/pgraph.c
+++ b/hw/xbox/nv2a/pgraph.c
@@ -21,6 +21,7 @@
 
 #include "nv2a_int.h"
 
+#include "nv2a_vsh_emulator.h"
 #include "s3tc.h"
 #include "ui/xemu-settings.h"
 #include "qemu/fast-hash.h"
@@ -3689,6 +3690,33 @@ DEF_METHOD(NV097, SET_SHADER_OTHER_STAGE_INPUT)
              GET_MASK(parameter, 0xFFFF000));
 }
 
+DEF_METHOD_INC(NV097, SET_TRANSFORM_DATA)
+{
+    int slot = (method - NV097_SET_TRANSFORM_DATA) / 4;
+    pg->vertex_state_shader_v0[slot] = parameter;
+}
+
+DEF_METHOD(NV097, LAUNCH_TRANSFORM_PROGRAM)
+{
+    unsigned int program_start = parameter;
+    assert(program_start < NV2A_MAX_TRANSFORM_PROGRAM_LENGTH);
+    Nv2aVshProgram program;
+    Nv2aVshParseResult result = nv2a_vsh_parse_program(
+            &program,
+            pg->program_data[program_start],
+            NV2A_MAX_TRANSFORM_PROGRAM_LENGTH - program_start);
+    assert(result == NV2AVPR_SUCCESS);
+
+    Nv2aVshCPUXVSSExecutionState state_linkage;
+    Nv2aVshExecutionState state = nv2a_vsh_emu_initialize_xss_execution_state(
+            &state_linkage, (float*)pg->vsh_constants);
+    memcpy(state_linkage.input_regs, pg->vertex_state_shader_v0, sizeof(pg->vertex_state_shader_v0));
+
+    nv2a_vsh_emu_execute_track_context_writes(&state, &program, pg->vsh_constants_dirty);
+
+    nv2a_vsh_program_destroy(&program);
+}
+
 DEF_METHOD(NV097, SET_TRANSFORM_EXECUTION_MODE)
 {
     SET_MASK(pg->regs[NV_PGRAPH_CSV0_D], NV_PGRAPH_CSV0_D_MODE,
diff --git a/hw/xbox/nv2a/pgraph_methods.h b/hw/xbox/nv2a/pgraph_methods.h
index 16c5b2cca2..0d0bb1d7e9 100644
--- a/hw/xbox/nv2a/pgraph_methods.h
+++ b/hw/xbox/nv2a/pgraph_methods.h
@@ -182,6 +182,8 @@ DEF_METHOD(NV097, SET_SHADOW_DEPTH_FUNC)
 DEF_METHOD(NV097, SET_SHADER_STAGE_PROGRAM)
 DEF_METHOD(NV097, SET_DOT_RGBMAPPING)
 DEF_METHOD(NV097, SET_SHADER_OTHER_STAGE_INPUT)
+DEF_METHOD_RANGE(NV097, SET_TRANSFORM_DATA, 4)
+DEF_METHOD(NV097, LAUNCH_TRANSFORM_PROGRAM)
 DEF_METHOD(NV097, SET_TRANSFORM_EXECUTION_MODE)
 DEF_METHOD(NV097, SET_TRANSFORM_PROGRAM_CXT_WRITE_EN)
 DEF_METHOD(NV097, SET_TRANSFORM_PROGRAM_LOAD)
diff --git a/hw/xbox/nv2a/thirdparty/nv2a_vsh_cpu b/hw/xbox/nv2a/thirdparty/nv2a_vsh_cpu
index 89e6ec8526..ead0af5dee 160000
--- a/hw/xbox/nv2a/thirdparty/nv2a_vsh_cpu
+++ b/hw/xbox/nv2a/thirdparty/nv2a_vsh_cpu
@@ -1 +1 @@
-Subproject commit 89e6ec852601b77f3c097014af4d29720ab59721
+Subproject commit ead0af5dee49e408c005151393f8a7dbcd27026c