diff --git a/thecl/eclmap.c b/thecl/eclmap.c index 52dc2b7..030020b 100644 --- a/thecl/eclmap.c +++ b/thecl/eclmap.c @@ -120,6 +120,7 @@ is_keyword( "times", "switch", "case", "default", "break", "async", "global", "sin", "cos", "sqrt", "rad", "false", "true", + "continue", "string", "loop", NULL }; const char **kwp = keywords; @@ -168,7 +169,7 @@ validate_type( int linenum, const char *ptr) { - if (ptr[0] != '$' && ptr[0] != '%' || ptr[0] && ptr[1]) { + if (ptr[0] != '$' && ptr[0] != '%' && ptr[0] != '?' || ptr[0] && ptr[1]) { fprintf(stderr, "%s:%s:%u: unknown type '%s'\n", argv0, state->fn, linenum, ptr); return 1; } diff --git a/thecl/ecsparse.y b/thecl/ecsparse.y index 91fbe72..1e47d00 100644 --- a/thecl/ecsparse.y +++ b/thecl/ecsparse.y @@ -147,7 +147,7 @@ static bool var_accessible(parser_state_t* state, thecl_variable_t* var); /* Returns variable of the given name in the specified sub, or NULL if the variable doesn't exist/is out of scope */ static thecl_variable_t* var_get(parser_state_t* state, thecl_sub_t* sub, const char* name); /* Returns the stack offset of a specified variable in the specified sub. */ -static int var_stack(parser_state_t* state, thecl_sub_t* sub, const char* name); +static int var_stack(parser_state_t* state, thecl_sub_t* sub, const char* name, int use_type); /* Returns the type of a specified variable in the specified sub. */ static int var_type(parser_state_t* state, thecl_sub_t* sub, const char* name); /* Returns 1 if a variable of a given name exists, and 0 if it doesn't. */ @@ -219,6 +219,7 @@ static const char sub_param_fi[] = {'f', 'i'}; %token T_INT "int" %token T_FLOAT "float" %token T_VOID "void" +%token T_STRING "string" %token T_INLINE "inline" %token T_RETURN "return" %token T_VARARGS "..." @@ -230,10 +231,12 @@ static const char sub_param_fi[] = {'f', 'i'}; %token T_DO "do" %token T_WHILE "while" %token T_TIMES "times" +%token T_LOOP "loop" %token T_SWITCH "switch" %token T_CASE "case" %token T_DEFAULT "default" %token T_BREAK "break" +%token T_CONTINUE "continue" %token T_ASYNC "async" %token T_GLOBAL "global" %token T_ASSIGNADD "+=" @@ -283,6 +286,7 @@ static const char sub_param_fi[] = {'f', 'i'}; %type Cast_Target %type DeclareKeyword +%type DeclareKeywordInline %type VarDeclaration %type Cast_Target2 @@ -300,7 +304,7 @@ static const char sub_param_fi[] = {'f', 'i'}; %left '<' "<=" '>' ">=" %left '+' '-' %left '*' '/' '%' -%precedence '!' T_NEG +%precedence '!' T_NEG '~' /* %precedence "--" */ /* not needed */ %expect 0 @@ -341,7 +345,7 @@ Statement: state->current_sub->is_inline = true; free($3); } - '(' ArgumentDeclaration ')' { + '(' ArgumentDeclarationInline ')' { ssize_t arity = state->current_sub->stack / 4; state->current_sub->arity = arity; char* format = malloc(arity + 1); @@ -538,7 +542,7 @@ Subroutine_Body: Global_Def: Instruction_Parameter[param] { if ($param->is_expression_param) { - yyerror(state, "expressions that can't ve evaluated compile-time are not allowed for global definitions"); + yyerror(state, "expressions that can't be evaluated compile-time are not allowed for global definitions"); } $$ = $param; } @@ -562,6 +566,11 @@ DeclareKeyword: | "float" { $$ = 'f'; } ; +DeclareKeywordInline: + DeclareKeyword + | "string" { $$ = 'z'; } + ; + VarDeclaration: DeclareKeyword | DeclareKeyword IDENTIFIER { @@ -598,9 +607,22 @@ ArgumentDeclaration: } ; +ArgumentDeclarationInline: + %empty + | DeclareKeywordInline IDENTIFIER { + var_create(state, state->current_sub, $2, $1); + free($2); + } + | ArgumentDeclarationInline ',' DeclareKeywordInline IDENTIFIER { + var_create(state, state->current_sub, $4, $3); + free($4); + } + ; + Instructions: %empty | Instructions INTEGER ':' { set_time(state, $2); } + | Instructions '-' INTEGER ':' { set_time(state, -$3); } | Instructions '+' INTEGER ':' { set_time(state, state->instr_time + $3); } | Instructions IDENTIFIER ':' { label_create(state, $2); free($2); } | Instructions Instruction ';' @@ -636,6 +658,7 @@ Block: | IfBlock | WhileBlock | TimesBlock + | LoopBlock | SwitchBlock ; @@ -656,7 +679,8 @@ BreakStatement: if ( strncmp(head->data, "while", 5) == 0 || strncmp(head->data, "switch", 6) == 0 || - strncmp(head->data, "times", 5) == 0 + strncmp(head->data, "times", 5) == 0 || + strncmp(head->data, "loop", 4) == 0 ) { char labelstr[256]; snprintf(labelstr, 256, "%s_end", (char*)head->data); @@ -665,7 +689,29 @@ BreakStatement: } } if(!head) { - yyerror(state, "break not within while or switch"); + yyerror(state, "break not within while, times, or switch"); + g_was_error = true; + } + } + ; + +ContinueStatement: + "continue" { + list_node_t *head = state->block_stack.head; + for(; head; head = head->next) { + if ( + strncmp(head->data, "while", 5) == 0 || + strncmp(head->data, "times", 5) == 0 || + strncmp(head->data, "loop", 4) == 0 + ) { + char labelstr[256]; + snprintf(labelstr, 256, "%s_st", (char*)head->data); + expression_create_goto(state, GOTO, labelstr); + break; + } + } + if(!head) { + yyerror(state, "continue not within while or times"); g_was_error = true; } } @@ -829,10 +875,36 @@ TimesBlock: } ; +LoopBlock: + "loop" { + char labelstr[250]; + snprintf(labelstr, 250, "loop_%i_%i", yylloc.first_line, yylloc.first_column); + char labelstr_st[256]; + char labelstr_end[256]; + snprintf(labelstr_st, 256, "%s_st", (char*)labelstr); + snprintf(labelstr_end, 256, "%s_end", (char*)labelstr); + + list_prepend_new(&state->block_stack, strdup(labelstr)); + label_create(state, labelstr_st); + } CodeBlock { + char labelstr_st[256]; + char labelstr_end[256]; + list_node_t *head = state->block_stack.head; + snprintf(labelstr_st, 256, "%s_st", (char*)head->data); + snprintf(labelstr_end, 256, "%s_end", (char*)head->data); + + expression_create_goto(state, GOTO, labelstr_st); + label_create(state, labelstr_end); + + free(head->data); + list_del(&state->block_stack, head); + } + ; + SwitchBlock: "switch" '(' ExpressionAny[cond] ')' { char name[256]; - list_prepend_new(&state->block_stack, NULL); /* The NULL acts as a sentinel of switch cases. */ + list_prepend_new(&state->switch_stack, NULL); /* The NULL acts as a sentinel of switch cases. */ snprintf(name, 256, "switch_%i_%i", yylloc.first_line, yylloc.first_column); list_prepend_new(&state->block_stack, strdup(name)); @@ -848,7 +920,7 @@ SwitchBlock: const expr_t* tmp = expr_get_by_symbol(state->version, $cond->result_type == 'S' ? ASSIGNI : ASSIGNF); instr_add(state->current_sub, instr_new(state, tmp->id, "p", param)); - list_prepend_new(&state->block_stack, var); /* We will need it later. */ + list_prepend_new(&state->switch_stack, var); /* We will need it later. */ expression_free($cond); expression_create_goto(state, GOTO, name); /* Jump to the case checks. */ @@ -857,7 +929,7 @@ SwitchBlock: } CaseList '}' { scope_finish(state); - list_node_t* head = state->block_stack.head; + list_node_t* head = state->switch_stack.head; thecl_variable_t* var = (thecl_variable_t*)head->data; thecl_param_t* param = param_new(var->type); param->stack = 1; @@ -865,7 +937,7 @@ SwitchBlock: param->value.val.S = var->stack; else param->value.val.f = (float)var->stack; - list_del(&state->block_stack, head); + list_del(&state->switch_stack, head); head = state->block_stack.head; @@ -883,7 +955,7 @@ SwitchBlock: expr = expr_get_by_symbol(state->version, param->type == 'S' ? LOADI : LOADF); int id_load = expr->id; - list_node_t *node = state->block_stack.head; + list_node_t *node = state->switch_stack.head; while (node->data) { switch_case_t *switch_case = node->data; @@ -904,9 +976,9 @@ SwitchBlock: free(buf); } param_free(param); - if (node->next != NULL) /* Prevent crashing when there is nothing else on the block stack. */ + if (node->next != NULL) /* Prevent crashing when there is nothing else on the switch stack. */ node->next->prev = NULL; - state->block_stack.head = node->next; + state->switch_stack.head = node->next; free(node); label_create(state, labelstr); @@ -924,7 +996,7 @@ Case: switch_case_t *switch_case = malloc(sizeof(switch_case_t)); switch_case->expr = $2; - list_node_t *node = state->block_stack.head; + list_node_t *node = state->switch_stack.head; if (((thecl_variable_t*)node->data)->type != $2->result_type) yyerror(state, "wrong value type in switch case"); @@ -936,7 +1008,7 @@ Case: while(node->data) /* Sentinel has data=NULL */ node = node->next; - list_prepend_to(&state->block_stack, switch_case, node); /* Prepends to the sentinel. */ + list_prepend_to(&state->switch_stack, switch_case, node); /* Prepends to the sentinel. */ } | "default" ':' { @@ -946,11 +1018,11 @@ Case: snprintf(switch_case->labelstr, 250, "case_%i_%i", yylloc.first_line, yylloc.first_column); label_create(state, switch_case->labelstr); - list_node_t *node = state->block_stack.head; + list_node_t *node = state->switch_stack.head; while(node->data) node = node->next; - list_prepend_to(&state->block_stack, switch_case, node); + list_prepend_to(&state->switch_stack, switch_case, node); } ; @@ -1095,6 +1167,7 @@ InstructionNoGoto: instr_add(state->current_sub, instr_new(state, TH10_INS_STACK_ALLOC, "S", state->current_sub->stack)); } | BreakStatement + | ContinueStatement | "return" ExpressionAny { if (!is_post_th10(state->version)) yyerror(state, "return statement is not supported pre-th10"); @@ -1137,6 +1210,10 @@ InstructionNoGoto: Assignment: Address '=' ExpressionAny { + if ($3->result_type != 'S' && $3->result_type != 'f') { + yyerror(state, "assignment requires int or float"); + exit(2); + } const expr_t* expr = expr_get_by_symbol(state->version, $1->type == 'S' ? ASSIGNI : ASSIGNF); expression_output(state, $3, 1); expression_free($3); @@ -1147,7 +1224,12 @@ Assignment: } else { if ($1->value.val.f >= 0.0f) var = state->current_sub->vars[(int)$1->value.val.f / 4]; } - if (var != NULL) var->is_written = true; + if (var != NULL) { + var->is_written = true; + if ($1->value.type != var->type) { + var->is_punned = true; + } + } } | Address "+=" ExpressionAny { var_shorthand_assign(state, $1, $3, ADDI, ADDF); } | Address "-=" ExpressionAny { var_shorthand_assign(state, $1, $3, SUBTRACTI, SUBTRACTF); } @@ -1307,6 +1389,19 @@ ExpressionCall: ExpressionSubset: ExpressionSubsetNoUnaryPlus | '+' Expression %prec T_NEG { $$ = $2; } + | '-' Expression %prec T_NEG { + if (is_post_th125(state->version)) { + $$ = EXPR_21(NEGI, NEGF, $2); + } else { + thecl_param_t* p = param_new($2->result_type); + if (p->value.type == 'f') { + p->value.val.f = 0; + $$ = EXPR_12(SUBTRACTF, expression_load_new(state, p), $2); + } else { + $$ = EXPR_11(NEGI, $2); + } + } + } ; ExpressionSubsetNoUnaryPlus: @@ -1330,24 +1425,17 @@ ExpressionSubsetNoUnaryPlus: | Expression '&' Expression { $$ = EXPR_12(B_AND, $1, $3); } | Expression '?' Expression ':' Expression %prec '?' { $$ = expression_ternary_new(state, $1, $3, $5); } - | '!' Expression { $$ = EXPR_11(NOT, $2); } + | '!' Expression { $$ = EXPR_21(NOTI, NOTF, $2); } + | '~' Expression { + thecl_param_t* p = param_new($2->result_type); + p->value.val.S = -1; + $$ = EXPR_12(XOR, $2, expression_load_new(state, p)); + } | Address "--" { $$ = EXPR_1A(DEC, $1); if ($1->value.val.S >= 0) /* Stack variables only. This is also verrfied to be int by expression creation. */ state->current_sub->vars[$1->value.val.S / 4]->is_written = true; } - | '-' Expression %prec T_NEG { - if (is_post_th13(state->version)) { - $$ = EXPR_21(NEGI, NEGF, $2); - } else { - thecl_param_t* p = param_new($2->result_type); - if (p->value.type == 'f') - p->value.val.f = 0; - else - p->value.val.S = 0; - $$ = EXPR_22(SUBTRACTI, SUBTRACTF, expression_load_new(state, p), $2); - } - } ; Address: @@ -1358,13 +1446,13 @@ Address: | '$' IDENTIFIER { $$ = param_new('S'); $$->stack = 1; - $$->value.val.S = var_stack(state, state->current_sub, $2); + $$->value.val.S = var_stack(state, state->current_sub, $2, 'S'); free($2); } | '%' IDENTIFIER { $$ = param_new('f'); $$->stack = 1; - $$->value.val.f = var_stack(state, state->current_sub, $2); + $$->value.val.f = var_stack(state, state->current_sub, $2, 'f'); free($2); } | IDENTIFIER { @@ -1376,10 +1464,15 @@ Address: } $$ = param_new(type); $$->stack = 1; - if (type == 'S') { - $$->value.val.S = var_stack(state, state->current_sub, $1); - } else { - $$->value.val.f = var_stack(state, state->current_sub, $1); + switch (type) { + case 'z': + $$->is_inline_string = true; + case 'S': + $$->value.val.S = var_stack(state, state->current_sub, $1, 'S'); + break; + case 'f': + $$->value.val.f = var_stack(state, state->current_sub, $1, 'f'); + break; } } else { global_definition_t *def = global_get(state, $1); @@ -1528,8 +1621,8 @@ instr_set_types( if (new_type != param->type && !(param->type == 'D' && new_type == 'H') && - !(param->type == 'z' && (new_type == 'm' || new_type == 'x' || new_type == 'N' || new_type == 'n')) && - !(param->type == 'S' && (new_type == 's' || new_type == 'U' || new_type == 't')) + !(param->type == 'z' && (new_type == 'm' || new_type == 'x' || new_type == 'N' || new_type == 'n' || new_type == 'T')) && + !(param->type == 'S' && (new_type == 's' || new_type == 'U' || new_type == 'u' || new_type == 't' || new_type == 'C' || new_type == 'N' || new_type == 'n')) ) { seqmap_entry_t* ent = seqmap_get(g_eclmap->ins_names, instr->id); char buf[128]; @@ -1753,17 +1846,27 @@ static void instr_create_inline_call( size_t i = 0; thecl_param_t* param; list_for_each(params, param) { - if (sub->format[i] == '\0') { - yyerror(state, "too many paramters for inline sub \"%s\"", sub->name); - list_for_each(params, param) - param_free(param); - return; - } - if (sub->format[i] != param->type) { - yyerror(state, "wrong parameter %i when calling inline sub \"%s\", expected type: %c\n", i + 1, sub->name, sub->format[i]); - list_for_each(params, param) - param_free(param); - return; + switch (sub->format[i]) { + case '\0': + yyerror(state, "too many paramters for inline sub \"%s\"", sub->name); + list_for_each(params, param) + param_free(param); + return; + case '?': + if (param->type != 'S' && param->type != 'f') { + yyerror(state, "wrong parameter %i when calling inline sub \"%s\" (expected: S or f, got: %c)\n", i + 1, sub->name, param->type); + list_for_each(params, param) + param_free(param); + return; + } + break; + default: + if (sub->format[i] != param->type) { + yyerror(state, "wrong parameter %i when calling inline sub \"%s\" (expected: %c, got: %c)\n", i + 1, sub->name, sub->format[i], param->type); + list_for_each(params, param) + param_free(param); + return; + } } ++i; } @@ -1786,14 +1889,15 @@ static void instr_create_inline_call( * gets written to, or the passed parameter is an expression. * We will use a param_replace array to replace all argument variable references from the code of copied inline sub. */ thecl_param_t** param_replace = calloc(sub->arity, sizeof(thecl_param_t*)); + thecl_variable_t** param_replace_vars = malloc(sub->arity * sizeof(thecl_variable_t*)); thecl_variable_t* var; i = 0; + size_t replaced_params = 0; list_for_each(params, param) { /* It has alredy been verified that param amount is correct. */ var = sub->vars[i]; - if (var->is_written || param->is_expression_param || param_is_system_var(state, param)) { - - if (param->is_expression_param && !var->is_written) { + if (var->type != 'z' && (var->is_written || var->is_punned || param->is_expression_param || param_is_system_var(state, param))) { + if (param->is_expression_param && !var->is_written && !var->is_punned) { /* Check if the passed expression can be simplified to a literal value. */ list_node_t* node = state->expressions.tail; expression_t* expr = (expression_t*)node->data; @@ -1811,6 +1915,7 @@ static void instr_create_inline_call( strcpy(buf, name); strcat(buf, var->name); thecl_variable_t* var = var_create(state, state->current_sub, buf, param->type); + param_replace_vars[replaced_params++] = var; thecl_param_t* new_param = param_new(param->type); new_param->stack = 1; if (new_param->type == 'S') @@ -1852,6 +1957,7 @@ static void instr_create_inline_call( thecl_variable_t* var_new = var_create(state, state->current_sub, buf, var->type); stack_replace[i - sub->arity] = var_new; } + size_t stack_replace_count = (sub->stack / 4) - sub->arity; /* Temprary label list that modifications will be apply to when needed. * Content of this list will be later copied into the sub that created the inline call. */ @@ -1904,11 +2010,19 @@ static void instr_create_inline_call( continue; } + bool recalculate_size = false; + list_node_t* param_node; list_for_each_node(&new_instr->params, param_node) { /* Still reusing the same param variable as earlier. */ param = (thecl_param_t*)param_node->data; - if (param->stack) { + if (param->is_inline_string) { + thecl_param_t* replacement = param_copy(param_replace[param->value.val.S / 4]); + replacement->type = param->type; + param_node->data = replacement; + param_free(param); + recalculate_size = true; + } else if (param->stack) { if (param->type == 'D' || param->type == 'H') { thecl_sub_param_t* D = (thecl_sub_param_t*)param->value.val.m.data; if (D->from == 'i') { @@ -1931,7 +2045,12 @@ static void instr_create_inline_call( } else if (param->value.type == 'S') { if (param->value.val.S < sub->arity*4 && param->value.val.S >= 0) { /* Parameter. */ - param_node->data = param_copy(param_replace[param->value.val.S / 4]); + thecl_param_t* replacement = param_copy(param_replace[param->value.val.S / 4]); + if (replacement->type == 'f') { + replacement->value.type = 'S'; + replacement->value.val.S = (int)replacement->value.val.f; + } + param_node->data = replacement; param_free(param); } else if (param->value.val.S > 0) { /* Regular stack variable, needs adjusting the offset. */ @@ -1939,9 +2058,16 @@ static void instr_create_inline_call( } } else if (param->value.type == 'f') { if (param->value.val.f < (float)(sub->arity*4) && param->value.val.f >= 0.0f) { - param_node->data = param_copy(param_replace[(int)param->value.val.f / 4]); + /* Parameter. */ + thecl_param_t* replacement = param_copy(param_replace[(int)param->value.val.f / 4]); + if (replacement->type == 'S') { + replacement->value.type = 'f'; + replacement->value.val.f = (float)replacement->value.val.S; + } + param_node->data = replacement; param_free(param); } else if (param->value.val.f > 0.0f) { + /* Regular stack variable, needs adjusting the offset. */ param->value.val.f = (float)stack_replace[(int)param->value.val.f / 4 - sub->arity]->stack; } } @@ -1961,6 +2087,10 @@ static void instr_create_inline_call( } } } + if (recalculate_size) { + new_instr->size = state->instr_size(state->version, new_instr, false); + labels_adjust(&tmp_labels, new_instr->offset - offset_diff, -new_instr->size); + } instr_add(state->current_sub, new_instr); } @@ -1978,10 +2108,12 @@ static void instr_create_inline_call( scope_finish(state); - /* We have to mark variables that were marked as unused in the inline sub - * as unused in the current sub as well. */ - for (size_t v=sub->arity; vvar_count; ++v) { - stack_replace[v - sub->arity]->is_unused = sub->vars[v]->is_unused; + /* Allow next created vars to reuse stack offsets. */ + for (size_t v=0; vis_unused = true; + } + for (size_t v=0; vis_unused = true; } /* Free stuff. */ @@ -1996,6 +2128,7 @@ static void instr_create_inline_call( if (params_org == NULL) free(params); free(param_replace); + free(param_replace_vars); free(stack_replace); } @@ -2637,9 +2770,12 @@ expression_optimize( case NEGF: param->value.val.f = -val1f; break; - case NOT: + case NOTI: param->value.val.S = !val1S; break; + case NOTF: + param->value.val.S = !val1f; + break; case SIN: param->value.val.f = sinf(val1f); break; @@ -2649,6 +2785,9 @@ expression_optimize( case SQRT: param->value.val.f = sqrtf(val1f); break; + case B_NOT: + param->value.val.S = ~val1S; + break; case DEC: /* This happens if the user tries to use the decrement operator on a non-variable. */ yyerror(state, "decrement operator can't be used on literals"); @@ -2973,6 +3112,7 @@ var_create( var->stack = var_get_new_stack(state, sub); var->is_written = false; var->is_unused = false; + var->is_punned = false; var->scope = state->scope_stack[state->scope_cnt - 1]; ++sub->var_count; @@ -3000,6 +3140,10 @@ var_create_assign( yyerror(state, "var creation with assignment requires int/float declaration keyword"); exit(2); } + if (expr->result_type != 'S' && expr->result_type != 'f') { + yyerror(state, "assignment requires int or float"); + exit(2); + } thecl_variable_t* var = var_create(state, sub, name, type); var->is_written = true; @@ -3050,14 +3194,19 @@ static int var_stack( parser_state_t* state, thecl_sub_t* sub, - const char* name) + const char* name, + int use_type) { seqmap_entry_t* ent = seqmap_find(g_eclmap->gvar_names, name); if (ent) return ent->key; thecl_variable_t* var = var_get(state, sub, name); - if (var != NULL) + if (var != NULL) { + if (var->type != use_type) { + var->is_punned = true; + } return var->stack; + } yyerror(state, "variable not found: %s", name); return 0; @@ -3072,8 +3221,11 @@ var_type( seqmap_entry_t* ent = seqmap_find(g_eclmap->gvar_names, name); if (ent) { ent = seqmap_get(g_eclmap->gvar_types, ent->key); - if (ent) - return ent->value[0] == '$' ? 'S' : 'f'; + if (ent) { + if (ent->value[0] == '$') return 'S'; + else if (ent->value[0] == '%') return 'f'; + else return '?'; + } } thecl_variable_t* var = var_get(state, sub, name); @@ -3125,7 +3277,12 @@ var_shorthand_assign( } else { if (param->value.val.f >= 0.0f) var = state->current_sub->vars[(int)param->value.val.f / 4]; } - if (var != NULL) var->is_written = true; + if (var != NULL) { + var->is_written = true; + if (param->value.type != var->type) { + var->is_punned = true; + } + } } static void diff --git a/thecl/ecsscan.l b/thecl/ecsscan.l index ea007d4..005939c 100644 --- a/thecl/ecsscan.l +++ b/thecl/ecsscan.l @@ -85,6 +85,7 @@ "int" return T_INT; "float" return T_FLOAT; "void" return T_VOID; +"string" return T_STRING; "inline" return T_INLINE; "return" return T_RETURN; "@" return '@'; @@ -99,10 +100,12 @@ "do" return T_DO; "while" return T_WHILE; "times" return T_TIMES; +"loop" return T_LOOP; "switch" return T_SWITCH; "case" return T_CASE; "default" return T_DEFAULT; "break" return T_BREAK; +"continue" return T_CONTINUE; "async" return T_ASYNC; "global" return T_GLOBAL; "=" return '='; @@ -131,6 +134,7 @@ "^" return '^'; "|" return '|'; "&" return '&'; +"~" return '~'; "--" return T_DEC; "sin" return T_SIN; "cos" return T_COS; @@ -167,6 +171,11 @@ ins_[0-9]+ { yylval.integer = strtoul(yytext+2, NULL, 2); return INTEGER; } +#[0-9a-fA-F]{8} { + uint32_t color_int = strtoul(yytext+1, NULL, 16); + yylval.integer = (color_int & 0xFF000000) >> 24 | (color_int & 0xFF0000) >> 8 | (color_int & 0xFF00) << 8 | (color_int & 0xFF) << 24; + return INTEGER; +} "false" { /* boolean "false" */ yylval.integer = 0; diff --git a/thecl/expr.c b/thecl/expr.c index 7383965..59b4dcd 100644 --- a/thecl/expr.c +++ b/thecl/expr.c @@ -67,18 +67,20 @@ th06_expressions[] = { { GTF, -21, 'S', NULL, 2, "ff", "s1 > s0", 0 }, { GTEQI, -22, 'S', NULL, 2, "SS", "s1 >= s0", 0 }, { GTEQF, -23, 'S', NULL, 2, "ff", "s1 >= s0", 0 }, - { NOT, -24, 'S', NULL, 1, "S", "!s0", 0 }, - { OR, -25, 'S', NULL, 2, "SS", "s1 || s0", 0 }, - { AND, -26, 'S', NULL, 2, "SS", "s1 && s0", 0 }, - { XOR, -27, 'S', NULL, 2, "SS", "s1 ^ s0", 0 }, - { B_OR, -28, 'S', NULL, 2, "SS", "s1 | s0", 0 }, - { B_AND, -29, 'S', NULL, 2, "SS", "s1 & s0", 0 }, - { DEC, -30, 'S', NULL, 0, NULL, "p0--", 0 }, - { SIN, -31, 'f', NULL, 1, "f", "sin(s0)", 1 }, - { COS, -32, 'f', NULL, 1, "f", "cos(s0)", 1 }, - { NEGI, -33, 'S', NULL, 1, "S", "-s0", 0 }, - { NEGF, -34, 'f', NULL, 1, "f", "-s0", 0 }, - { SQRT, -35, 'f', NULL, 1, "f", "sqrt(s0)", 1 }, + { NOTI, -24, 'S', NULL, 1, "S", "!s0", 0 }, + { NOTF, -25, 'S', NULL, 1, "f", "!s0", 0 }, + { OR, -26, 'S', NULL, 2, "SS", "s1 || s0", 0 }, + { AND, -27, 'S', NULL, 2, "SS", "s1 && s0", 0 }, + { XOR, -28, 'S', NULL, 2, "SS", "s1 ^ s0", 0 }, + { B_OR, -29, 'S', NULL, 2, "SS", "s1 | s0", 0 }, + { B_AND, -30, 'S', NULL, 2, "SS", "s1 & s0", 0 }, + { DEC, -31, 'S', NULL, 0, NULL, "p0--", 0 }, + { SIN, -32, 'f', NULL, 1, "f", "sin(s0)", 1 }, + { COS, -33, 'f', NULL, 1, "f", "cos(s0)", 1 }, + { NEGI, -34, 'S', NULL, 1, "S", "-s0", 0 }, + { NEGF, -35, 'f', NULL, 1, "f", "-s0", 0 }, + { SQRT, -36, 'f', NULL, 1, "f", "sqrt(s0)", 1 }, + { B_NOT, -37, 'S', NULL, 1, "S", "~s0", 0 }, { 0, 0, 0, NULL, 0, NULL, NULL, 0 } }; @@ -122,17 +124,35 @@ th10_expressions[] = { { GTF, 68, 'S', NULL, 2, "ff", "s1 > s0", 0 }, { GTEQI, 69, 'S', NULL, 2, "SS", "s1 >= s0", 0 }, { GTEQF, 70, 'S', NULL, 2, "ff", "s1 >= s0", 0 }, - { NOT, 71, 'S', NULL, 1, "S", "!s0", 0 }, -/* { XXX, 72, 0, NULL, 0, NULL, NULL },*/ + { NOTI, 71, 'S', NULL, 1, "S", "!s0", 0 }, + { NOTF, 72, 'S', NULL, 1, "f", "!s0", 0 }, { OR, 73, 'S', NULL, 2, "SS", "s1 || s0", 0 }, { AND, 74, 'S', NULL, 2, "SS", "s1 && s0", 0 }, { XOR, 75, 'S', NULL, 2, "SS", "s1 ^ s0", 0 }, { B_OR, 76, 'S', NULL, 2, "SS", "s1 | s0", 0 }, { B_AND, 77, 'S', NULL, 2, "SS", "s1 & s0", 0 }, { DEC, 78, 'S', "S", 0, NULL, "p0--", 0 }, - { SIN, 79, 'f', NULL, 1, "f", "sin(s0)", 1 }, - { COS, 80, 'f', NULL, 1, "f", "cos(s0)", 1 }, - { SQRT, 88, 'f', NULL, 1, "f", "sqrt(s0)", 1 }, + { SIN, 79, 'f', NULL, 1, "f", "sin(s0)", 1 }, + { COS, 80, 'f', NULL, 1, "f", "cos(s0)", 1 }, + { NEGI, 84, 'S', NULL, 1, "S", "-s0", 0 }, + // NEGF existed since MoF but had a broken implementation until DS + { NEGF, -1, 'f', NULL, 1, "f", "-s0", 0 }, + { SQRT, -2, 'f', NULL, 1, "f", "sqrt(s0)", 1 }, + { B_NOT, -3, 'S', NULL, 1, "S", "~s0", 0 }, + { 0, 0, 0, NULL, 0, NULL, NULL, 0 } +}; + +static const expr_t +alcostg_expressions[] = { + /*SYM ID RET P A S DISP */ + { SQRT, 88, 'f', NULL, 1, "f", "sqrt(s0)", 1 }, + { 0, 0, 0, NULL, 0, NULL, NULL, 0 } +}; + +static const expr_t +th125_expressions[] = { + /*SYM ID RET P A S DISP */ + { NEGF, 85, 'f', NULL, 1, "f", "-s0", 0 }, { 0, 0, 0, NULL, 0, NULL, NULL, 0 } }; @@ -165,9 +185,24 @@ expr_get_by_symbol( { const expr_t* ret = NULL; - if (!ret && is_post_th13(version)) ret = expr_get_by_symbol_from_table(th13_expressions, symbol); - if (!ret && is_post_th10(version)) ret = expr_get_by_symbol_from_table(th10_expressions, symbol); - if (!ret && !is_post_th10(version)) ret = expr_get_by_symbol_from_table(th06_expressions, symbol); + switch (engine_version(version)) { + default: + case VER_POST_TH13: + ret = expr_get_by_symbol_from_table(th13_expressions, symbol); + if (ret) break; + case VER_POST_TH125: + ret = expr_get_by_symbol_from_table(th125_expressions, symbol); + if (ret) break; + case VER_POST_ALCOSTG: + ret = expr_get_by_symbol_from_table(alcostg_expressions, symbol); + if (ret) break; + case VER_POST_TH10: + ret = expr_get_by_symbol_from_table(th10_expressions, symbol); + break; + case VER_PRE_TH10: + case VER_PRE_TH8: + ret = expr_get_by_symbol_from_table(th06_expressions, symbol); + } return ret; } @@ -193,9 +228,24 @@ expr_get_by_id( { const expr_t* ret = NULL; - if (!ret && is_post_th13(version)) ret = expr_get_by_id_from_table(th13_expressions, id); - if (!ret && is_post_th10(version)) ret = expr_get_by_id_from_table(th10_expressions, id); - if (!ret && !is_post_th10(version)) ret = expr_get_by_id_from_table(th06_expressions, id); + switch (engine_version(version)) { + default: + case VER_POST_TH13: + ret = expr_get_by_id_from_table(th13_expressions, id); + if (ret) break; + case VER_POST_TH125: + ret = expr_get_by_id_from_table(th125_expressions, id); + if (ret) break; + case VER_POST_ALCOSTG: + ret = expr_get_by_id_from_table(alcostg_expressions, id); + if (ret) break; + case VER_POST_TH10: + ret = expr_get_by_id_from_table(th10_expressions, id); + break; + case VER_PRE_TH10: + case VER_PRE_TH8: + ret = expr_get_by_id_from_table(th06_expressions, id); + } return ret; } diff --git a/thecl/expr.h b/thecl/expr.h index f3c5728..a613f13 100644 --- a/thecl/expr.h +++ b/thecl/expr.h @@ -93,7 +93,8 @@ enum { GTF, GTEQI, GTEQF, - NOT, + NOTI, + NOTF, AND, OR, XOR, @@ -106,5 +107,6 @@ enum { SIN, COS, SQRT, + B_NOT, }; #endif diff --git a/thecl/thecl.c b/thecl/thecl.c index f925694..fb40646 100644 --- a/thecl/thecl.c +++ b/thecl/thecl.c @@ -139,8 +139,7 @@ thecl_instr_free(thecl_instr_t* instr) thecl_param_t* param; list_for_each(&instr->params, param) { - value_free(¶m->value); - free(param); + param_free(param); } list_free_nodes(&instr->params); @@ -161,6 +160,7 @@ param_new( param->type = type; param->value.type = type; param->is_expression_param = 0; + param->is_inline_string = 0; return param; } @@ -170,12 +170,15 @@ param_copy( ) { thecl_param_t* copy = calloc(1, sizeof(thecl_param_t)); memcpy(copy, param, sizeof(thecl_param_t)); - /* Handle possible pointers in the value. */ - if (copy->value.type == 'z') - copy->value.val.z = strdup(param->value.val.z); - else if (copy->value.type == 'm') { - copy->value.val.m.data = malloc(param->value.val.m.length); - memcpy(copy->value.val.m.data, param->value.val.m.data, param->value.val.m.length); + /* Inline string values cannot have valid pointers */ + if (!copy->is_inline_string) { + /* Handle possible pointers in the value. */ + if (copy->value.type == 'z') + copy->value.val.z = strdup(param->value.val.z); + else if (copy->value.type == 'm') { + copy->value.val.m.data = malloc(param->value.val.m.length); + memcpy(copy->value.val.m.data, param->value.val.m.data, param->value.val.m.length); + } } return copy; } @@ -184,7 +187,11 @@ void param_free( thecl_param_t* param) { - value_free(¶m->value); + // Filter out inline string params since they don't + // have valid pointers that need to be freed. + if (!param->is_inline_string) { + value_free(¶m->value); + } free(param); } @@ -223,11 +230,39 @@ free_globals(void) } bool -is_post_th10( - unsigned int version) -{ +is_pre_th8(unsigned int version) { + switch (version) { + case 6: case 7: return true; + default: return false; + } +} + +bool +is_post_th10(unsigned int version) { switch(version) { - case 6: case 7: case 8: case 9: case 95: return false; + case 6: case 7: + case 8: case 9: case 95: return false; + default: return true; + } +} + +bool +is_post_alcostg(unsigned int version) { + switch (version) { + case 6: case 7: + case 8: case 9: case 95: + case 10: return false; + default: return true; + } +} + +bool +is_post_th125(unsigned int version) { + switch (version) { + case 6: case 7: + case 8: case 9: case 95: + case 10: + case 103: case 11: case 12: return false; default: return true; } } @@ -235,12 +270,33 @@ is_post_th10( bool is_post_th13(unsigned int version) { switch(version) { - case 6: case 7: case 8: case 9: case 95: - case 10: case 103: case 11: case 12: case 125: case 128: return false; + case 6: case 7: + case 8: case 9: case 95: + case 10: + case 103: case 11: case 12: + case 125: case 128: return false; default: return true; } } +thecl_engine_version +engine_version(unsigned int version) { + switch (version) { + default: + return VER_POST_TH13; + case 125: case 128: + return VER_POST_TH125; + case 103: case 11: case 12: + return VER_POST_ALCOSTG; + case 10: + return VER_POST_TH10; + case 8: case 9: case 95: + return VER_PRE_TH10; + case 6: case 7: + return VER_PRE_TH8; + } +} + bool is_numeric_difficulty_version(unsigned int version) { return version == 185 || version == 19; diff --git a/thecl/thecl.h b/thecl/thecl.h index 56c0cf6..710a0c9 100644 --- a/thecl/thecl.h +++ b/thecl/thecl.h @@ -94,6 +94,7 @@ typedef struct thecl_param_t { value_t value; int stack; char is_expression_param; /* Temporary variable for ecsparse.y */ + char is_inline_string; } thecl_param_t; thecl_param_t* param_new( @@ -103,12 +104,33 @@ thecl_param_t* param_copy( void param_free( thecl_param_t* param); +bool is_pre_th8( + unsigned int version); + bool is_post_th10( unsigned int version); +bool is_post_alcostg( + unsigned int version); + +bool is_post_th125( + unsigned int version); + bool is_post_th13( unsigned int version); +typedef enum { + VER_PRE_TH8, /* Timeline instructions don't support rank and include arg0 */ + VER_PRE_TH10, /* Timeline instructions support rank and time is 32 bit with no arg0 */ + VER_POST_TH10, /* New engine */ + VER_POST_ALCOSTG, /* SQRT exists */ + VER_POST_TH125, /* NEGF is no longer broken */ + VER_POST_TH13 /* NEGI/NEGF change indices */ +} thecl_engine_version; + +thecl_engine_version engine_version( + unsigned int version); + bool is_numeric_difficulty_version( unsigned int version); @@ -170,6 +192,7 @@ typedef struct { int scope; bool is_written; bool is_unused; + bool is_punned; } thecl_variable_t; void thecl_variable_free( @@ -259,6 +282,7 @@ typedef struct { bool is_timeline_sub; /* Variable for escparse.y */ list_t expressions; list_t block_stack; + list_t switch_stack; list_t global_definitions; int* scope_stack; int scope_cnt; diff --git a/thecl/thecl06.c b/thecl/thecl06.c index 830481e..378e897 100644 --- a/thecl/thecl06.c +++ b/thecl/thecl06.c @@ -58,13 +58,25 @@ PACK_BEGIN PACK_END } PACK_ATTRIBUTE th06_instr_t; +// v1/v2 suffixes are used to prevent accidental use of +// the wrong field when translating from internal format typedef struct { PACK_BEGIN - uint16_t time; - int16_t arg0; + union { + struct { + int16_t time_v1; + int16_t arg0; + }; + int32_t time_v2; + }; uint16_t id; - uint8_t size; - uint8_t rank; + union { + struct { + uint8_t size_v2; + uint8_t rank; + }; + int16_t size_v1; + }; unsigned char data[]; PACK_END } PACK_ATTRIBUTE th06_timeline_instr_t; @@ -78,7 +90,7 @@ th06_value_from_data( { if (type == 'n') { return value_from_data(data, data_length, 's', value); - } else if (type == 'o' || type == 'N') { + } else if (type == 'o' || type == 't' || type == 'N' || type == 'T') { return value_from_data(data, data_length, 'S', value); } else { return value_from_data(data, data_length, type, value); @@ -94,7 +106,7 @@ th08_value_from_data( { if (type == 'n') { return value_from_data(data, data_length, 's', value); - } else if (type == 'o' || type == 'N') { + } else if (type == 'o' || type == 't' || type == 'N' || type == 'T') { return value_from_data(data, data_length, 'S', value); } else if (type == 'm') { int ret; @@ -136,7 +148,7 @@ th95_value_from_data( { if (type == 'n') { return value_from_data(data, data_length, 's', value); - } else if (type == 'o' || type == 'N') { + } else if (type == 'o' || type == 't' || type == 'N' || type == 'T') { return value_from_data(data, data_length, 'S', value); } else if (type == 'm') { int ret; @@ -180,37 +192,55 @@ th06_param_to_text( static char* th06_stringify_param( - const thecl_sub_t* sub, + const char* sub_name, const thecl_instr_t* instr, const thecl_param_t* param, unsigned int version) { char temp[512]; switch(param->type) { + // Some instructions use -1 as "none" instead of a sub ID case 'n': - snprintf(temp, 512, "\"Sub%d\"", param->value.val.s); + snprintf(temp, 512, param->value.val.s >= 0 ? "\"Sub%d\"" : "%d", param->value.val.s); return strdup(temp); case 'N': - snprintf(temp, 512, "\"Sub%d\"", param->value.val.S); + snprintf(temp, 512, param->value.val.S >= 0 ? "\"Sub%d\"" : "%d", param->value.val.S); + return strdup(temp); + case 'T': + snprintf(temp, 512, param->value.val.S >= 0 ? "\"Timeline%d\"" : "%d", param->value.val.S); return strdup(temp); case 'o': - snprintf(temp, 512, "%s_%d", sub->name, instr->offset + param->value.val.S); + snprintf(temp, 512, "%s_%d", sub_name, instr->offset + param->value.val.S); + return strdup(temp); + case 't': + snprintf(temp, 512, "%d", param->value.val.S); return strdup(temp); default: - if ( - (param->stack || version == 6) - && (param->value.type == 'f' || param->value.type == 'S' || param->value.type == 's') - ) { - int val; - if (param->value.type == 'S') val = param->value.val.S; - else if (param->value.type == 'f') val = floor(param->value.val.f); - else val = param->value.val.s; - - seqmap_entry_t* ent = seqmap_get(g_eclmap->gvar_names, val); - if (ent) { - snprintf(temp, 256, "%c%s", param->value.type == 'f' ? '%' : '$', ent->value); - return strdup(temp); - } + switch (param->value.type) { + case 'S': case 's': case 'U': case 'u': case 'C': case 'f': + if (param->stack || version == 6) { + int val; + switch (param->value.type) { + case 'S': case 'U': case 'C': + val = param->value.val.S; + break; + case 's': + val = param->value.val.s; + break; + case 'u': + val = param->value.val.u; + break; + case 'f': + val = (int)floorf(param->value.val.f); + break; + } + + seqmap_entry_t * ent = seqmap_get(g_eclmap->gvar_names, val); + if (ent) { + snprintf(temp, 256, "%c%s", param->value.type == 'f' ? '%' : '$', ent->value); + return strdup(temp); + } + } } @@ -228,42 +258,54 @@ th06_stringify_param( static const id_format_pair_t th06_timeline_fmts[] = { /* the first parameter is a part of the struct and is always s */ {0, "nfffssS"}, + {1, "nfff"}, {2, "nfffssS"}, + {3, "nfff"}, {4, "nfffssS"}, + {5, "nfff"}, {6, "nfffssS"}, + {7, "nfff"}, {8, "s"}, - {9, "s"}, - {10, "sSS"}, + {9, ""}, + {10, "SS"}, + {11, "u"}, {12, "s"}, { 0, 0 }, }; static const id_format_pair_t th07_timeline_fmts[] = { - {0, "sfffssSS"}, - {2, "sfffssSS"}, - {4, "sfffssSS"}, - {6, "sfffssSS"}, + {0, "nfffSSS"}, + {2, "nfffSSS"}, + {4, "nfffSSS"}, + {6, "nfffSSS"}, + {11, "s"}, { 0, 0 }, }; static const id_format_pair_t th08_timeline_fmts[] = { - { 0, "sSffssSS"}, - { 1, "sSffssSS"}, - { 2, "sSffssSSS"}, - { 4, "sSffssSSS"}, - { 6, "sS"}, - { 7, "sS"}, - { 8, "sSS"}, - { 10, "sS"}, - { 13, "sS"}, - { 14, "sS"}, - { 15, "sSffssSS"}, - { 16, "s"}, + { 0, "NffSSS"}, + { 1, "NffSSS"}, + { 2, "NfffSSS"}, + { 3, "NfSSS"}, + { 4, "NfffSSS"}, + { 5, "NfSSS"}, + { 6, "S"}, + { 7, ""}, + { 8, "Sss"}, // "Ss--" + { 9, "S" }, + { 10, "S"}, + { 11, "NffSSSS"}, + { 12, "NffSSSS"}, + { 13, "T"}, + { 14, "T"}, + { 15, "NffSSS"}, + { 16, ""}, { 0, 0 }, }; static const id_format_pair_t th09_timeline_fmts[] = { - { 17, "sSffssSS"}, + { 8, "SS"}, + { 17, "NffSSS"}, { 0, 0 }, }; @@ -305,17 +347,19 @@ th06_find_timeline_format( static const id_format_pair_t th06_fmts[] = { /* Special param types: - 'o' - 'S' used as a label (offset in jump instructions) + - 't' - 'S' used as a time - 'N' - 'S' used as a sub name string (in call/enm creation instructions) - 'n' - 's' used as a sub name string + - 'T' - 'S' used as a timeline name string */ { 0, "" }, - { 1, "S" }, - { 2, "So" }, - { 3, "SoS" }, + { 1, "S" }, // "_" + { 2, "to" }, + { 3, "toS" }, { 4, "SS" }, { 5, "Sf" }, - { 6, "SS" }, - { 7, "SSS" }, + { 6, "SU" }, + { 7, "SUS" }, { 8, "Sf" }, { 9, "Sff" }, { 10, "S" }, @@ -337,12 +381,12 @@ static const id_format_pair_t th06_fmts[] = { { 26, "S" }, { 27, "SS" }, { 28, "ff" }, - { 29, "So" }, - { 30, "So" }, - { 31, "So" }, - { 32, "So" }, - { 33, "So" }, - { 34, "So" }, + { 29, "to" }, + { 30, "to" }, + { 31, "to" }, + { 32, "to" }, + { 33, "to" }, + { 34, "to" }, { 35, "NSf" }, { 36, "" }, { 37, "NSfSS" }, @@ -393,8 +437,8 @@ static const id_format_pair_t th06_fmts[] = { { 82, "SSSSffff" }, { 83, "" }, { 84, "S" }, - { 85, "ssfSffffSSSSSS" }, - { 86, "ssffSfffSSSSSS" }, + { 85, "ssffffffSSSSSS" }, + { 86, "ssffffffSSSSSS" }, { 87, "S" }, { 88, "Sf" }, { 89, "Sf" }, @@ -406,16 +450,16 @@ static const id_format_pair_t th06_fmts[] = { { 95, "NfffssS" }, { 96, "" }, { 97, "S" }, - { 98, "ssssS" }, + { 98, "ssssS" }, // "sssss--" { 99, "SS" }, - { 100, "S" }, + { 100, "S" }, // "bbb-" { 101, "S" }, { 102, "Sffff" }, { 103, "fff" }, - { 104, "S" }, - { 105, "S" }, + { 104, "S" }, // "b---" + { 105, "S" }, // "b---" { 106, "S" }, - { 107, "S" }, + { 107, "S" }, // "b---" { 108, "N" }, { 109, "NS" }, { 110, "S" }, @@ -425,10 +469,10 @@ static const id_format_pair_t th06_fmts[] = { { 114, "N" }, { 115, "S" }, { 116, "N" }, - { 117, "S" }, - { 118, "SSss" }, + { 117, "S" }, // "b---" + { 118, "SUC" }, { 119, "S" }, - { 120, "S" }, + { 120, "S" }, // "b---" { 121, "SS" }, { 122, "S" }, { 123, "S" }, @@ -436,25 +480,26 @@ static const id_format_pair_t th06_fmts[] = { { 125, "" }, { 126, "S" }, { 127, "S" }, - { 128, "S" }, - { 129, "SS" }, - { 130, "S" }, + { 128, "S" }, // "s--" + { 129, "SS" }, // "Ss--" + { 130, "S" }, // "b---" { 131, "ffSSSS" }, - { 132, "S" }, + { 132, "S" }, // "b---" { 133, "" }, { 134, "" }, - { 135, "S" }, + { 135, "S" }, // "b---" + { 0, 0 }, }; static const id_format_pair_t th07_fmts[] = { { 0, "" }, { 1, "" }, - { 2, "So" }, - { 3, "SoS" }, + { 2, "to" }, + { 3, "toS" }, { 4, "SS" }, { 5, "ff" }, - { 6, "SS" }, - { 7, "SSS" }, + { 6, "SU" }, + { 7, "SUS" }, { 8, "ff" }, { 9, "fff" }, { 10, "SS" }, @@ -470,33 +515,35 @@ static const id_format_pair_t th07_fmts[] = { { 20, "fff" }, { 21, "fff" }, { 22, "fff" }, + { 23, "fff" }, { 24, "ff" }, { 25, "ff" }, { 26, "fffff" }, { 27, "fSSSffff" }, - { 28, "SSSo" }, - { 29, "ffSo" }, - { 30, "SSSo" }, - { 31, "ffSo" }, - { 32, "SSSo" }, - { 33, "ffSo" }, - { 34, "SSSo" }, - { 35, "ffSo" }, - { 36, "SSSo" }, - { 37, "ffSo" }, - { 38, "SSSo" }, - { 39, "ffSo" }, + { 28, "SSto" }, + { 29, "ffto" }, + { 30, "SSto" }, + { 31, "ffto" }, + { 32, "SSto" }, + { 33, "ffto" }, + { 34, "SSto" }, + { 35, "ffto" }, + { 36, "SSto" }, + { 37, "ffto" }, + { 38, "SSto" }, + { 39, "ffto" }, { 40, "f" }, - { 41, "S" }, + { 41, "N" }, { 42, "" }, { 43, "SSS" }, { 44, "ffS" }, { 45, "S" }, { 46, "fff" }, - { 47, "ffS" }, + { 47, "fff" }, { 48, "f" }, - { 49, "S" }, + { 49, "f" }, { 50, "f" }, + { 51, "fff" }, { 52, "fff" }, { 53, "ff" }, { 54, "SSff" }, @@ -535,59 +582,66 @@ static const id_format_pair_t th07_fmts[] = { { 87, "Sfff" }, { 88, "S" }, { 89, "S" }, - { 90, "ssm" }, + { 90, "sum" }, { 91, "" }, - { 92, "SfffSSS" }, - { 93, "SfffSSS" }, + { 92, "NfffSSS" }, + { 93, "NfffSSS" }, { 94, "" }, { 95, "S" }, - { 96, "ssssS" }, + { 96, "ssssS" }, // "sssss--" { 97, "SS" }, - { 98, "S" }, + { 98, "S" }, // "cbb-" { 99, "S" }, { 100, "Sffff" }, { 101, "fff" }, - { 102, "S" }, - { 103, "S" }, - { 104, "S" }, + { 102, "S" }, // "b---" + { 103, "S" }, // "b---" + { 104, "S" }, // "b---" { 105, "S" }, - { 106, "S" }, - { 107, "S" }, - { 108, "SS" }, + { 106, "S" }, // "b---" + { 107, "N" }, // "b---" but b is a byte N + { 108, "NS" }, + { 109, "S" }, { 110, "S" }, { 111, "S" }, { 112, "S" }, - { 113, "S" }, + { 113, "N" }, { 114, "S" }, - { 115, "S" }, - { 116, "S" }, - { 117, "SSS" }, - { 118, "SSSfff" }, + { 115, "N" }, + { 116, "S" }, // "b---" + { 117, "SUC" }, + { 118, "SUCfff" }, { 119, "S" }, - { 120, "S" }, + { 120, "S" }, // "b---" { 121, "SS" }, { 122, "SS" }, + { 123, "S" }, { 124, "S" }, { 125, "S" }, { 126, "S" }, + { 127, "S" }, { 128, "S" }, + { 129, "SS" }, // "Ss--" + { 130, "S" }, // "b---" { 131, "ffSSSS" }, - { 132, "S" }, + { 132, "S" }, // "b---" { 133, "" }, - { 135, "S" }, - { 136, "S" }, - { 137, "S" }, - { 138, "SSSS" }, - { 139, "SSSS" }, + { 134, "" }, + { 135, "S" }, // "b---" + { 136, "S" }, // "b---" + { 137, "S" }, // "b---" + { 138, "SSSS" }, // "b---SSS" + { 139, "SSSC" }, { 140, "ffff" }, { 141, "S" }, { 142, "S" }, { 143, "f" }, - { 144, "SS" }, + { 144, "SN" }, { 145, "SS" }, { 146, "" }, - { 148, "SSS" }, - { 149, "SffS" }, + { 147, "S" }, + { 148, "SSN" }, + { 149, "Sfff" }, { 150, "f" }, { 151, "ffff" }, { 152, "Sf" }, @@ -608,10 +662,11 @@ static const id_format_pair_t th08_fmts[] = { { 1, "" }, { 2, "S" }, { 3, "S" }, - { 4, "So" }, - { 5, "SoS" }, + { 4, "to" }, + { 5, "toS" }, { 6, "SS" }, { 7, "ff" }, + { 8, "SS" }, { 9, "ff" }, { 10, "SS" }, { 11, "SS" }, @@ -632,32 +687,38 @@ static const id_format_pair_t th08_fmts[] = { { 26, "fff" }, { 27, "fff" }, { 28, "fff" }, + { 29, "fff" }, { 30, "S" }, { 31, "S" }, { 32, "ff" }, { 33, "ff" }, { 34, "fffff" }, + { 35, "ffff" }, { 36, "fSSSffff" }, { 37, "f" }, { 38, "ffff" }, { 39, "fffff" }, - { 40, "SSSo" }, - { 42, "SSSo" }, - { 44, "SSSo" }, - { 45, "ffSo" }, - { 46, "SSSo" }, - { 47, "ffSo" }, - { 48, "SSSo" }, - { 49, "ffSo" }, - { 50, "SSSo" }, - { 51, "ffSo" }, - { 52, "S" }, + { 40, "SSto" }, + { 41, "ffto" }, + { 42, "SSto" }, + { 43, "ffto" }, + { 44, "SSto" }, + { 45, "ffto" }, + { 46, "SSto" }, + { 47, "ffto" }, + { 48, "SSto" }, + { 49, "ffto" }, + { 50, "SSto" }, + { 51, "ffto" }, + { 52, "N" }, { 53, "" }, { 54, "S" }, { 55, "S" }, + { 56, "SSSSSS" }, { 57, "SS" }, { 58, "S" }, { 59, "S" }, + { 60, "SSSSSS" }, { 61, "SS" }, { 62, "" }, { 63, "ff" }, @@ -665,6 +726,8 @@ static const id_format_pair_t th08_fmts[] = { { 65, "ff" }, { 66, "SSff" }, { 67, "SSf" }, + { 68, "ff" }, + { 69, "SSff" }, { 70, "f" }, { 71, "f" }, { 72, "Sffffff" }, @@ -683,12 +746,13 @@ static const id_format_pair_t th08_fmts[] = { { 85, "" }, { 86, "SSS" }, { 87, "ffS" }, - { 88, "SS" }, - { 90, "SffSSS" }, - { 91, "SffSSS" }, - { 92, "SffSSS" }, - { 93, "SffSSSS" }, - { 94, "SffSSSS" }, + { 88, "SN" }, + { 89, "SS" }, + { 90, "NffSSS" }, + { 91, "NffSSS" }, + { 92, "NffSSS" }, + { 93, "NfffSSS" }, + { 94, "NfffSSS" }, { 95, "" }, { 96, "ssSSffffS" }, { 97, "ssSSffffS" }, @@ -696,63 +760,85 @@ static const id_format_pair_t th08_fmts[] = { { 99, "ssSSffffS" }, { 100, "ssSSffffS" }, { 101, "ssSSffffS" }, + { 102, "ssSSffffS" }, + { 103, "ssSSffffS" }, { 104, "ssSSffffS" }, { 105, "S" }, { 106, "S" }, { 107, "" }, { 108, "" }, + { 109, "" }, { 110, "ff" }, { 111, "SSSSSff" }, { 112, "" }, { 113, "SS" }, { 114, "ssffffffSSSSSS" }, + { 115, "ssffffffSSSSSS" }, { 116, "S" }, { 117, "Sf" }, { 118, "Sf" }, { 119, "Sfff" }, { 120, "S" }, { 121, "S" }, - { 122, "ssSmmmm" }, + { 122, "suSmmmm" }, { 123, "" }, { 124, "S" }, - { 126, "SS" }, + { 125, "S" }, + { 126, "NS" }, { 127, "S" }, { 128, "Sffff" }, - { 129, "S" }, - { 130, "S" }, + { 129, "S" }, // "b---" + { 130, "N" }, // "n--" { 131, "S" }, { 132, "S" }, - { 133, "SSS" }, - { 134, "SS" }, - { 135, "SS" }, + { 133, "SSN" }, + { 134, "SN" }, + { 135, "SN" }, { 136, "SS" }, { 137, "SS" }, - { 139, "SSS" }, - { 140, "SSSfSS" }, + { 138, "S" }, // "cbb-" + { 139, "SUC" }, + { 140, "SUCfff" }, { 141, "S" }, { 142, "S" }, + { 143, "S" }, { 144, "SS" }, - { 145, "S" }, + { 145, "S" }, // "b---" + { 146, "S" }, { 147, "S" }, { 148, "S" }, + { 149, "S" }, + { 150, "SS" }, // "Ss--" + { 151, "S" }, // "b---" { 152, "ffSSSS" }, { 153, "" }, - { 155, "S" }, - { 157, "SSSS" }, - { 158, "SSSS" }, + { 154, "" }, + { 155, "S" }, // "b---" + { 156, "S" }, // "b---" + { 157, "SSSS" }, // "b---SSS" + { 158, "SSSC" }, { 159, "S" }, { 160, "S" }, + { 161, "f" }, { 162, "" }, + { 163, "S" }, + { 164, "Sfff" }, { 165, "f" }, + { 166, "ffff" }, { 167, "Sf" }, { 168, "S" }, + { 169, "f" }, + { 170, "SS" }, + { 171, "Sf" }, + { 172, "Sff" }, { 173, "S" }, { 174, "S" }, { 175, "S" }, - { 176, "S" }, + { 176, "S" }, // "b---" { 177, "S" }, { 178, "SSf" }, { 179, "" }, + { 180, "" }, { 181, "" }, { 182, "S" }, { 183, "S" }, @@ -761,12 +847,6 @@ static const id_format_pair_t th08_fmts[] = { }; static const id_format_pair_t th09_fmts[] = { - { 14, "SS" }, - { 63, "Sf" }, - { 64, "SSSf" }, - { 110, "SS" }, - { 114, "ssffSSffSSSSSS" }, - { 138, "S" }, { 185, "S" }, { 186, "" }, { 187, "S" }, @@ -774,48 +854,65 @@ static const id_format_pair_t th09_fmts[] = { }; static const id_format_pair_t th95_fmts[] = { - { 0, "" }, - { 14, "SS" }, - { 42, "SfSS" }, - { 43, "fffS" }, - { 49, "fffS" }, - { 56, "SSSSSS" }, - { 84, "Sff" }, - { 85, "" }, - { 86, "SSSffffS" }, - { 87, "SSSffffS" }, - { 88, "SSSffffS" }, - { 89, "SSSffffS" }, - { 90, "SSSffffS" }, - { 91, "SSSffffS" }, - { 94, "SSSffffS" }, + { 83, "N" }, + { 84, "Nff" }, // Technically the game reads "Nfff" but all instructions are missing the final argument + { 86, "ssSSffffS" }, + { 87, "ssSSffffS" }, + { 88, "ssSSffffS" }, + { 89, "ssSSffffS" }, + { 90, "ssSSffffS" }, + { 91, "ssSSffffS" }, + { 94, "ssSSffffS" }, + { 95, "S" }, + { 96, "S" }, + { 97, "" }, + { 98, "" }, + { 99, "" }, { 100, "ff" }, { 101, "SSSSSff" }, { 102, "" }, { 103, "SS" }, { 104, "m" }, { 105, "" }, - { 108, "SS" }, + { 106, "" }, + { 108, "NS" }, { 109, "S" }, - { 112, "S" }, + { 112, "N" }, // "n--" + { 113, "S" }, { 114, "S" }, - { 115, "SSS" }, - { 117, "SS" }, + { 115, "SSN" }, + { 116, "SN" }, + { 117, "SN" }, { 118, "S" }, { 119, "S" }, + { 120, "S" }, // "b---" + { 126, "S" }, // "b---" + { 128, "" }, + { 130, "S" }, // "b---" + { 131, "SSSS" }, // "b---SSS" + { 133, "S" }, + { 136, "ffff" }, + { 137, "f" }, + { 138, "S" }, + { 139, "S" }, + { 140, "S" }, { 142, "" }, { 143, "SS" }, { 144, "S" }, { 145, "SSffff" }, { 146, "SSffff" }, { 147, "SSfffSSSSfS" }, + { 148, "SSfffSSSSfS" }, { 149, "f" }, { 150, "S" }, { 151, "SS" }, { 152, "fS" }, { 153, "SSfffSSSSfS" }, - { 155, "SSfffSSSSffff" }, + { 154, "SSfffSSSSfS" }, + { 155, "SSfffSSSSfSff" }, + { 156, "SSfffSSSSfSff" }, { 157, "SSfffSSSSfSff" }, + { 158, "S" }, { 0, 0 }, }; @@ -865,10 +962,10 @@ th06_check_timeline_sentinel( unsigned int version, th06_timeline_instr_t* raw_instr) { - if (version < 8) - return raw_instr->time == 0xffff && raw_instr->arg0 == 4; + if (is_pre_th8(version)) + return raw_instr->time_v1 == -1 && raw_instr->arg0 == 4; else - return raw_instr->time == 0xffff && (uint16_t)raw_instr->arg0 == 0xffff && raw_instr->size == 0x00; + return raw_instr->time_v2 == -1 && raw_instr->size_v2 == 0x00; } static void @@ -933,7 +1030,7 @@ th06_open( return NULL; /* TODO: Check magic. */ - if (version >= 8) + if (!is_pre_th8(version)) header = (th06_header_t*)(map + sizeof(uint32_t)); else header = (th06_header_t*)map; @@ -1068,23 +1165,28 @@ th06_open( strcpy(timeline->name, name); list_init(&timeline->instrs); - uint16_t time = 0; - uint8_t rank = version >= 8 ? 0xff : 0x00; + bool is_timeline_v2 = !is_pre_th8(version); + int32_t time = 0; + uint8_t rank = 0xff; th06_timeline_instr_t* raw_instr = (th06_timeline_instr_t*)(map + header->offsets[e]); while(!th06_check_timeline_sentinel(version, raw_instr)) { - if (raw_instr->rank != rank) { - thecl_instr_t* new = thecl_instr_rank(raw_instr->rank); - list_append_new(&timeline->instrs, new); - rank = raw_instr->rank; + if (is_timeline_v2) { + if (raw_instr->rank != rank) { + thecl_instr_t* new = thecl_instr_rank(raw_instr->rank); + list_append_new(&timeline->instrs, new); + rank = raw_instr->rank; + } } - if (raw_instr->time != time) { - thecl_instr_t* new = thecl_instr_time(raw_instr->time); + int32_t ins_time = is_timeline_v2 ? raw_instr->time_v2 : raw_instr->time_v1; + if (ins_time != time) { + thecl_instr_t* new = thecl_instr_time(ins_time); list_append_new(&timeline->instrs, new); - time = raw_instr->time; + time = ins_time; } thecl_instr_t* instr = thecl_instr_new(); - instr->size = raw_instr->size; + int32_t ins_size = is_timeline_v2 ? raw_instr->size_v2 : raw_instr->size_v1; + instr->size = ins_size; instr->id = raw_instr->id; const char* format = th06_find_timeline_format(version, raw_instr->id); @@ -1092,10 +1194,10 @@ th06_open( if (!format) { fprintf(stderr, "%-3d %04xB: ", raw_instr->id, - raw_instr->size + ins_size ); - for (size_t d = 0; d < raw_instr->size - sizeof(th06_timeline_instr_t); d += 4) { + for (size_t d = 0; d < ins_size - sizeof(th06_timeline_instr_t); d += 4) { fprintf(stderr, " %08x (%d, %f)", *(uint32_t*)(raw_instr->data + d), *(int32_t*)(raw_instr->data + d), @@ -1105,16 +1207,23 @@ th06_open( fprintf(stderr, "\n"); } else { - thecl_param_t* param0 = param_new(format[0]); - param0->value.val.s = raw_instr->arg0; - list_append_new(&instr->params, param0); - - if (raw_instr->size > sizeof(th06_timeline_instr_t)) { - /* The first parameter is in the struct and is always s, so it needs to be ignored here. */ - value_t* values = value_list_from_data(value_from_data, raw_instr->data, raw_instr->size - sizeof(th06_timeline_instr_t), format + 1); + if (!is_timeline_v2) { + switch (format[0]) { + /* Ignore the first parameter if it is small enough to fit inside the struct */ + case 'n': case 's': case 'u': case 'b': case 'c': + { + thecl_param_t* param0 = param_new(format[0]); + param0->value.val.s = raw_instr->arg0; + list_append_new(&instr->params, param0); + ++format; + } + } + } + if (ins_size > sizeof(th06_timeline_instr_t)) { + value_t* values = value_list_from_data(th06_value_from_data, raw_instr->data, ins_size - sizeof(th06_timeline_instr_t), format); value_t* value_iter = values; while (value_iter->type) { - thecl_param_t* param = param_new(value_iter->type); + thecl_param_t* param = param_new(*format++); param->value = *value_iter; ++value_iter; list_append_new(&instr->params, param); @@ -1125,7 +1234,7 @@ th06_open( list_append_new(&timeline->instrs, instr); } - raw_instr = (th06_timeline_instr_t*)((char*)raw_instr + raw_instr->size); + raw_instr = (th06_timeline_instr_t*)((char*)raw_instr + ins_size); } list_append_new(&ecl->timelines, timeline); @@ -1203,7 +1312,7 @@ th06_dump( first = 0; } - char* ret = th06_stringify_param(sub, instr, param, ecl->version); + char* ret = th06_stringify_param(sub->name, instr, param, ecl->version); fprintf(out, "%s", ret); free(ret); } @@ -1219,32 +1328,37 @@ th06_dump( } thecl_timeline_t* timeline; + bool is_timeline_v2 = !is_pre_th8(ecl->version); list_for_each(&ecl->timelines, timeline) { fprintf(out, "\ntimeline %s()\n{\n", timeline->name); thecl_instr_t* instr; - unsigned int time_last = 0; + int32_t time_last = 0; list_for_each(&timeline->instrs, instr) { switch(instr->type) { - case THECL_INSTR_TIME: - if (instr->time != 0xffff) /* The last ins has to always be 0xffff, so let's not make it relative... */ - fprintf(out, "+%u: //%u\n", instr->time - time_last, instr->time); + case THECL_INSTR_TIME: { + int32_t ins_time = is_timeline_v2 ? instr->time : (int16_t)instr->time; + if (ins_time != -1) /* The last ins has to always be -1, so let's not make it relative... */ + fprintf(out, "+%d: //%d\n", ins_time - time_last, ins_time); else - fprintf(out, "%u:\n", instr->time); - time_last = instr->time; + fprintf(out, "%d:\n", ins_time); + time_last = ins_time; break; + } case THECL_INSTR_RANK: - if(instr->rank == 0xFF) fprintf(out, "!*\n"); - else if(instr->rank == 0xF0) fprintf(out, "!-\n"); - else { - fprintf(out, "!%s%s%s%s%s%s%s%s\n", - (instr->rank) & RANK_EASY ? "E" : "", - (instr->rank) & RANK_NORMAL ? "N" : "", - (instr->rank) & RANK_HARD ? "H" : "", - (instr->rank) & RANK_LUNATIC ? "L" : "", - !((instr->rank) & RANK_ID_4) ? "4" : "", - !((instr->rank) & RANK_ID_5) ? "5" : "", - !((instr->rank) & RANK_ID_6) ? "6" : "", - !((instr->rank) & RANK_ID_7) ? "7" : ""); + if (is_timeline_v2) { + if(instr->rank == 0xFF) fprintf(out, "!*\n"); + else if(instr->rank == 0xF0) fprintf(out, "!-\n"); + else { + fprintf(out, "!%s%s%s%s%s%s%s%s\n", + (instr->rank) & RANK_EASY ? "E" : "", + (instr->rank) & RANK_NORMAL ? "N" : "", + (instr->rank) & RANK_HARD ? "H" : "", + (instr->rank) & RANK_LUNATIC ? "L" : "", + !((instr->rank) & RANK_ID_4) ? "4" : "", + !((instr->rank) & RANK_ID_5) ? "5" : "", + !((instr->rank) & RANK_ID_6) ? "6" : "", + !((instr->rank) & RANK_ID_7) ? "7" : ""); + } } break; case THECL_INSTR_INSTR: { @@ -1263,9 +1377,11 @@ th06_dump( first = 0; } - char* ret = th06_stringify_param(NULL, instr, param, ecl->version); - fprintf(out, "%s", ret); - free(ret); + char* ret = th06_stringify_param(timeline->name, instr, param, ecl->version); + if (ret) { + fprintf(out, "%s", ret); + free(ret); + } } fprintf(out, ");\n"); break; @@ -1283,17 +1399,36 @@ th06_timeline_instr_size( unsigned int version, const thecl_instr_t* instr) { - (void)version; size_t ret = sizeof(th06_timeline_instr_t); thecl_param_t* param; - int first = 1; + int first = is_pre_th8(version); list_for_each(&instr->params, param) { /* The first parameter is already a part of the struct. */ - if (first) first = 0; - else { - value_t v = param->value; - v.type = param->type; - ret += value_size(&v); + if (first) { + first = 0; + switch (param->type) { + // All types that fit within arg0 + case 'n': case 's': case 'u': case 'b': case 'c': + continue; + } + } + switch (param->type) { + case 'n': + ret += sizeof(uint16_t); + break; + case 'o': case 't': case 'N': case 'T': + ret += sizeof(uint32_t); + break; + case 'z': + if (g_ecl_encode_cp932) { + ret += utf8_to_cp932_len(param->value.val.z); + break; + } + default: { + value_t v = param->value; + v.type = param->type; + ret += value_size(&v); + } } } @@ -1323,7 +1458,7 @@ th06_instr_size( list_for_each(&instr->params, param) { if (param->type == 'n') { ret += sizeof(uint16_t); - } else if (param->type == 'o' || param->type == 'N') { + } else if (param->type == 'o' || param->type == 't' || param->type == 'N') { ret += sizeof(uint32_t); } else if (param->type == 'z' && g_ecl_encode_cp932) { ret += utf8_to_cp932_len(param->value.val.z); @@ -1356,6 +1491,7 @@ th06_parse( state.has_numeric_difficulties = false; list_init(&state.expressions); list_init(&state.block_stack); + list_init(&state.switch_stack); list_init(&state.global_definitions); state.scope_stack = NULL; state.scope_cnt = 0; @@ -1392,7 +1528,10 @@ th06_find_sub( const thecl_t* ecl, char* name) { - uint32_t n = 0; + if ((uintptr_t)name == UINT32_MAX) { + return -1; + } + int32_t n = 0; thecl_sub_t* sub; list_for_each(&ecl->subs, sub) { if (!strcmp(sub->name, name)) return n; @@ -1402,6 +1541,110 @@ th06_find_sub( return 0; } +static uint32_t +th06_find_timeline( + const thecl_t* ecl, + char* name) +{ + if ((uintptr_t)name == UINT32_MAX) { + return -1; + } + int32_t n = 0; + thecl_sub_t* timeline; + list_for_each(&ecl->timelines, timeline) { + if (!strcmp(timeline->name, name)) return n; + ++n; + } + fprintf(stderr, "%s: timeline not found: %s\n", argv0, name); + return 0; +} + +static ssize_t +th06_serialize_data( + const thecl_t* ecl, + thecl_sub_t* sub, + thecl_instr_t* instr, + thecl_param_t* param, + unsigned char* param_data, + int param_count, + size_t data_size, + bool is_timeline) +{ + value_t v; + switch (param->type) { + case 'n': + if (data_size < sizeof(int16_t)) + return -1; + *(int16_t*)param_data = th06_find_sub(ecl, param->value.val.z); + return sizeof(int16_t); + case 'N': + if (data_size < sizeof(int32_t)) + return -1; + *(int32_t*)param_data = th06_find_sub(ecl, param->value.val.z); + return sizeof(int32_t); + case 'T': + if (data_size < sizeof(int32_t)) + return -1; + *(int32_t*)param_data = th06_find_timeline(ecl, param->value.val.z); + return sizeof(int32_t); + case 'o': + if (data_size < sizeof(int32_t)) + return -1; + *(int32_t*)param_data = label_offset(sub, param->value.val.z); + return sizeof(int32_t); + case 't': + if (data_size < sizeof(int32_t)) + return -1; + *(int32_t*)param_data = param->value.val.S; + return sizeof(int32_t); + case 'z': case 'm': case 'x': + if (!is_timeline) { + char *zstr = param->value.val.z; + if (g_ecl_encode_cp932) + zstr = utf8_to_cp932(malloc(utf8_to_cp932_len(zstr) + 1), zstr); + switch (ecl->version) { + case 6: + strncpy((char*)param_data, zstr, 34); + if (g_ecl_encode_cp932) + free(zstr); + return 34; + case 7: case 95: + strncpy((char*)param_data, zstr, 48); + util_xor(param_data, 48, 0xaa, 0, 0); + if (g_ecl_encode_cp932) + free(zstr); + return 48; + case 8: + switch (param_count) { + case 4: case 5: + strncpy((char*)param_data, zstr, 48); + util_xor(param_data, 48, param_count == 4 ? 0xaa : 0xbb, 0, 0); + if (g_ecl_encode_cp932) + free(zstr); + return 48; + case 6: case 7: + strncpy((char*)param_data, zstr, 64); + util_xor(param_data, 64, param_count == 6 ? 0xdd : 0xee, 0, 0); + if (g_ecl_encode_cp932) + free(zstr); + return 64; + } + break; + } + } + return -1; + case 'S': + //if (data_size == sizeof(int16_t) && param->value.val.S == param->value.val.s) { + //v.type = 's'; + //} else { + default: + v.type = param->type; + //} + v.val = param->value.val; + return value_to_data(&v, param_data, data_size); + } +} + static th06_instr_t* th06_instr_serialize( const thecl_t* ecl, @@ -1416,7 +1659,7 @@ th06_instr_serialize( if (instr->rank != 0xff) { ret->rank_mask = (instr->rank & 0xff) << 8; - if (ecl->version == 6 || ecl->version == 7) + if (is_pre_th8(ecl->version)) ret->rank_mask &= 0x0f00; } else ret->rank_mask = 0xff00; @@ -1433,55 +1676,9 @@ th06_instr_serialize( if (param->stack) ret->param_mask |= 1 << param_count; ++param_count; - if (param->type == 'n') { - uint16_t num = th06_find_sub(ecl, param->value.val.z); - memcpy(param_data, &num, sizeof(uint16_t)); - param_data += sizeof(uint16_t); - } else if (param->type == 'N') { - uint32_t num = th06_find_sub(ecl, param->value.val.z); - memcpy(param_data, &num, sizeof(uint32_t)); - param_data += sizeof(uint32_t); - } else if (param->type == 'o') { - uint32_t label = label_offset(sub, param->value.val.z) - instr->offset; - memcpy(param_data, &label, sizeof(uint32_t)); - param_data += sizeof(uint32_t); - } else if (param->value.type == 'z') { - char *zstr = param->value.val.z; - if (g_ecl_encode_cp932) - zstr = utf8_to_cp932(malloc(utf8_to_cp932_len(zstr)+1), zstr); - if (ecl->version == 6) { - memset(param_data, 0, 34); - strncpy((char*)param_data, zstr, 34); - param_data += 34; - } else if (ecl->version == 7 || ecl->version == 95) { - memset(param_data, 0, 48); - strncpy((char*)param_data, zstr, 48); - util_xor(param_data, 48, 0xaa, 0, 0); - param_data += 48; - } else if (ecl->version == 8) { - switch (param_count) { - case 4: - case 5: - memset(param_data, 0, 48); - strncpy((char*)param_data, zstr, 48); - util_xor(param_data, 48, param_count == 4 ? 0xaa : 0xbb, 0, 0); - param_data += 48; - break; - case 6: - case 7: - memset(param_data, 0, 64); - strncpy((char*)param_data, zstr, 64); - util_xor(param_data, 64, param_count == 6 ? 0xdd : 0xee, 0, 0); - param_data += 64; - break; - } - } - if (g_ecl_encode_cp932) - free(zstr); - } else { - value_t v = param->value; - v.type = param->type; - param_data += value_to_data(&v, param_data, instr->size - (param_data - (unsigned char*)ret)); + ssize_t param_size = th06_serialize_data(ecl, sub, instr, param, param_data, param_count, instr->size - (param_data - (unsigned char*)ret), false); + if (param_size != -1) { + param_data += param_size; } } @@ -1491,32 +1688,43 @@ th06_instr_serialize( static th06_timeline_instr_t* th06_timeline_instr_serialize( const thecl_t* ecl, + thecl_sub_t* timeline, thecl_instr_t* instr) { th06_timeline_instr_t* ret; ret = malloc(instr->size); - ret->time = instr->time; + int first; + if (is_pre_th8(ecl->version)) { + first = 1; + ret->time_v1 = instr->time; + ret->arg0 = 0; // Default value + ret->size_v1 = instr->size; + } else { + first = 0; + ret->time_v2 = instr->time; + ret->size_v2 = instr->size; + ret->rank = instr->rank; + } ret->id = instr->id; - ret->size = instr->size; - ret->rank = ecl->version < 8 ? 0 : instr->rank; thecl_param_t* param; unsigned char* param_data = ret->data; - int first = 1; list_for_each(&instr->params, param) { if (first) { - if (param->type == 'n') - ret->arg0 = th06_find_sub(ecl, param->value.val.z); - else - ret->arg0 = param->value.val.s; first = 0; - } else { - /* TODO: implement other param types if necessary */ - /* Or maybe it would be better to make a function that both this - and th06_instr_serialize would use for writing param data? */ - value_t v = param->value; - v.type = param->type; - param_data += value_to_data(&v, param_data, instr->size - (param_data - (unsigned char*)ret)); + switch (param->type) { + // All types that fit within arg0 + case 'n': case 's': case 'u': + th06_serialize_data(ecl, timeline, instr, param, (unsigned char*)&ret->arg0, 0, sizeof(uint16_t), true); + continue; + case 'b': case 'c': + th06_serialize_data(ecl, timeline, instr, param, (unsigned char*)&ret->arg0, 0, sizeof(uint8_t), true); + continue; + } + } + ssize_t param_size = th06_serialize_data(ecl, timeline, instr, param, param_data, 0, instr->size - (param_data - (unsigned char*)ret), true); + if (param_size != -1) { + param_data += param_size; } } @@ -1597,23 +1805,28 @@ th06_compile( ++s; } - char timeline_sentinel_th6[4] = {0xff, 0xff, 0x04, 0x00}; - char timeline_sentinel_th8[8] = {0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}; + const char timeline_sentinel_th6[4] = {0xff, 0xff, 0x04, 0x00}; + const char timeline_sentinel_th8[8] = {0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}; size_t o = 0; thecl_sub_t* timeline; + bool timeline_v2 = !is_pre_th8(ecl->version); list_for_each(&ecl->timelines, timeline) { offsets[o++] = file_tell(out); thecl_instr_t* instr; list_for_each(&timeline->instrs, instr) { - th06_timeline_instr_t* raw_instr = th06_timeline_instr_serialize(ecl, instr); + th06_timeline_instr_t* raw_instr = th06_timeline_instr_serialize(ecl, timeline, instr); - file_write(out, raw_instr, raw_instr->size); + file_write(out, raw_instr, timeline_v2 ? raw_instr->size_v2 : raw_instr->size_v1); free(raw_instr); } - file_write(out, ecl->version < 8 ? timeline_sentinel_th6 : timeline_sentinel_th8, ecl->version < 8 ? 4 : 8); + if (timeline_v2) { + file_write(out, timeline_sentinel_th8, sizeof(timeline_sentinel_th8)); + } else { + file_write(out, timeline_sentinel_th6, sizeof(timeline_sentinel_th6)); + } } if (ecl->version != 9 && ecl->version != 6) diff --git a/thecl/thecl10.c b/thecl/thecl10.c index 0d875d1..442b6d7 100644 --- a/thecl/thecl10.c +++ b/thecl/thecl10.c @@ -2060,13 +2060,31 @@ th10_instr_size( list_for_each(&instr->params, param) { /* XXX: I think 'z' is what will be passed ... */ - if (param->type == 'm' || param->type == 'x') { - size_t zlen = g_ecl_encode_cp932 ? utf8_to_cp932_len(param->value.val.z) : strlen(param->value.val.z); - ret += sizeof(uint32_t) + zlen + (4 - (zlen % 4)); - } else if (param->type == 'o' || param->type == 't') { - ret += sizeof(uint32_t); - } else { - ret += value_size(¶m->value); + switch (param->type) { + case 'm': case 'x': { + if (param->is_inline_string) { + // Instruction has an inline string param, so + // the size can't be calculated yet. It'll be + // filled in with values once the sub is inlined. + return 0; + } + size_t zlen = g_ecl_encode_cp932 ? utf8_to_cp932_len(param->value.val.z) : strlen(param->value.val.z); + ret += sizeof(uint32_t) + zlen + (4 - (zlen % 4)); + break; + } + case 'o': case 't': + ret += sizeof(uint32_t); + break; + case 'z': + if (param->is_inline_string) { + // Instruction has an inline string param, so + // the size can't be calculated yet. It'll be + // filled in with values once the sub is inlined. + return 0; + } + default: + ret += value_size(¶m->value); + break; } } @@ -2091,6 +2109,7 @@ th10_parse( state.uses_stack_offsets = is_post_th13(version); list_init(&state.expressions); list_init(&state.block_stack); + list_init(&state.switch_stack); list_init(&state.global_definitions); state.scope_stack = NULL; state.scope_cnt = 0;