// mostly stolen from https://code.google.com/p/cxbx/source/browse/trunk/src/CxbxKrnl/EmuD3D8/VertexShader.cpp?r=3 // not *everything* implemented, but enough for GTA #include #include typedef unsigned char byte; typedef unsigned int uint; typedef unsigned short ushort; typedef byte boolean; typedef enum VSH_FIELD_NAME VSH_FIELD_NAME; enum VSH_FIELD_NAME { FLD_ILU = 0, FLD_MAC, FLD_CONST, FLD_V, // Input A FLD_A_NEG, FLD_A_SWZ_X, FLD_A_SWZ_Y, FLD_A_SWZ_Z, FLD_A_SWZ_W, FLD_A_R, FLD_A_MUX, // Input B FLD_B_NEG, FLD_B_SWZ_X, FLD_B_SWZ_Y, FLD_B_SWZ_Z, FLD_B_SWZ_W, FLD_B_R, FLD_B_MUX, // Input C FLD_C_NEG, FLD_C_SWZ_X, FLD_C_SWZ_Y, FLD_C_SWZ_Z, FLD_C_SWZ_W, FLD_C_R_HIGH, FLD_C_R_LOW, FLD_C_MUX, // Output FLD_OUT_MAC_MASK_X, FLD_OUT_MAC_MASK_Y, FLD_OUT_MAC_MASK_Z, FLD_OUT_MAC_MASK_W, FLD_OUT_R, FLD_OUT_ILU_MASK_X, FLD_OUT_ILU_MASK_Y, FLD_OUT_ILU_MASK_Z, FLD_OUT_ILU_MASK_W, FLD_OUT_O_MASK_X, FLD_OUT_O_MASK_Y, FLD_OUT_O_MASK_Z, FLD_OUT_O_MASK_W, FLD_OUT_ORB, FLD_OUT_ADDRESS, FLD_OUT_MUX, // Relative addressing FLD_A0X, // Final instruction FLD_FINAL }; typedef enum VSH_ILU VSH_ILU; enum VSH_ILU { ILU_NOP = 0, ILU_MOV, ILU_RCP, ILU_RCC, ILU_RSQ, ILU_EXP, ILU_LOG, ILU_LIT }; typedef enum VSH_MAC VSH_MAC; enum VSH_MAC { MAC_NOP, MAC_MOV, MAC_MUL, MAC_ADD, MAC_MAD, MAC_DP3, MAC_DPH, MAC_DP4, MAC_DST, MAC_MIN, MAC_MAX, MAC_SLT, MAC_SGE, MAC_ARL }; typedef enum VSH_OUTPUT_MUX VSH_OUTPUT_MUX; enum VSH_OUTPUT_MUX { OMUX_MAC = 0, OMUX_ILU }; typedef enum VSH_OUTPUT_TYPE VSH_OUTPUT_TYPE; enum VSH_OUTPUT_TYPE { OUTPUT_C = 0, OUTPUT_O }; typedef struct VSH_OUTPUT VSH_OUTPUT; struct VSH_OUTPUT { // Output register VSH_OUTPUT_MUX OutputMux; // MAC or ILU used as output VSH_OUTPUT_TYPE OutputType; // C or O boolean OutputMask[4]; short OutputAddress; // MAC output R register boolean MACRMask[4]; boolean MACRAddress; // ILU output R register boolean ILURMask[4]; boolean ILURAddress; }; typedef enum VSH_PARAMETER_TYPE VSH_PARAMETER_TYPE; enum VSH_PARAMETER_TYPE { PARAM_UNKNOWN = 0, PARAM_R, PARAM_V, PARAM_C }; typedef enum VSH_SWIZZLE VSH_SWIZZLE; enum VSH_SWIZZLE { SWIZZLE_X = 0, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_W }; typedef struct VSH_PARAMETER VSH_PARAMETER; struct VSH_PARAMETER { VSH_PARAMETER_TYPE ParameterType; // Parameter type, R, V or C boolean Neg; // TRUE if negated, FALSE if not VSH_SWIZZLE Swizzle[4]; // The four swizzles short Address; // Register address }; typedef struct VSH_SHADER_INSTRUCTION VSH_SHADER_INSTRUCTION; struct VSH_SHADER_INSTRUCTION { VSH_ILU ILU; VSH_MAC MAC; VSH_OUTPUT Output; VSH_PARAMETER A; VSH_PARAMETER B; VSH_PARAMETER C; boolean a0x; }; typedef struct VSH_FIELDMAPPING VSH_FIELDMAPPING; struct VSH_FIELDMAPPING { VSH_FIELD_NAME fieldName; byte subToken; byte startBit; byte bitLength; }; static VSH_FIELDMAPPING fieldMapping[] = { // Field Name DWORD BitPos BitSize { FLD_ILU, 1, 25, 3 }, { FLD_MAC, 1, 21, 4 }, { FLD_CONST, 1, 13, 8 }, { FLD_V, 1, 9, 4 }, // INPUT A { FLD_A_NEG, 1, 8, 1 }, { FLD_A_SWZ_X, 1, 6, 2 }, { FLD_A_SWZ_Y, 1, 4, 2 }, { FLD_A_SWZ_Z, 1, 2, 2 }, { FLD_A_SWZ_W, 1, 0, 2 }, { FLD_A_R, 2, 28, 4 }, { FLD_A_MUX, 2, 26, 2 }, // INPUT B { FLD_B_NEG, 2, 25, 1 }, { FLD_B_SWZ_X, 2, 23, 2 }, { FLD_B_SWZ_Y, 2, 21, 2 }, { FLD_B_SWZ_Z, 2, 19, 2 }, { FLD_B_SWZ_W, 2, 17, 2 }, { FLD_B_R, 2, 13, 4 }, { FLD_B_MUX, 2, 11, 2 }, // INPUT C { FLD_C_NEG, 2, 10, 1 }, { FLD_C_SWZ_X, 2, 8, 2 }, { FLD_C_SWZ_Y, 2, 6, 2 }, { FLD_C_SWZ_Z, 2, 4, 2 }, { FLD_C_SWZ_W, 2, 2, 2 }, { FLD_C_R_HIGH, 2, 0, 2 }, { FLD_C_R_LOW, 3, 30, 2 }, { FLD_C_MUX, 3, 28, 2 }, // Output { FLD_OUT_MAC_MASK_X, 3, 27, 1 }, { FLD_OUT_MAC_MASK_Y, 3, 26, 1 }, { FLD_OUT_MAC_MASK_Z, 3, 25, 1 }, { FLD_OUT_MAC_MASK_W, 3, 24, 1 }, { FLD_OUT_R, 3, 20, 4 }, { FLD_OUT_ILU_MASK_X, 3, 19, 1 }, { FLD_OUT_ILU_MASK_Y, 3, 18, 1 }, { FLD_OUT_ILU_MASK_Z, 3, 17, 1 }, { FLD_OUT_ILU_MASK_W, 3, 16, 1 }, { FLD_OUT_O_MASK_X, 3, 15, 1 }, { FLD_OUT_O_MASK_Y, 3, 14, 1 }, { FLD_OUT_O_MASK_Z, 3, 13, 1 }, { FLD_OUT_O_MASK_W, 3, 12, 1 }, { FLD_OUT_ORB, 3, 11, 1 }, { FLD_OUT_ADDRESS, 3, 3, 8 }, { FLD_OUT_MUX, 3, 2, 1 }, // Other { FLD_A0X, 3, 1, 1 }, { FLD_FINAL, 3, 0, 1 } }; typedef struct VSH_OPCODE_PARAMS VSH_OPCODE_PARAMS; struct VSH_OPCODE_PARAMS { VSH_ILU ILU; VSH_MAC MAC; boolean A; boolean B; boolean C; }; VSH_OPCODE_PARAMS opCodeParams[] = { // ILU OP MAC OP ParamA ParamB ParamC { ILU_MOV, MAC_NOP, 0, 0, 1 }, { ILU_RCP, MAC_NOP, 0, 0, 1 }, { ILU_RCC, MAC_NOP, 0, 0, 1 }, { ILU_RSQ, MAC_NOP, 0, 0, 1 }, { ILU_EXP, MAC_NOP, 0, 0, 1 }, { ILU_LOG, MAC_NOP, 0, 0, 1 }, { ILU_LIT, MAC_NOP, 0, 0, 1 }, { ILU_NOP, MAC_MOV, 1, 0, 0 }, { ILU_NOP, MAC_MUL, 1, 1, 0 }, { ILU_NOP, MAC_ADD, 1, 0, 1 }, { ILU_NOP, MAC_MAD, 1, 1, 1 }, { ILU_NOP, MAC_DP3, 1, 1, 0 }, { ILU_NOP, MAC_DPH, 1, 1, 0 }, { ILU_NOP, MAC_DP4, 1, 1, 0 }, { ILU_NOP, MAC_DST, 1, 1, 0 }, { ILU_NOP, MAC_MIN, 1, 1, 0 }, { ILU_NOP, MAC_MAX, 1, 1, 0 }, { ILU_NOP, MAC_SLT, 1, 1, 0 }, { ILU_NOP, MAC_SGE, 1, 1, 0 }, { ILU_NOP, MAC_ARL, 1, 0, 0 } }; char *MAC_OpCode[] = { "nop", "mov", "mul", "add", "mad", "dp3", "dph", "dp4", "dst", "min", "max", "slt", "sge", "arl", "???", "???" }; char *ILU_OpCode[] = { "nop", "mov", "rcp", "rcc", "rsq", "exp", "log", "lit" }; char *OReg_Name[] = { "oPos", "???", "???", "oD0", "oD1", "oFog", "oPts", "oB0", "oB1", "oT0", "oT1", "oT2", "oT3", "???", "???", "a0.x" }; int IsInUse(const boolean *m) { return (m[0] || m[1] || m[2] || m[3]); } boolean HasMACR(VSH_SHADER_INSTRUCTION *inst) { return IsInUse(inst->Output.MACRMask) && inst->MAC != MAC_NOP; } boolean HasMACO(VSH_SHADER_INSTRUCTION *inst) { return IsInUse(inst->Output.OutputMask) && inst->Output.OutputMux == OMUX_MAC && inst->MAC != MAC_NOP; } boolean HasMACARL(VSH_SHADER_INSTRUCTION *inst) { return /*!IsInUse(inst->Output.OutputMask) && inst->Output.OutputMux == OMUX_MAC &&*/ inst->MAC == MAC_ARL; } boolean HasILUR(VSH_SHADER_INSTRUCTION *inst) { return IsInUse(inst->Output.ILURMask) && inst->ILU != ILU_NOP; } boolean HasILUO(VSH_SHADER_INSTRUCTION *inst) { return IsInUse(inst->Output.OutputMask) && inst->Output.OutputMux == OMUX_ILU && inst->ILU != ILU_NOP; } int VshGetFromToken(uint *token, byte subToken, byte startBit, byte bitLength) { return (token[subToken] >> startBit) & ~(0xFFFFFFFF << bitLength); } short ConvertCRegister(short CReg) { // return CReg; return ((((CReg >> 5) & 7) - 3) * 32) + (CReg & 0x1F); } VSH_OPCODE_PARAMS* VshGetOpCodeParams(VSH_ILU ILU, VSH_MAC MAC) { int i; for(i = 0; i < (sizeof(opCodeParams)/sizeof(opCodeParams[0])); i++) if((ILU != ILU_NOP && ILU == opCodeParams[i].ILU) || (MAC != MAC_NOP && MAC == opCodeParams[i].MAC)) return &opCodeParams[i]; return NULL; } byte VshGetField(uint *token, VSH_FIELD_NAME fieldName) { return (byte)VshGetFromToken(token, fieldMapping[fieldName].subToken, fieldMapping[fieldName].startBit, fieldMapping[fieldName].bitLength); } void disassemble(uint *tok, VSH_SHADER_INSTRUCTION *inst) { // printf("%08X %08X %08X %08X\n", tok[0], tok[1], tok[2], tok[3]); inst->ILU = VshGetField(tok, FLD_ILU); inst->MAC = VshGetField(tok, FLD_MAC); // param A inst->A.ParameterType = VshGetField(tok, FLD_A_MUX); switch(inst->A.ParameterType){ case PARAM_R: inst->A.Address = VshGetField(tok, FLD_A_R); break; case PARAM_V: inst->A.Address = VshGetField(tok, FLD_V); break; case PARAM_C: inst->A.Address = ConvertCRegister(VshGetField(tok, FLD_CONST)); break; default: fprintf(stderr, "unknown param A\n"); break; } inst->A.Neg = VshGetField(tok, FLD_A_NEG); inst->A.Swizzle[0] = VshGetField(tok, FLD_A_SWZ_X); inst->A.Swizzle[1] = VshGetField(tok, FLD_A_SWZ_Y); inst->A.Swizzle[2] = VshGetField(tok, FLD_A_SWZ_Z); inst->A.Swizzle[3] = VshGetField(tok, FLD_A_SWZ_W); // param B inst->B.ParameterType = VshGetField(tok, FLD_B_MUX); switch(inst->B.ParameterType){ case PARAM_R: inst->B.Address = VshGetField(tok, FLD_B_R); break; case PARAM_V: inst->B.Address = VshGetField(tok, FLD_V); break; case PARAM_C: inst->B.Address = ConvertCRegister(VshGetField(tok, FLD_CONST)); break; default: fprintf(stderr, "unknown param B\n"); break; } inst->B.Neg = VshGetField(tok, FLD_B_NEG); inst->B.Swizzle[0] = VshGetField(tok, FLD_B_SWZ_X); inst->B.Swizzle[1] = VshGetField(tok, FLD_B_SWZ_Y); inst->B.Swizzle[2] = VshGetField(tok, FLD_B_SWZ_Z); inst->B.Swizzle[3] = VshGetField(tok, FLD_B_SWZ_W); // param C inst->C.ParameterType = VshGetField(tok, FLD_C_MUX); switch(inst->C.ParameterType){ case PARAM_R: inst->C.Address = VshGetField(tok, FLD_C_R_HIGH) << 2 | VshGetField(tok, FLD_C_R_LOW); break; case PARAM_V: inst->C.Address = VshGetField(tok, FLD_V); break; case PARAM_C: inst->C.Address = ConvertCRegister(VshGetField(tok, FLD_CONST)); break; default: fprintf(stderr, "unknown param C\n"); break; } inst->C.Neg = VshGetField(tok, FLD_C_NEG); inst->C.Swizzle[0] = VshGetField(tok, FLD_C_SWZ_X); inst->C.Swizzle[1] = VshGetField(tok, FLD_C_SWZ_Y); inst->C.Swizzle[2] = VshGetField(tok, FLD_C_SWZ_Z); inst->C.Swizzle[3] = VshGetField(tok, FLD_C_SWZ_W); inst->Output.OutputType = VshGetField(tok, FLD_OUT_ORB); switch(inst->Output.OutputType){ case OUTPUT_C: inst->Output.OutputAddress = ConvertCRegister(VshGetField(tok, FLD_OUT_ADDRESS)); break; case OUTPUT_O: inst->Output.OutputAddress = VshGetField(tok, FLD_OUT_ADDRESS) & 0xF; break; } inst->Output.OutputMux = VshGetField(tok, FLD_OUT_MUX); inst->Output.OutputMask[0] = VshGetField(tok, FLD_OUT_O_MASK_X); inst->Output.OutputMask[1] = VshGetField(tok, FLD_OUT_O_MASK_Y); inst->Output.OutputMask[2] = VshGetField(tok, FLD_OUT_O_MASK_Z); inst->Output.OutputMask[3] = VshGetField(tok, FLD_OUT_O_MASK_W); inst->Output.MACRMask[0] = VshGetField(tok, FLD_OUT_MAC_MASK_X); inst->Output.MACRMask[1] = VshGetField(tok, FLD_OUT_MAC_MASK_Y); inst->Output.MACRMask[2] = VshGetField(tok, FLD_OUT_MAC_MASK_Z); inst->Output.MACRMask[3] = VshGetField(tok, FLD_OUT_MAC_MASK_W); inst->Output.MACRAddress = VshGetField(tok, FLD_OUT_R); inst->Output.ILURMask[0] = VshGetField(tok, FLD_OUT_ILU_MASK_X); inst->Output.ILURMask[1] = VshGetField(tok, FLD_OUT_ILU_MASK_Y); inst->Output.ILURMask[2] = VshGetField(tok, FLD_OUT_ILU_MASK_Z); inst->Output.ILURMask[3] = VshGetField(tok, FLD_OUT_ILU_MASK_W); inst->Output.ILURAddress = VshGetField(tok, FLD_OUT_R); inst->a0x = VshGetField(tok, FLD_A0X); } void outparam(VSH_SHADER_INSTRUCTION *inst, VSH_PARAMETER *p) { int i, j; printf(", %s%c", p->Neg ? "-" : "", "?rvc"[p->ParameterType]); if(p->ParameterType == PARAM_C && inst->a0x) printf("XXX"); else printf("%d", p->Address); if(p->Swizzle[0] == 0 && p->Swizzle[1] == 1 && p->Swizzle[2] == 2 && p->Swizzle[3] == 3) return; printf("."); for(i = 0; i < 4; i++){ printf("%c", "xyzw"[p->Swizzle[i]]); for(j = 0; j < 4; j++) if(p->Swizzle[i] != p->Swizzle[j]) break; if(j == 4) break; } } void outparams(VSH_SHADER_INSTRUCTION *inst, VSH_ILU ILU, VSH_MAC MAC) { VSH_OPCODE_PARAMS *p = VshGetOpCodeParams(ILU, MAC); if(p->A) outparam(inst, &inst->A); if(p->B) outparam(inst, &inst->B); if(p->C) outparam(inst, &inst->C); } char* outmask(boolean *m) { static char out[10]; if(m[0] && m[1] && m[2] && m[3]) return ""; sprintf(out, ".%s%s%s%s", m[0] ? "x" : "", m[1] ? "y" : "", m[2] ? "z" : "", m[3] ? "w" : ""); return out; } int outMAC_R(VSH_SHADER_INSTRUCTION *inst, int isCombined) { if(!HasMACR(inst)) return 0; printf("%s%s", isCombined ? "+" : "", MAC_OpCode[inst->MAC]); printf("\tr%d%s", inst->Output.MACRAddress, outmask(inst->Output.MACRMask)); outparams(inst, ILU_NOP, inst->MAC); printf("\n"); return 1; } int outMAC_O(VSH_SHADER_INSTRUCTION *inst, int isCombined) { if(!HasMACO(inst)) return 0; VSH_OUTPUT *o = &inst->Output; printf("%s%s", isCombined ? "+" : "", MAC_OpCode[inst->MAC]); printf("\t%s%s", o->OutputType == OUTPUT_C ? "cN" : OReg_Name[o->OutputAddress], outmask(o->OutputMask)); outparams(inst, ILU_NOP, inst->MAC); printf("\n"); return 1; } int outMAC_ARL(VSH_SHADER_INSTRUCTION *inst, int isCombined) { if(!HasMACARL(inst)) return 0; VSH_OUTPUT *o = &inst->Output; printf("%s%s", isCombined ? "+" : "", MAC_OpCode[inst->MAC]); printf("\ta0x%d", o->OutputAddress); outparams(inst, ILU_NOP, inst->MAC); printf("\n"); return 1; } int outILU_R(VSH_SHADER_INSTRUCTION *inst, int isCombined) { if(!HasILUR(inst)) return 0; VSH_OUTPUT *o = &inst->Output; printf("%s%s", isCombined ? "+" : "", ILU_OpCode[inst->ILU]); printf("\tr%d%s", isCombined ? 1 : o->ILURAddress, outmask(o->ILURMask)); outparams(inst, inst->ILU, MAC_NOP); printf("\n"); return 1; } int outILU_O(VSH_SHADER_INSTRUCTION *inst, int isCombined) { if(!HasILUO(inst)) return 0; VSH_OUTPUT *o = &inst->Output; printf("%s%s", isCombined ? "+" : "", ILU_OpCode[inst->ILU]); printf("\t%s%s", o->OutputType == OUTPUT_C ? "cN" : OReg_Name[o->OutputAddress], outmask(o->OutputMask)); outparams(inst, inst->ILU, MAC_NOP); printf("\n"); return 1; } void out(VSH_SHADER_INSTRUCTION *inst) { int isCombined = 0; if(outMAC_R(inst, isCombined) && (HasMACO(inst) || HasILUR(inst) || HasILUO(inst))) isCombined = 1; if(outMAC_O(inst, isCombined) && (HasILUR(inst) || HasILUO(inst))) isCombined = 1; if(outMAC_ARL(inst, isCombined) && (HasILUR(inst) || HasILUO(inst))) isCombined = 1; if(outILU_R(inst, isCombined) && (HasILUO(inst))) isCombined = 1; outILU_O(inst, isCombined); } int main(int argc, char *argv[]) { FILE *in; byte header[4]; uint *shader; int i; if(argc < 2) return 1; in = fopen(argv[1], "rb"); if(in == NULL) return 1; fread(header, 1, 4, in); // printf("%X %X %X %X\n", header[0], header[1], header[2], header[3]); if(header[1] != 0x20){ fprintf(stderr, "shader not type 0x20\n"); return 1; } shader = malloc(header[2]*sizeof(uint)*4); fread(shader, 4, header[2]*4, in); fclose(in); for(i = 0; i < header[2]*4; i += 4){ VSH_SHADER_INSTRUCTION inst; disassemble(&shader[i], &inst); out(&inst); } free(shader); }