diff --git a/.gitignore b/.gitignore index d0870c9b..8fd148a6 100644 --- a/.gitignore +++ b/.gitignore @@ -164,6 +164,7 @@ data/iw5/ data/iw6/ data/iw7/ data/iw8/ +data/iw9/ data/h1/ data/h2/ data/s1/ diff --git a/README.md b/README.md index 3f0a94a1..f5bf1a9f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A utility to compile & decompile IW engine game scripts. - **IW6** *(Call of Duty: Ghosts)* `PC` `PS3` `PS4` `Xbox 360` `Xbox One` - **IW7** *(Call of Duty: Infinite Warfare)* `PC` `PS4` `Xbox One` - **IW8** *(Call of Duty: Modern Warfare (2019) / Warzone)* `PC` `PS4` `PS5` `Xbox One` `Xbox Series X|S` +- **IW9** *(Call of Duty: Modern Warfare II (2022) / Warzone 2)* ***\*WIP\**** - **S1** *(Call of Duty: Advanced Warfare)* `PC` `PS3` `PS4` `Xbox 360` `Xbox One` - **S2** *(Call of Duty: WWII)* `PC` `PS4` `Xbox One` - **S4** *(Call of Duty: Vanguard)* `PC` `PS4` `PS5` `Xbox One` `Xbox Series X|S` diff --git a/gen/iw9/Makefile b/gen/iw9/Makefile new file mode 100644 index 00000000..1c14c2f3 --- /dev/null +++ b/gen/iw9/Makefile @@ -0,0 +1,9 @@ +generate: iw9 + +clean: + rm -rf ./parser.hpp + rm -rf ./parser.cpp + +iw9: parser.ypp + bison parser.ypp -Wcounterexamples + mv parser.hpp parser.cpp ../../src/iw9/xsk/ diff --git a/gen/iw9/parser.ypp b/gen/iw9/parser.ypp new file mode 100644 index 00000000..4437b3f5 --- /dev/null +++ b/gen/iw9/parser.ypp @@ -0,0 +1,914 @@ +/* Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. +*/ + +%require "3.7" +%skeleton "lalr1.cc" +%language "c++" +%output "parser.cpp" +%defines "parser.hpp" +%define api.prefix {IW9} +%define api.namespace {xsk::gsc::iw9} +%define api.location.type {xsk::gsc::location} +%define api.value.type variant +%define api.token.constructor +%define api.token.raw +%define parse.assert +%define parse.trace +%define parse.error detailed +%define parse.lac full +%locations +%lex-param { xsk::gsc::iw9::lexer& lexer } +%parse-param { xsk::gsc::iw9::lexer& lexer } +%parse-param { xsk::gsc::ast::program::ptr& ast } + +%code requires +{ +#ifdef _MSC_VER +#pragma warning(disable:4065) +#pragma warning(disable:4127) +#endif +#include "iw9.hpp" +namespace xsk::gsc::iw9 { class lexer; } +} + +%code top +{ +#include "stdafx.hpp" +#include "parser.hpp" +#include "lexer.hpp" +using namespace xsk::gsc; +xsk::gsc::iw9::parser::symbol_type IW9lex(xsk::gsc::iw9::lexer& lexer); +} + +%token SH_DEFINE "#define" +%token SH_UNDEF "#undef" +%token SH_IFDEF "#ifdef" +%token SH_IFNDEF "#ifndef" +%token SH_IF "#if" +%token SH_ELIF "#elif" +%token SH_ELSE "#else" +%token SH_ENDIF "#endif" +%token DEVBEGIN "/#" +%token DEVEND "#/" +%token INLINE "#inline" +%token INCLUDE "#include" +%token USINGTREE "#using_animtree" +%token ANIMTREE "#animtree" +%token ENDON "endon" +%token NOTIFY "notify" +%token WAIT "wait" +%token WAITTILL "waittill" +%token WAITTILLMATCH "waittillmatch" +%token WAITTILLFRAMEEND "waittillframeend" +%token WAITFRAME "waitframe" +%token IF "if" +%token ELSE "else" +%token DO "do" +%token WHILE "while" +%token FOR "for" +%token FOREACH "foreach" +%token IN "in" +%token SWITCH "switch" +%token CASE "case" +%token DEFAULT "default" +%token BREAK "break" +%token CONTINUE "continue" +%token RETURN "return" +%token BREAKPOINT "breakpoint" +%token PROFBEGIN "prof_begin" +%token PROFEND "prof_end" +%token THREAD "thread" +%token CHILDTHREAD "childthread" +%token THISTHREAD "thisthread" +%token CALL "call" +%token TRUE "true" +%token FALSE "false" +%token UNDEFINED "undefined" +%token SIZE "size" +%token GAME "game" +%token SELF "self" +%token ANIM "anim" +%token LEVEL "level" +%token ISDEFINED "isdefined" +%token ISTRUE "istrue" +%token LPAREN "(" +%token RPAREN ")" +%token LBRACE "{" +%token RBRACE "}" +%token LBRACKET "[" +%token RBRACKET "]" +%token COMMA "," +%token DOT "." +%token DOUBLECOLON "::" +%token COLON ":" +%token SEMICOLON ";" +%token QMARK "?" +%token INCREMENT "++" +%token DECREMENT "--" +%token LSHIFT "<<" +%token RSHIFT ">>" +%token OR "||" +%token AND "&&" +%token EQUALITY "==" +%token INEQUALITY "!=" +%token LESS_EQUAL "<=" +%token GREATER_EQUAL ">=" +%token LESS "<" +%token GREATER ">" +%token NOT "!" +%token COMPLEMENT "~" +%token ASSIGN "=" +%token ASSIGN_ADD "+=" +%token ASSIGN_SUB "-=" +%token ASSIGN_MUL "*=" +%token ASSIGN_DIV "/=" +%token ASSIGN_MOD "%=" +%token ASSIGN_BW_OR "|=" +%token ASSIGN_BW_AND "&=" +%token ASSIGN_BW_EXOR "^=" +%token ASSIGN_RSHIFT ">>=" +%token ASSIGN_LSHIFT "<<=" +%token BITWISE_OR "|" +%token BITWISE_AND "&" +%token BITWISE_EXOR "^" +%token ADD "+" +%token SUB "-" +%token MUL "*" +%token DIV "/" +%token MOD "%" +%token PATH "path" +%token IDENTIFIER "identifier" +%token STRING "string literal" +%token ISTRING "localized string" +%token FLOAT "float" +%token INTEGER "integer" + +%type program +%type include +%type declaration +%type decl_usingtree +%type decl_constant +%type decl_thread +%type stmt +%type stmt_or_dev +%type stmt_list +%type stmt_or_dev_list +%type stmt_dev +%type stmt_block +%type stmt_expr +%type stmt_call +%type stmt_assign +%type stmt_endon +%type stmt_notify +%type stmt_wait +%type stmt_waittill +%type stmt_waittillmatch +%type stmt_waittillframeend +%type stmt_waitframe +%type stmt_if +%type stmt_ifelse +%type stmt_while +%type stmt_dowhile +%type stmt_for +%type stmt_foreach +%type stmt_switch +%type stmt_case +%type stmt_default +%type stmt_break +%type stmt_continue +%type stmt_return +%type stmt_breakpoint +%type stmt_prof_begin +%type stmt_prof_end +%type expr +%type expr_or_empty +%type expr_assign +%type expr_increment +%type expr_decrement +%type expr_ternary +%type expr_binary +%type expr_primitive +%type expr_complement +%type expr_negate +%type expr_not +%type expr_call +%type expr_method +%type expr_function +%type expr_pointer +%type expr_add_array +%type expr_parameters +%type expr_arguments +%type expr_arguments_no_empty +%type expr_isdefined +%type expr_istrue +%type expr_reference +%type expr_tuple +%type expr_tuple_arguments +%type expr_tuple_types +%type expr_array +%type expr_field +%type expr_size +%type expr_paren +%type expr_object +%type expr_thisthread +%type expr_empty_array +%type expr_undefined +%type expr_game +%type expr_self +%type expr_anim +%type expr_level +%type expr_animation +%type expr_animtree +%type expr_identifier_nosize +%type expr_identifier +%type expr_path +%type expr_istring +%type expr_string +%type expr_vector +%type expr_float +%type expr_integer +%type expr_false +%type expr_true + +%nonassoc SIZEOF +%nonassoc ADD_ARRAY +%nonassoc RBRACKET +%nonassoc THEN +%nonassoc ELSE +%nonassoc INCREMENT DECREMENT + +%precedence TERN +%right QMARK +%left OR +%left AND +%left BITWISE_OR +%left BITWISE_EXOR +%left BITWISE_AND +%left EQUALITY INEQUALITY +%left LESS GREATER LESS_EQUAL GREATER_EQUAL +%left LSHIFT RSHIFT +%left ADD SUB +%left MUL DIV MOD +%right NOT COMPLEMENT + +%precedence NEG +%precedence ANIMREF +%precedence PREINC PREDEC +%precedence POSTINC POSTDEC + +%start root + +%% + +root + : program { ast = std::move($1); } + | { ast = std::make_unique(@$); } + ; + +program + : program inline + { $$ = std::move($1); } + | program include + { $$ = std::move($1); $$->includes.push_back(std::move($2)); } + | program declaration + { $$ = std::move($1); $$->declarations.push_back(std::move($2)); } + | inline + { $$ = std::make_unique(@$); } + | include + { $$ = std::make_unique(@$); $$->includes.push_back(std::move($1)); } + | declaration + { $$ = std::make_unique(@$); $$->declarations.push_back(std::move($1)); } + ; + +inline + : INLINE expr_path SEMICOLON { lexer.push_header($2->value); } + ; + +include + : INCLUDE expr_path SEMICOLON + { $$ = std::make_unique(@$, std::move($2)); } + ; + +declaration + : DEVBEGIN { $$.as_dev_begin = std::make_unique(@$); } + | DEVEND { $$.as_dev_end = std::make_unique(@$); } + | decl_usingtree { $$.as_usingtree = std::move($1); } + | decl_constant { $$.as_constant = std::move($1); } + | decl_thread { $$.as_thread = std::move($1); } + ; + +decl_usingtree + : USINGTREE LPAREN expr_string RPAREN SEMICOLON + { lexer.ban_header(@$); $$ = std::make_unique(@$, std::move($3)); } + ; + +decl_constant + : expr_identifier ASSIGN expr SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($3)); } + ; + +decl_thread + : expr_identifier LPAREN expr_parameters RPAREN stmt_block + { lexer.ban_header(@$); $$ = std::make_unique(@$, std::move($1), std::move($3), std::move($5)); } + ; + +stmt + : stmt_block { $$.as_list = std::move($1); } + | stmt_call { $$.as_call = std::move($1); } + | stmt_assign { $$.as_assign = std::move($1); } + | stmt_endon { $$.as_endon = std::move($1); } + | stmt_notify { $$.as_notify = std::move($1); } + | stmt_wait { $$.as_wait = std::move($1); } + | stmt_waittill { $$.as_waittill = std::move($1); } + | stmt_waittillmatch { $$.as_waittillmatch = std::move($1); } + | stmt_waittillframeend { $$.as_waittillframeend = std::move($1); } + | stmt_waitframe { $$.as_waitframe = std::move($1); } + | stmt_if { $$.as_if = std::move($1); } + | stmt_ifelse { $$.as_ifelse = std::move($1); } + | stmt_while { $$.as_while = std::move($1); } + | stmt_dowhile { $$.as_dowhile = std::move($1); } + | stmt_for { $$.as_for = std::move($1); } + | stmt_foreach { $$.as_foreach = std::move($1); } + | stmt_switch { $$.as_switch = std::move($1); } + | stmt_case { $$.as_case = std::move($1); } + | stmt_default { $$.as_default = std::move($1); } + | stmt_break { $$.as_break = std::move($1); } + | stmt_continue { $$.as_continue = std::move($1); } + | stmt_return { $$.as_return = std::move($1); } + | stmt_breakpoint { $$.as_breakpoint = std::move($1); } + | stmt_prof_begin { $$.as_prof_begin = std::move($1); } + | stmt_prof_end { $$.as_prof_end = std::move($1); } + ; + +stmt_or_dev + : stmt { $$ = std::move($1); } + | stmt_dev { $$.as_dev = std::move($1); } + ; + +stmt_list + : stmt_list stmt + { $$ = std::move($1); $$->list.push_back(std::move($2)); } + | stmt + { $$ = std::make_unique(@$); $$->list.push_back(std::move($1)); } + ; + +stmt_or_dev_list + : stmt_or_dev_list stmt_or_dev + { $$ = std::move($1); $$->list.push_back(std::move($2)); } + | stmt_or_dev + { $$ = std::make_unique(@$); $$->list.push_back(std::move($1)); } + ; + +stmt_dev + : DEVBEGIN stmt_list DEVEND { $$ = std::make_unique(@$, std::move($2)); } + | DEVBEGIN DEVEND { $$ = std::make_unique(@$, std::make_unique(@$)); } + ; + +stmt_block + : LBRACE stmt_or_dev_list RBRACE { $$ = std::move($2); } + | LBRACE RBRACE { $$ = std::make_unique(@$); } + ; + +stmt_expr + : expr_assign + { $$ = std::make_unique(@$, std::move($1)); } + | expr_increment + { $$ = std::make_unique(@$, std::move($1)); } + | expr_decrement + { $$ = std::make_unique(@$, std::move($1)); } + | + { $$ = std::make_unique(@$, std::make_unique(@$)); } + ; + +stmt_call + : expr_call SEMICOLON + { $$ = std::make_unique(@$, ast::expr(std::move($1))); } + | expr_method SEMICOLON + { $$ = std::make_unique(@$, ast::expr(std::move($1))); } + ; + +stmt_assign + : expr_assign SEMICOLON + { $$ = std::make_unique(@$, std::move($1)); } + | expr_increment SEMICOLON + { $$ = std::make_unique(@$, std::move($1)); } + | expr_decrement SEMICOLON + { $$ = std::make_unique(@$, std::move($1)); } + ; + +stmt_endon + : expr_object ENDON LPAREN expr RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4)); } + ; + +stmt_notify + : expr_object NOTIFY LPAREN expr COMMA expr_arguments_no_empty RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4), std::move($6)); } + | expr_object NOTIFY LPAREN expr RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4), std::make_unique(@$)); } + ; + +stmt_wait + : WAIT expr SEMICOLON + { $$ = std::make_unique(@$, std::move($2)); } + ; + +stmt_waittill + : expr_object WAITTILL LPAREN expr COMMA expr_arguments_no_empty RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4), std::move($6)); } + | expr_object WAITTILL LPAREN expr RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4), std::make_unique(@$)); } + ; + +stmt_waittillmatch + : expr_object WAITTILLMATCH LPAREN expr COMMA expr_arguments_no_empty RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4), std::move($6)); } + | expr_object WAITTILLMATCH LPAREN expr RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($1), std::move($4), std::make_unique(@$)); } + ; + +stmt_waittillframeend + : WAITTILLFRAMEEND SEMICOLON + { $$ = std::make_unique(@$); } + ; + +stmt_waitframe + : WAITFRAME SEMICOLON + { $$ = std::make_unique(@$); } + | WAITFRAME LPAREN RPAREN SEMICOLON + { $$ = std::make_unique(@$); } + ; + +stmt_if + : IF LPAREN expr RPAREN stmt %prec THEN + { $$ = std::make_unique(@$, std::move($3), std::move($5)); } + ; + +stmt_ifelse + : IF LPAREN expr RPAREN stmt ELSE stmt + { $$ = std::make_unique(@$, std::move($3), std::move($5), std::move($7)); } + ; + +stmt_while + : WHILE LPAREN expr RPAREN stmt + { $$ = std::make_unique(@$, std::move($3), std::move($5)); } + ; + +stmt_dowhile + : DO stmt WHILE LPAREN expr RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($5), std::move($2)); } + ; + +stmt_for + : FOR LPAREN stmt_expr SEMICOLON expr_or_empty SEMICOLON stmt_expr RPAREN stmt + { $$ = std::make_unique(@$, ast::stmt(std::move($3)), std::move($5), ast::stmt(std::move($7)), std::move($9)); } + ; + +stmt_foreach + : FOREACH LPAREN expr_identifier IN expr RPAREN stmt + { $$ = std::make_unique(@$, ast::expr(std::move($3)), std::move($5), std::move($7)); } + | FOREACH LPAREN expr_identifier COMMA expr_identifier IN expr RPAREN stmt + { $$ = std::make_unique(@$, ast::expr(std::move($3)), ast::expr(std::move($5)), std::move($7), std::move($9)); } + ; + +stmt_switch + : SWITCH LPAREN expr RPAREN stmt_block + { $$ = std::make_unique(@$, std::move($3), std::move($5)); } + ; + +stmt_case + : CASE expr_integer COLON + { $$ = std::make_unique(@$, ast::expr(std::move($2)), std::make_unique(@$)); } + | CASE expr_string COLON + { $$ = std::make_unique(@$, ast::expr(std::move($2)), std::make_unique(@$)); } + ; + +stmt_default + : DEFAULT COLON + { $$ = std::make_unique(@$, std::make_unique(@$)); } + ; + +stmt_break + : BREAK SEMICOLON + { $$ = std::make_unique(@$); } + ; + +stmt_continue + : CONTINUE SEMICOLON + { $$ = std::make_unique(@$); } + ; + +stmt_return + : RETURN expr SEMICOLON + { $$ = std::make_unique(@$, std::move($2)); } + | RETURN SEMICOLON + { $$ = std::make_unique(@$, std::make_unique(@$)); } + ; + +stmt_breakpoint + : BREAKPOINT SEMICOLON + { $$ = std::make_unique(@$); } + ; + +stmt_prof_begin + : PROFBEGIN LPAREN expr_arguments RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($3)); } + ; + +stmt_prof_end + : PROFEND LPAREN expr_arguments RPAREN SEMICOLON + { $$ = std::make_unique(@$, std::move($3)); } + ; + +expr + : expr_ternary { $$ = std::move($1); } + | expr_binary { $$ = std::move($1); } + | expr_primitive { $$ = std::move($1); } + ; + +expr_or_empty + : expr { $$ = std::move($1); } + | { $$.as_node = std::make_unique(@$); } + ; + +expr_assign + : expr_tuple ASSIGN expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_BW_OR expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_BW_AND expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_BW_EXOR expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_LSHIFT expr + { $$.as_node = std::make_unique(@$, std::move($1),std::move( $3)); } + | expr_object ASSIGN_RSHIFT expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_ADD expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_SUB expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_MUL expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_DIV expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr_object ASSIGN_MOD expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + ; + +expr_increment + : INCREMENT expr_object %prec PREINC + { $$.as_node = std::make_unique(@$, std::move($2), true); } + | expr_object INCREMENT %prec POSTINC + { $$.as_node = std::make_unique(@$, std::move($1), false); } + ; + +expr_decrement + : DECREMENT expr_object %prec PREDEC + { $$.as_node = std::make_unique(@$, std::move($2), true); } + | expr_object DECREMENT %prec POSTDEC + { $$.as_node = std::make_unique(@$, std::move($1), false); } + ; + +expr_ternary + : expr QMARK expr COLON expr %prec TERN + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3), std::move($5)); } + ; + +expr_binary + : expr OR expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr AND expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr EQUALITY expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr INEQUALITY expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr LESS_EQUAL expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr GREATER_EQUAL expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr LESS expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr GREATER expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr BITWISE_OR expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr BITWISE_AND expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr BITWISE_EXOR expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr LSHIFT expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr RSHIFT expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr ADD expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr SUB expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr MUL expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr DIV expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + | expr MOD expr + { $$.as_node = std::make_unique(@$, std::move($1), std::move($3)); } + ; + +expr_primitive + : expr_complement { $$.as_node = std::move($1); } + | expr_negate { $$.as_node = std::move($1); } + | expr_not { $$.as_node = std::move($1); } + | expr_call { $$.as_node = std::move($1); } + | expr_method { $$.as_node = std::move($1); } + | expr_add_array { $$.as_node = std::move($1); } + | expr_isdefined { $$.as_node = std::move($1); } + | expr_istrue { $$.as_node = std::move($1); } + | expr_reference { $$.as_node = std::move($1); } + | expr_array { $$.as_node = std::move($1); } + | expr_field { $$.as_node = std::move($1); } + | expr_size { $$.as_node = std::move($1); } + | expr_paren { $$.as_node = std::move($1); } + | expr_thisthread { $$.as_node = std::move($1); } + | expr_empty_array { $$.as_node = std::move($1); } + | expr_undefined { $$.as_node = std::move($1); } + | expr_game { $$.as_node = std::move($1); } + | expr_self { $$.as_node = std::move($1); } + | expr_anim { $$.as_node = std::move($1); } + | expr_level { $$.as_node = std::move($1); } + | expr_animation { $$.as_node = std::move($1); } + | expr_animtree { $$.as_node = std::move($1); } + | expr_identifier { $$.as_node = std::move($1); } + | expr_istring { $$.as_node = std::move($1); } + | expr_string { $$.as_node = std::move($1); } + | expr_vector { $$.as_node = std::move($1); } + | expr_float { $$.as_node = std::move($1); } + | expr_integer { $$.as_node = std::move($1); } + | expr_false { $$.as_node = std::move($1); } + | expr_true { $$.as_node = std::move($1); } + ; + +expr_complement + : COMPLEMENT expr + { $$ = std::make_unique(@$, std::move($2)); } + ; + +expr_negate + : SUB expr_identifier %prec NEG + { $$ = std::make_unique(@$, ast::expr(std::move($2))); } + | SUB expr_paren %prec NEG + { $$ = std::make_unique(@$, ast::expr(std::move($2))); } + | SUB expr_array %prec NEG + { $$ = std::make_unique(@$, ast::expr(std::move($2))); } + | SUB expr_field %prec NEG + { $$ = std::make_unique(@$, ast::expr(std::move($2))); } + ; + +expr_not + : NOT expr + { $$ = std::make_unique(@$, std::move($2)); } + ; + +expr_call + : expr_function { $$ = std::make_unique(@$, std::move($1)); } + | expr_pointer { $$ = std::make_unique(@$, std::move($1)); } + ; +expr_method + : expr_object expr_function { $$ = std::make_unique(@$, std::move($1), std::move($2)); } + | expr_object expr_pointer { $$ = std::make_unique(@$, std::move($1), std::move($2)); } + ; + +expr_function + : expr_identifier LPAREN expr_arguments RPAREN + { $$.as_function = std::make_unique(@$, std::make_unique(@$), std::move($1), std::move($3), ast::call::mode::normal); } + | expr_path DOUBLECOLON expr_identifier LPAREN expr_arguments RPAREN + { $$.as_function = std::make_unique(@$, std::move($1), std::move($3), std::move($5), ast::call::mode::normal); } + | THREAD expr_identifier LPAREN expr_arguments RPAREN + { $$.as_function = std::make_unique(@$, std::make_unique(@$), std::move($2), std::move($4), ast::call::mode::thread); } + | THREAD expr_path DOUBLECOLON expr_identifier LPAREN expr_arguments RPAREN + { $$.as_function = std::make_unique(@$, std::move($2), std::move($4), std::move($6), ast::call::mode::thread); } + | CHILDTHREAD expr_identifier LPAREN expr_arguments RPAREN + { $$.as_function = std::make_unique(@$, std::make_unique(@$), std::move($2), std::move($4), ast::call::mode::childthread); } + | CHILDTHREAD expr_path DOUBLECOLON expr_identifier LPAREN expr_arguments RPAREN + { $$.as_function = std::make_unique(@$, std::move($2), std::move($4), std::move($6), ast::call::mode::childthread); } + ; + +expr_pointer + : LBRACKET LBRACKET expr RBRACKET RBRACKET LPAREN expr_arguments RPAREN + { $$.as_pointer = std::make_unique(@$, std::move($3), std::move($7), ast::call::mode::normal); } + | THREAD LBRACKET LBRACKET expr RBRACKET RBRACKET LPAREN expr_arguments RPAREN + { $$.as_pointer = std::make_unique(@$, std::move($4), std::move($8), ast::call::mode::thread); } + | CHILDTHREAD LBRACKET LBRACKET expr RBRACKET RBRACKET LPAREN expr_arguments RPAREN + { $$.as_pointer = std::make_unique(@$, std::move($4), std::move($8), ast::call::mode::childthread); } + | CALL LBRACKET LBRACKET expr RBRACKET RBRACKET LPAREN expr_arguments RPAREN + { $$.as_pointer = std::make_unique(@$, std::move($4), std::move($8), ast::call::mode::builtin); } + ; + +expr_add_array + : LBRACKET expr_arguments_no_empty RBRACKET + { $$ = std::make_unique(@$, std::move($2)); } + ; + +expr_parameters + : expr_parameters COMMA expr_identifier + { $$ = std::move($1); $$->list.push_back(std::move($3)); } + | expr_identifier + { $$ = std::make_unique(@$); $$->list.push_back(std::move($1)); } + | + { $$ = std::make_unique(@$); } + ; + +expr_arguments + : expr_arguments_no_empty + { $$ = std::move($1); } + | + { $$ = std::make_unique(@$); } + ; + +expr_arguments_no_empty + : expr_arguments COMMA expr + { $$ = std::move($1); $$->list.push_back(std::move($3)); } + | expr %prec ADD_ARRAY + { $$ = std::make_unique(@$); $$->list.push_back(std::move($1)); } + ; + +expr_isdefined + : ISDEFINED LPAREN expr RPAREN + { $$ = std::make_unique(@$, std::move($3)); } + ; + +expr_istrue + : ISTRUE LPAREN expr RPAREN + { $$ = std::make_unique(@$, std::move($3)); } + ; + +expr_reference + : DOUBLECOLON expr_identifier + { $$ = std::make_unique(@$, std::make_unique(@$), std::move($2)); } + | expr_path DOUBLECOLON expr_identifier + { $$ = std::make_unique(@$, std::move($1), std::move($3)); } + ; + +expr_tuple + : LBRACKET expr_tuple_arguments RBRACKET + { $$.as_node = std::move($2); } + ; + +expr_tuple_arguments + : expr_tuple_arguments COMMA expr_tuple_types + { $$ = std::move($1); $$->list.push_back(std::move($3)); } + | expr_tuple_types + { $$ = std::make_unique(@$); $$->list.push_back(std::move($1)); } + ; + +expr_tuple_types + : expr_array { $$.as_node = std::move($1); } + | expr_field { $$.as_node = std::move($1); } + | expr_identifier { $$.as_node = std::move($1); } + ; + +expr_array + : expr_object LBRACKET expr RBRACKET + { $$ = std::make_unique(@$, std::move($1), std::move($3)); } + ; + +expr_field + : expr_object DOT expr_identifier_nosize + { $$ = std::make_unique(@$, std::move($1), std::move($3)); } + ; + +expr_size + : expr_object DOT SIZE %prec SIZEOF + { $$ = std::make_unique(@$, std::move($1)); } + ; + +expr_paren + : LPAREN expr RPAREN + { $$ = std::make_unique(@$, std::move($2)); } + ; + +expr_object + : expr_call { $$.as_node = std::move($1); } + | expr_method { $$.as_node = std::move($1); } + | expr_array { $$.as_node = std::move($1); } + | expr_field { $$.as_node = std::move($1); } + | expr_game { $$.as_node = std::move($1); } + | expr_self { $$.as_node = std::move($1); } + | expr_anim { $$.as_node = std::move($1); } + | expr_level { $$.as_node = std::move($1); } + | expr_identifier { $$.as_node = std::move($1); } + ; + +expr_thisthread + : THISTHREAD + { $$ = std::make_unique(@$); }; + ; + +expr_empty_array + : LBRACKET RBRACKET + { $$ = std::make_unique(@$); }; + ; + +expr_undefined + : UNDEFINED + { $$ = std::make_unique(@$); }; + ; + +expr_game + : GAME + { $$ = std::make_unique(@$); }; + ; + +expr_self + : SELF + { $$ = std::make_unique(@$); }; + ; + +expr_anim + : ANIM + { $$ = std::make_unique(@$); }; + ; + +expr_level + : LEVEL + { $$ = std::make_unique(@$); }; + ; + +expr_animation + : MOD IDENTIFIER %prec ANIMREF + { $$ = std::make_unique(@$, $2); }; + ; + +expr_animtree + : ANIMTREE + { $$ = std::make_unique(@$); }; + ; + +expr_identifier_nosize + : IDENTIFIER + { $$ = std::make_unique(@$, $1); }; + ; + +expr_identifier + : IDENTIFIER + { $$ = std::make_unique(@$, $1); }; + | SIZE + { $$ = std::make_unique(@$, "size"); }; + ; + +expr_path + : IDENTIFIER + { $$ = std::make_unique(@$, $1); }; + | PATH + { $$ = std::make_unique(@$, $1); }; + ; + +expr_istring + : ISTRING + { $$ = std::make_unique(@$, $1); }; + ; + +expr_string + : STRING + { $$ = std::make_unique(@$, $1); }; + ; + +expr_vector + : LPAREN expr COMMA expr COMMA expr RPAREN + { $$ = std::make_unique(@$, std::move($2), std::move($4), std::move($6)); }; + ; + +expr_float + : SUB FLOAT %prec NEG + { $$ = std::make_unique(@$, "-" + $2); }; + | FLOAT + { $$ = std::make_unique(@$, $1); }; + ; + +expr_integer + : SUB INTEGER %prec NEG + { $$ = std::make_unique(@$, "-" + $2); }; + | INTEGER + { $$ = std::make_unique(@$, $1); }; + ; + +expr_false + : FALSE + { $$ = std::make_unique(@$); }; + ; + +expr_true + : TRUE + { $$ = std::make_unique(@$); }; + ; + +%% + +void xsk::gsc::iw9::parser::error(const xsk::gsc::location& loc, const std::string& msg) +{ + throw xsk::gsc::comp_error(loc, msg); +} diff --git a/premake5.lua b/premake5.lua index 0c03f26b..e39529c4 100644 --- a/premake5.lua +++ b/premake5.lua @@ -57,6 +57,7 @@ project "xsk-gsc-tool" dependson "xsk-gsc-iw6" dependson "xsk-gsc-iw7" dependson "xsk-gsc-iw8" + dependson "xsk-gsc-iw9" dependson "xsk-gsc-s1" dependson "xsk-gsc-s2" dependson "xsk-gsc-s4" @@ -82,6 +83,7 @@ project "xsk-gsc-tool" "xsk-gsc-iw6", "xsk-gsc-iw7", "xsk-gsc-iw8", + "xsk-gsc-iw9", "xsk-gsc-s1", "xsk-gsc-s2", "xsk-gsc-s4", @@ -226,6 +228,24 @@ project "xsk-gsc-iw8" "./src" } +project "xsk-gsc-iw9" + kind "StaticLib" + language "C++" + + pchheader "stdafx.hpp" + pchsource "src/iw9/stdafx.cpp" + + files { + "./src/iw9/**.h", + "./src/iw9/**.hpp", + "./src/iw9/**.cpp" + } + + includedirs { + "./src/iw9", + "./src" + } + project "xsk-gsc-s1" kind "StaticLib" language "C++" diff --git a/src/iw9/stdafx.cpp b/src/iw9/stdafx.cpp new file mode 100644 index 00000000..1f373bf1 --- /dev/null +++ b/src/iw9/stdafx.cpp @@ -0,0 +1,6 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#include "stdafx.hpp" diff --git a/src/iw9/stdafx.hpp b/src/iw9/stdafx.hpp new file mode 100644 index 00000000..19f22f43 --- /dev/null +++ b/src/iw9/stdafx.hpp @@ -0,0 +1,26 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; diff --git a/src/iw9/xsk/context.cpp b/src/iw9/xsk/context.cpp new file mode 100644 index 00000000..6b837d4d --- /dev/null +++ b/src/iw9/xsk/context.cpp @@ -0,0 +1,23 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#include "stdafx.hpp" +#include "iw9.hpp" + +namespace xsk::gsc::iw9 +{ + +void context::init(build mode, read_cb_type callback) +{ + compiler_.mode(mode); + resolver::init(callback); +} + +void context::cleanup() +{ + resolver::cleanup(); +} + +} // namespace xsk::gsc::iw9 diff --git a/src/iw9/xsk/context.hpp b/src/iw9/xsk/context.hpp new file mode 100644 index 00000000..6dc76419 --- /dev/null +++ b/src/iw9/xsk/context.hpp @@ -0,0 +1,25 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#pragma once + +namespace xsk::gsc::iw9 +{ + +class context : public gsc::context +{ + iw9::disassembler disassembler_; + +public: + void init(build mode, read_cb_type callback); + void cleanup(); + + auto assembler() -> gsc::assembler& { throw std::runtime_error("not impl"); } + auto disassembler() -> gsc::disassembler& { return disassembler_; } + auto compiler() -> gsc::compiler& { throw std::runtime_error("not impl"); } + auto decompiler() -> gsc::decompiler& { throw std::runtime_error("not impl"); } +}; + +} // namespace xsk::gsc::iw9 diff --git a/src/iw9/xsk/disassembler.cpp b/src/iw9/xsk/disassembler.cpp new file mode 100644 index 00000000..68cefc86 --- /dev/null +++ b/src/iw9/xsk/disassembler.cpp @@ -0,0 +1,393 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#include "stdafx.hpp" +#include "iw9.hpp" + +namespace xsk::gsc::iw9 +{ + +auto disassembler::output() -> std::vector +{ + return std::move(functions_); +} + +auto disassembler::output_data() -> std::vector +{ + output_ = std::make_unique(0x100000); + + output_->write_string("// IW9 GSC ASSEMBLY\n"); + output_->write_string("// Disassembled by https://github.com/xensik/gsc-tool\n"); + + for (const auto& func : functions_) + { + print_function(func); + } + + std::vector output; + + output.resize(output_->pos()); + std::memcpy(output.data(), output_->buffer().data(), output.size()); + + return output; +} + +void disassembler::disassemble(const std::string& file, std::vector& script, std::vector& stack) +{ + filename_ = file; + script_ = std::make_unique(script); + stack_ = std::make_unique(stack); + functions_.clear(); + + script_->seek(1); + + while (stack_->is_avail() && script_->is_avail()) + { + functions_.push_back(std::make_unique()); + const auto& func = functions_.back(); + + func->index = static_cast(script_->pos()); + func->size = stack_->read(); + func->id = stack_->read(); + func->name = func->id == 0 ? decrypt_string(stack_->read_c_string()) : resolver::token_name(func->id); + + dissasemble_function(func); + + func->labels = labels_; + labels_.clear(); + } + + resolve_local_functions(); +} + +void disassembler::dissasemble_function(const function::ptr& func) +{ + auto size = func->size; + + while (size > 0) + { + func->instructions.push_back(std::make_unique()); + + const auto& inst = func->instructions.back(); + inst->index = static_cast(script_->pos()); + inst->opcode = script_->read(); + inst->size = opcode_size(inst->opcode); + + dissasemble_instruction(inst); + + size -= inst->size; + } +} + +void disassembler::dissasemble_instruction(const instruction::ptr& inst) +{ + switch (static_cast(inst->opcode)) + { + + default: + throw disasm_error(utils::string::va("unhandled opcode 0x%X at index '%04X'!", inst->opcode, inst->index)); + } +} + +void disassembler::disassemble_builtin_call(const instruction::ptr& inst, bool method, bool args) +{ + if (args) + { + inst->data.push_back(utils::string::va("%i", script_->read())); + } + + const auto id = script_->read(); + const auto name = method ? resolver::method_name(id) : resolver::function_name(id); + inst->data.emplace(inst->data.begin(), name); +} + +void disassembler::disassemble_local_call(const instruction::ptr& inst, bool thread) +{ + const auto offset = disassemble_offset(); + + inst->data.push_back(utils::string::va("%X", offset + inst->index + 1)); + + if (thread) + { + inst->data.push_back(utils::string::va("%i", script_->read())); + } +} + +void disassembler::disassemble_far_call(const instruction::ptr& inst, bool thread) +{ + script_->seek(3); + + if (thread) + { + inst->data.push_back(utils::string::va("%i", script_->read())); + } + + const auto file_id = stack_->read(); + const auto file_name = file_id == 0 ? decrypt_string(stack_->read_c_string()) : resolver::token_name(file_id); + const auto func_id = stack_->read(); + const auto func_name = func_id == 0 ? decrypt_string(stack_->read_c_string()) : resolver::token_name(func_id); + + inst->data.emplace(inst->data.begin(), func_name); + inst->data.emplace(inst->data.begin(), file_name); +} + +void disassembler::disassemble_switch(const instruction::ptr& inst) +{ + const auto addr = inst->index + 4 + script_->read(); + const auto label = utils::string::va("loc_%X", addr); + + inst->data.push_back(label); + labels_.insert({ addr, label }); +} + +void disassembler::disassemble_end_switch(const instruction::ptr& inst) +{ + const auto count = script_->read(); + inst->data.push_back(utils::string::va("%i", count)); + + std::uint32_t index = inst->index + 3; + + if (count) + { + for (auto i = count; i > 0; i--) + { + const auto value = script_->read(); + + if (value < 0x100000 && value > 0) + { + inst->data.push_back("case"); + inst->data.push_back(utils::string::quote(decrypt_string(stack_->read_c_string()), false)); + } + else if (value == 0) + { + inst->data.push_back("default"); + stack_->read_c_string(); // this should be always [0x01 0x00] unencrypted + } + else + { + inst->data.push_back("case"); + inst->data.push_back(utils::string::va("%i", (value - 0x800000) & 0xFFFFFF)); + } + + index += 4; + + const auto addr = disassemble_offset() + index; + const auto label = utils::string::va("loc_%X", addr); + + inst->data.push_back(label); + labels_.insert({ addr, label }); + + index += 3; + inst->size += 7; + } + } +} + +void disassembler::disassemble_field_variable(const instruction::ptr& inst) +{ + const auto id = script_->read(); + std::string name; + + if (id > max_string_id) + { + auto temp = stack_->read(); + name = temp == 0 ? decrypt_string(stack_->read_c_string()) : std::to_string(temp); + } + else + { + name = resolver::token_name(id); + } + + inst->data.push_back(name); +} + +void disassembler::disassemble_formal_params(const instruction::ptr& inst) +{ + const auto count = script_->read(); + + inst->size += count; + inst->data.push_back(utils::string::va("%i", count)); + + for (auto i = 0u; i < count; i++) + { + inst->data.push_back(utils::string::va("%d", script_->read())); + } +} + +void disassembler::disassemble_jump(const instruction::ptr& inst, bool expr, bool back) +{ + std::int32_t addr; + + if (expr) + { + addr = inst->index + 3 + script_->read(); + } + else if (back) + { + addr = inst->index + 3 - script_->read(); + } + else + { + addr = inst->index + 5 + script_->read(); + } + + const auto label = utils::string::va("loc_%X", addr); + + inst->data.push_back(label); + labels_.insert({ addr, label }); +} + +auto disassembler::disassemble_offset() -> std::int32_t +{ + std::array bytes = {}; + + for (auto i = 0; i < 3; i++) + { + bytes[i] = script_->read(); + } + + auto offset = *reinterpret_cast(bytes.data()); + + offset = (offset << 8) >> 8; + + return offset; +} + +void disassembler::resolve_local_functions() +{ + for (const auto& func : functions_) + { + for (const auto& inst : func->instructions) + { + switch (static_cast(inst->opcode)) + { + /*case opcode::OP_GetLocalFunction: + case opcode::OP_ScriptLocalFunctionCall: + case opcode::OP_ScriptLocalFunctionCall2: + case opcode::OP_ScriptLocalMethodCall: + case opcode::OP_ScriptLocalThreadCall: + case opcode::OP_ScriptLocalChildThreadCall: + case opcode::OP_ScriptLocalMethodThreadCall: + case opcode::OP_ScriptLocalMethodChildThreadCall: + inst->data[0] = resolve_function(inst->data[0]); + break;*/ + default: + break; + } + } + } +} + +auto disassembler::resolve_function(const std::string& index) -> std::string +{ + if (utils::string::is_hex_number(index)) + { + std::uint32_t idx = std::stoul(index, nullptr, 16); + + for (const auto& func : functions_) + { + if (func->index == idx) + { + return func->name; + } + } + + throw disasm_error(utils::string::va("couldn't resolve function name at index '0x%04X'!", idx)); + } + + throw disasm_error(utils::string::va("\"%s\" is not valid function address!", index.data())); +} + +auto disassembler::decrypt_string(const std::string& str) -> std::string +{ + if (str.size() > 0 && ((static_cast(str[0]) & 0xC0) == 0x80)) + { + std::string data = "_encstr_"; + + for (auto i = 0u; i < str.size(); i++) + { + data = utils::string::va("%s%02X", data.data(), static_cast(str[i])); + } + + return data; + } + + return str; +} + +void disassembler::print_function(const function::ptr& func) +{ + output_->write_string("\n"); + output_->write_string(utils::string::va("sub_%s\n", func->name.data())); + + for (const auto& inst : func->instructions) + { + const auto itr = func->labels.find(inst->index); + + if (itr != func->labels.end()) + { + output_->write_string(utils::string::va("\t%s\n", itr->second.data())); + } + + print_instruction(inst); + } + + output_->write_string(utils::string::va("end_%s\n", func->name.data())); +} + +void disassembler::print_instruction(const instruction::ptr& inst) +{ + output_->write_string(utils::string::va("\t\t%s", resolver::opcode_name(inst->opcode).data())); + + switch (static_cast(inst->opcode)) + { + /*case opcode::OP_GetLocalFunction: + case opcode::OP_ScriptLocalFunctionCall: + case opcode::OP_ScriptLocalFunctionCall2: + case opcode::OP_ScriptLocalMethodCall: + output_->write_string(utils::string::va(" sub_%s", inst->data[0].data())); + break; + case opcode::OP_ScriptLocalThreadCall: + case opcode::OP_ScriptLocalChildThreadCall: + case opcode::OP_ScriptLocalMethodThreadCall: + case opcode::OP_ScriptLocalMethodChildThreadCall: + output_->write_string(utils::string::va(" sub_%s %s\n", inst->data[0].data(), inst->data[1].data())); + break; + case opcode::OP_endswitch: + output_->write_string(utils::string::va(" %s\n", inst->data[0].data())); + { + std::uint32_t totalcase = std::stoul(inst->data[0]); + auto index = 0; + for (auto casenum = 0u; casenum < totalcase; casenum++) + { + if (inst->data[1 + index] == "case") + { + output_->write_string(utils::string::va("\t\t\t%s %s %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data(), inst->data[1 + index + 2].data())); + index += 3; + } + else if (inst->data[1 + index] == "default") + { + output_->write_string(utils::string::va("\t\t\t%s %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data())); + index += 2; + } + if (casenum != totalcase - 1) + { + output_->write_string("\n"); + } + } + } + break;*/ + default: + for (auto& data : inst->data) + { + output_->write_string(utils::string::va(" %s", data.data())); + } + break; + } + + output_->write_string("\n"); +} + +} // namespace xsk::gsc::iw9 diff --git a/src/iw9/xsk/disassembler.hpp b/src/iw9/xsk/disassembler.hpp new file mode 100644 index 00000000..e6bffc15 --- /dev/null +++ b/src/iw9/xsk/disassembler.hpp @@ -0,0 +1,44 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#pragma once + +namespace xsk::gsc::iw9 +{ + +class disassembler : public gsc::disassembler +{ + std::string filename_; + utils::byte_buffer::ptr script_; + utils::byte_buffer::ptr stack_; + utils::byte_buffer::ptr output_; + std::vector functions_; + std::unordered_map labels_; + +public: + auto output() -> std::vector; + auto output_data() -> std::vector; + void disassemble(const std::string& file, std::vector& script, std::vector& stack); + +private: + void dissasemble_function(const function::ptr& func); + void dissasemble_instruction(const instruction::ptr& inst); + void disassemble_builtin_call(const instruction::ptr& inst, bool method, bool args); + void disassemble_local_call(const instruction::ptr& inst, bool thread); + void disassemble_far_call(const instruction::ptr& inst, bool thread); + void disassemble_switch(const instruction::ptr& inst); + void disassemble_end_switch(const instruction::ptr& inst); + void disassemble_field_variable(const instruction::ptr& inst); + void disassemble_formal_params(const instruction::ptr& inst); + void disassemble_jump(const instruction::ptr& inst, bool expr, bool back); + auto disassemble_offset() -> std::int32_t; + void resolve_local_functions(); + auto resolve_function(const std::string& index) -> std::string; + auto decrypt_string(const std::string& str) -> std::string; + void print_function(const function::ptr& func); + void print_instruction(const instruction::ptr& inst); +}; + +} // namespace xsk::gsc::iw9 diff --git a/src/iw9/xsk/iw9.cpp b/src/iw9/xsk/iw9.cpp new file mode 100644 index 00000000..7e6ecd04 --- /dev/null +++ b/src/iw9/xsk/iw9.cpp @@ -0,0 +1,22 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#include "stdafx.hpp" +#include "iw9.hpp" + +namespace xsk::gsc::iw9 +{ + +auto opcode_size(std::uint8_t id) -> std::uint32_t +{ + switch (static_cast(id)) + { + + default: + throw error("couldn't resolve instruction size for " + std::to_string(id)); + } +} + +} // namespace xsk::gsc::iw9 diff --git a/src/iw9/xsk/iw9.hpp b/src/iw9/xsk/iw9.hpp new file mode 100644 index 00000000..91d1cc3c --- /dev/null +++ b/src/iw9/xsk/iw9.hpp @@ -0,0 +1,193 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#pragma once + +#include "utils/xsk/utils.hpp" + +#include "disassembler.hpp" +#include "resolver.hpp" +#include "context.hpp" + +namespace xsk::gsc::iw9 +{ + +constexpr std::uint32_t max_string_id = 0x1472F; + +enum class opcode : std::uint8_t +{ + unk_000 = 0x00, + unk_001 = 0x01, + unk_002 = 0x02, + unk_003 = 0x03, + unk_004 = 0x04, + unk_005 = 0x05, + unk_006 = 0x06, + unk_007 = 0x07, + unk_008 = 0x08, + unk_009 = 0x09, + unk_010 = 0x0A, + unk_011 = 0x0B, + unk_012 = 0x0C, + unk_013 = 0x0D, + unk_014 = 0x0E, + unk_015 = 0x0F, + unk_016 = 0x10, + unk_017 = 0x11, + unk_018 = 0x12, + unk_019 = 0x13, + unk_020 = 0x14, + unk_021 = 0x15, + unk_022 = 0x16, + unk_023 = 0x17, + unk_024 = 0x18, + unk_025 = 0x19, + unk_026 = 0x1A, + unk_027 = 0x1B, + unk_028 = 0x1C, + unk_029 = 0x1D, + unk_030 = 0x1E, + unk_031 = 0x1F, + unk_032 = 0x20, + unk_033 = 0x21, + unk_034 = 0x22, + unk_035 = 0x23, + unk_036 = 0x24, + unk_037 = 0x25, + unk_038 = 0x26, + unk_039 = 0x27, + unk_040 = 0x28, + unk_041 = 0x29, + unk_042 = 0x2A, + unk_043 = 0x2B, + unk_044 = 0x2C, + unk_045 = 0x2D, + unk_046 = 0x2E, + unk_047 = 0x2F, + unk_048 = 0x30, + unk_049 = 0x31, + unk_050 = 0x32, + unk_051 = 0x33, + unk_052 = 0x34, + unk_053 = 0x35, + unk_054 = 0x36, + unk_055 = 0x37, + unk_056 = 0x38, + unk_057 = 0x39, + unk_058 = 0x3A, + unk_059 = 0x3B, + unk_060 = 0x3C, + unk_061 = 0x3D, + unk_062 = 0x3E, + unk_063 = 0x3F, + unk_064 = 0x40, + unk_065 = 0x41, + unk_066 = 0x42, + unk_067 = 0x43, + unk_068 = 0x44, + unk_069 = 0x45, + unk_070 = 0x46, + unk_071 = 0x47, + unk_072 = 0x48, + unk_073 = 0x49, + unk_074 = 0x4A, + unk_075 = 0x4B, + unk_076 = 0x4C, + unk_077 = 0x4D, + unk_078 = 0x4E, + unk_079 = 0x4F, + unk_080 = 0x50, + unk_081 = 0x51, + unk_082 = 0x52, + unk_083 = 0x53, + unk_084 = 0x54, + unk_085 = 0x55, + unk_086 = 0x56, + unk_087 = 0x57, + unk_088 = 0x58, + unk_089 = 0x59, + unk_090 = 0x5A, + unk_091 = 0x5B, + unk_092 = 0x5C, + unk_093 = 0x5D, + unk_094 = 0x5E, + unk_095 = 0x5F, + unk_096 = 0x60, + unk_097 = 0x61, + unk_098 = 0x62, + unk_099 = 0x63, + unk_100 = 0x64, + unk_101 = 0x65, + unk_102 = 0x66, + unk_103 = 0x67, + unk_104 = 0x68, + unk_105 = 0x69, + unk_106 = 0x6A, + unk_107 = 0x6B, + unk_108 = 0x6C, + unk_109 = 0x6D, + unk_110 = 0x6E, + unk_111 = 0x6F, + unk_112 = 0x70, + unk_113 = 0x71, + unk_114 = 0x72, + unk_115 = 0x73, + unk_116 = 0x74, + unk_117 = 0x75, + unk_118 = 0x76, + unk_119 = 0x77, + unk_120 = 0x78, + unk_121 = 0x79, + unk_122 = 0x7A, + unk_123 = 0x7B, + unk_124 = 0x7C, + unk_125 = 0x7D, + unk_126 = 0x7E, + unk_127 = 0x7F, + unk_128 = 0x80, + unk_129 = 0x81, + unk_130 = 0x82, + unk_131 = 0x83, + unk_132 = 0x84, + unk_133 = 0x85, + unk_134 = 0x86, + unk_135 = 0x87, + unk_136 = 0x88, + unk_137 = 0x89, + unk_138 = 0x8A, + unk_139 = 0x8B, + unk_140 = 0x8C, + unk_141 = 0x8D, + unk_142 = 0x8E, + unk_143 = 0x8F, + unk_144 = 0x90, + unk_145 = 0x91, + unk_146 = 0x92, + unk_147 = 0x93, + unk_148 = 0x94, + unk_149 = 0x95, + unk_150 = 0x96, + unk_151 = 0x97, + unk_152 = 0x98, + unk_153 = 0x99, + unk_154 = 0x9A, + unk_155 = 0x9B, + unk_156 = 0x9C, + unk_157 = 0x9D, + unk_158 = 0x9E, + unk_159 = 0x9F, + unk_160 = 0xA0, + unk_161 = 0xA1, + unk_162 = 0xA2, + unk_163 = 0xA3, + unk_164 = 0xA4, + unk_165 = 0xA5, + unk_166 = 0xA6, + OP_count = 0xA7, +}; + +auto opcode_size(std::uint8_t op) -> std::uint32_t; + +} // namespace xsk::gsc::iw9 diff --git a/src/iw9/xsk/resolver.cpp b/src/iw9/xsk/resolver.cpp new file mode 100644 index 00000000..281505ba --- /dev/null +++ b/src/iw9/xsk/resolver.cpp @@ -0,0 +1,530 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#include "stdafx.hpp" +#include "iw9.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4244) +#endif + +namespace xsk::gsc::iw9 +{ + +std::unordered_map opcode_map; +std::unordered_map function_map; +std::unordered_map method_map; +std::unordered_map token_map; +std::unordered_map opcode_map_rev; +std::unordered_map function_map_rev; +std::unordered_map method_map_rev; +std::unordered_map token_map_rev; +std::unordered_map> files; +read_cb_type read_callback = nullptr; +std::set string_map; + +void resolver::init(read_cb_type callback) +{ + read_callback = callback; +} + +void resolver::cleanup() +{ + files.clear(); +} + +auto resolver::opcode_id(const std::string& name) -> std::uint8_t +{ + const auto itr = opcode_map_rev.find(name); + + if (itr != opcode_map_rev.end()) + { + return itr->second; + } + + throw error(utils::string::va("couldn't resolve opcode id for name '%s'!", name.data())); +} + +auto resolver::opcode_name(std::uint8_t id) -> std::string +{ + const auto itr = opcode_map.find(id); + + if (itr != opcode_map.end()) + { + return std::string(itr->second); + } + + throw error(utils::string::va("couldn't resolve opcode name for id '0x%hhX'!", id)); +} + +auto resolver::function_id(const std::string& name) -> std::uint16_t +{ + if (name.starts_with("_func_")) + { + return static_cast(std::stoul(name.substr(6), nullptr, 16)); + } + + const auto itr = function_map_rev.find(name); + + if (itr != function_map_rev.end()) + { + return itr->second; + } + + throw error(utils::string::va("couldn't resolve builtin function id for name '%s'!", name.data())); +} + +auto resolver::function_name(std::uint16_t id) -> std::string +{ + const auto itr = function_map.find(id); + + if (itr != function_map.end()) + { + return std::string(itr->second); + } + + return utils::string::va("_func_%04X", id); +} + +auto resolver::method_id(const std::string& name) -> std::uint16_t +{ + if (name.starts_with("_meth_")) + { + return static_cast(std::stoul(name.substr(6), nullptr, 16)); + } + + const auto itr = method_map_rev.find(name); + + if (itr != method_map_rev.end()) + { + return itr->second; + } + + throw error(utils::string::va("couldn't resolve builtin method id for name '%s'!", name.data())); +} + +auto resolver::method_name(std::uint16_t id) -> std::string +{ + const auto itr = method_map.find(id); + + if (itr != method_map.end()) + { + return std::string(itr->second); + } + + return utils::string::va("_meth_%04X", id); +} + +auto resolver::token_id(const std::string& name) -> std::uint32_t +{ + if (name.starts_with("_id_")) + { + return static_cast(std::stoul(name.substr(4), nullptr, 16)); + } + + const auto itr = token_map_rev.find(name); + + if (itr != token_map_rev.end()) + { + return itr->second; + } + + return 0; +} + +auto resolver::token_name(std::uint32_t id) -> std::string +{ + const auto itr = token_map.find(id); + + if (itr != token_map.end()) + { + return std::string(itr->second); + } + + return utils::string::va("_id_%04X", id); +} + +auto resolver::find_function(const std::string& name) -> bool +{ + if (name.starts_with("_func_")) return true; + + const auto itr = function_map_rev.find(name); + + if (itr != function_map_rev.end()) + { + return true; + } + + return false; +} + +auto resolver::find_method(const std::string& name) -> bool +{ + if (name.starts_with("_meth_")) return true; + + const auto itr = method_map_rev.find(name); + + if (itr != method_map_rev.end()) + { + return true; + } + + return false; +} + +void resolver::add_function(const std::string& name, std::uint16_t id) +{ + const auto itr = function_map_rev.find(name); + + if (itr != function_map_rev.end()) + { + throw error(utils::string::va("builtin function '%s' already defined.", name.data())); + } + + const auto str = string_map.find(name); + + if (str != string_map.end()) + { + function_map.insert({ id, *str }); + function_map_rev.insert({ *str, id }); + } + else + { + auto ins = string_map.insert(name); + + if (ins.second) + { + function_map.insert({ id, *ins.first }); + function_map_rev.insert({ *ins.first, id }); + } + } +} + +void resolver::add_method(const std::string& name, std::uint16_t id) +{ + const auto itr = method_map_rev.find(name); + + if (itr != method_map_rev.end()) + { + throw error(utils::string::va("builtin method '%s' already defined.", name.data())); + } + + const auto str = string_map.find(name); + + if (str != string_map.end()) + { + method_map.insert({ id, *str }); + method_map_rev.insert({ *str, id }); + } + else + { + auto ins = string_map.insert(name); + + if (ins.second) + { + method_map.insert({ id, *ins.first }); + method_map_rev.insert({ *ins.first, id }); + } + } +} + +auto resolver::make_token(std::string_view str) -> std::string +{ + if (str.starts_with("_id_") || str.starts_with("_func_") || str.starts_with("_meth_")) + { + return std::string(str); + } + + auto data = std::string(str.begin(), str.end()); + + for (std::size_t i = 0; i < data.size(); i++) + { + data[i] = static_cast(std::tolower(static_cast(str[i]))); + if (data[i] == '\\') data[i] = '/'; + } + + return data; +} + +auto resolver::file_data(const std::string& name) -> std::tuple +{ + const auto itr = files.find(name); + + if (itr != files.end()) + { + return { &itr->first ,reinterpret_cast(itr->second.data()), itr->second.size() }; + } + + auto data = read_callback(name); + + const auto res = files.insert({ name, std::move(data)}); + + if (res.second) + { + return { &res.first->first, reinterpret_cast(res.first->second.data()), res.first->second.size() }; + } + + throw error("couldn't open gsc file '" + name + "'"); +} + +std::set paths +{ +}; + +auto resolver::fs_to_game_path(const std::filesystem::path& file) -> std::filesystem::path +{ + auto result = std::filesystem::path(); + auto root = false; + + for (auto& entry : file) + { + if (!root && paths.contains(entry.string())) + { + result = entry; + root = true; + } + else if (paths.contains(result.string())) + { + result /= entry; + } + } + + return result.empty() ? file : result; +} + +const std::array, 167> opcode_list +{{ + { 0x00, "unk_000" }, + { 0x01, "unk_001" }, + { 0x02, "unk_002" }, + { 0x03, "unk_003" }, + { 0x04, "unk_004" }, + { 0x05, "unk_005" }, + { 0x06, "unk_006" }, + { 0x07, "unk_007" }, + { 0x08, "unk_008" }, + { 0x09, "unk_009" }, + { 0x0A, "unk_010" }, + { 0x0B, "unk_011" }, + { 0x0C, "unk_012" }, + { 0x0D, "unk_013" }, + { 0x0E, "unk_014" }, + { 0x0F, "unk_015" }, + { 0x10, "unk_016" }, + { 0x11, "unk_017" }, + { 0x12, "unk_018" }, + { 0x13, "unk_019" }, + { 0x14, "unk_020" }, + { 0x15, "unk_021" }, + { 0x16, "unk_022" }, + { 0x17, "unk_023" }, + { 0x18, "unk_024" }, + { 0x19, "unk_025" }, + { 0x1A, "unk_026" }, + { 0x1B, "unk_027" }, + { 0x1C, "unk_028" }, + { 0x1D, "unk_029" }, + { 0x1E, "unk_030" }, + { 0x1F, "unk_031" }, + { 0x20, "unk_032" }, + { 0x21, "unk_033" }, + { 0x22, "unk_034" }, + { 0x23, "unk_035" }, + { 0x24, "unk_036" }, + { 0x25, "unk_037" }, + { 0x26, "unk_038" }, + { 0x27, "unk_039" }, + { 0x28, "unk_040" }, + { 0x29, "unk_041" }, + { 0x2A, "unk_042" }, + { 0x2B, "unk_043" }, + { 0x2C, "unk_044" }, + { 0x2D, "unk_045" }, + { 0x2E, "unk_046" }, + { 0x2F, "unk_047" }, + { 0x30, "unk_048" }, + { 0x31, "unk_049" }, + { 0x32, "unk_050" }, + { 0x33, "unk_051" }, + { 0x34, "unk_052" }, + { 0x35, "unk_053" }, + { 0x36, "unk_054" }, + { 0x37, "unk_055" }, + { 0x38, "unk_056" }, + { 0x39, "unk_057" }, + { 0x3A, "unk_058" }, + { 0x3B, "unk_059" }, + { 0x3C, "unk_060" }, + { 0x3D, "unk_061" }, + { 0x3E, "unk_062" }, + { 0x3F, "unk_063" }, + { 0x40, "unk_064" }, + { 0x41, "unk_065" }, + { 0x42, "unk_066" }, + { 0x43, "unk_067" }, + { 0x44, "unk_068" }, + { 0x45, "unk_069" }, + { 0x46, "unk_070" }, + { 0x47, "unk_071" }, + { 0x48, "unk_072" }, + { 0x49, "unk_073" }, + { 0x4A, "unk_074" }, + { 0x4B, "unk_075" }, + { 0x4C, "unk_076" }, + { 0x4D, "unk_077" }, + { 0x4E, "unk_078" }, + { 0x4F, "unk_079" }, + { 0x50, "unk_080" }, + { 0x51, "unk_081" }, + { 0x52, "unk_082" }, + { 0x53, "unk_083" }, + { 0x54, "unk_084" }, + { 0x55, "unk_085" }, + { 0x56, "unk_086" }, + { 0x57, "unk_087" }, + { 0x58, "unk_088" }, + { 0x59, "unk_089" }, + { 0x5A, "unk_090" }, + { 0x5B, "unk_091" }, + { 0x5C, "unk_092" }, + { 0x5D, "unk_093" }, + { 0x5E, "unk_094" }, + { 0x5F, "unk_095" }, + { 0x60, "unk_096" }, + { 0x61, "unk_097" }, + { 0x62, "unk_098" }, + { 0x63, "unk_099" }, + { 0x64, "unk_100" }, + { 0x65, "unk_101" }, + { 0x66, "unk_102" }, + { 0x67, "unk_103" }, + { 0x68, "unk_104" }, + { 0x69, "unk_105" }, + { 0x6A, "unk_106" }, + { 0x6B, "unk_107" }, + { 0x6C, "unk_108" }, + { 0x6D, "unk_109" }, + { 0x6E, "unk_110" }, + { 0x6F, "unk_111" }, + { 0x70, "unk_112" }, + { 0x71, "unk_113" }, + { 0x72, "unk_114" }, + { 0x73, "unk_115" }, + { 0x74, "unk_116" }, + { 0x75, "unk_117" }, + { 0x76, "unk_118" }, + { 0x77, "unk_119" }, + { 0x78, "unk_120" }, + { 0x79, "unk_121" }, + { 0x7A, "unk_122" }, + { 0x7B, "unk_123" }, + { 0x7C, "unk_124" }, + { 0x7D, "unk_125" }, + { 0x7E, "unk_126" }, + { 0x7F, "unk_127" }, + { 0x80, "unk_128" }, + { 0x81, "unk_129" }, + { 0x82, "unk_130" }, + { 0x83, "unk_131" }, + { 0x84, "unk_132" }, + { 0x85, "unk_133" }, + { 0x86, "unk_134" }, + { 0x87, "unk_135" }, + { 0x88, "unk_136" }, + { 0x89, "unk_137" }, + { 0x8A, "unk_138" }, + { 0x8B, "unk_139" }, + { 0x8C, "unk_140" }, + { 0x8D, "unk_141" }, + { 0x8E, "unk_142" }, + { 0x8F, "unk_143" }, + { 0x90, "unk_144" }, + { 0x91, "unk_145" }, + { 0x92, "unk_146" }, + { 0x93, "unk_147" }, + { 0x94, "unk_148" }, + { 0x95, "unk_149" }, + { 0x96, "unk_150" }, + { 0x97, "unk_151" }, + { 0x98, "unk_152" }, + { 0x99, "unk_153" }, + { 0x9A, "unk_154" }, + { 0x9B, "unk_155" }, + { 0x9C, "unk_156" }, + { 0x9D, "unk_157" }, + { 0x9E, "unk_158" }, + { 0x9F, "unk_159" }, + { 0xA0, "unk_160" }, + { 0xA1, "unk_161" }, + { 0xA2, "unk_162" }, + { 0xA3, "unk_163" }, + { 0xA4, "unk_164" }, + { 0xA5, "unk_165" }, + { 0xA6, "unk_166" }, +}}; + +const std::array, 0> function_list +{{ +}}; + +const std::array, 0> method_list +{{ +}}; + +const std::array, 0> token_list +{{ +}}; + +struct __init__ +{ + __init__() + { + static bool init = false; + if (init) return; + init = true; + + opcode_map.reserve(opcode_list.size()); + opcode_map_rev.reserve(opcode_list.size()); + function_map.reserve(function_list.size()); + function_map_rev.reserve(function_list.size()); + method_map.reserve(method_list.size()); + method_map_rev.reserve(method_list.size()); + token_map.reserve(token_list.size()); + token_map_rev.reserve(token_list.size()); + + for (const auto& entry : opcode_list) + { + opcode_map.insert({ entry.first, entry.second }); + opcode_map_rev.insert({ entry.second, entry.first }); + } + + for (const auto& entry : function_list) + { + function_map.insert({ entry.first, entry.second }); + function_map_rev.insert({ entry.second, entry.first }); + } + + for (const auto& entry : method_list) + { + method_map.insert({ entry.first, entry.second }); + method_map_rev.insert({ entry.second, entry.first }); + } + + for (const auto& entry : token_list) + { + token_map.insert({ entry.first, entry.second }); + token_map_rev.insert({ entry.second, entry.first }); + } + } +}; + +__init__ _; + +} // namespace xsk::gsc::iw8 + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/src/iw9/xsk/resolver.hpp b/src/iw9/xsk/resolver.hpp new file mode 100644 index 00000000..b6bbd853 --- /dev/null +++ b/src/iw9/xsk/resolver.hpp @@ -0,0 +1,40 @@ +// Copyright 2022 xensik. All rights reserved. +// +// Use of this source code is governed by a GNU GPLv3 license +// that can be found in the LICENSE file. + +#pragma once + +namespace xsk::gsc::iw9 +{ + +class resolver +{ +public: + static void init(read_cb_type callback); + static void cleanup(); + + static auto opcode_id(const std::string& name) -> std::uint8_t; + static auto opcode_name(std::uint8_t id) -> std::string; + + static auto function_id(const std::string& name) -> std::uint16_t; + static auto function_name(std::uint16_t id) -> std::string; + + static auto method_id(const std::string& name) -> std::uint16_t; + static auto method_name(std::uint16_t id) -> std::string; + + static auto token_id(const std::string& name) -> std::uint32_t; + static auto token_name(std::uint32_t id) -> std::string; + + static auto find_function(const std::string& name) -> bool; + static auto find_method(const std::string& name) -> bool; + + static void add_function(const std::string& name, std::uint16_t id); + static void add_method(const std::string& name, std::uint16_t id); + + static auto make_token(std::string_view str) -> std::string; + static auto file_data(const std::string& name) -> std::tuple; + static auto fs_to_game_path(const std::filesystem::path& file) -> std::filesystem::path; +}; + +} // namespace xsk::gsc::iw9 diff --git a/src/tool/xsk/main.cpp b/src/tool/xsk/main.cpp index 91ff2128..deea8a11 100644 --- a/src/tool/xsk/main.cpp +++ b/src/tool/xsk/main.cpp @@ -12,6 +12,7 @@ #include "iw6/xsk/iw6.hpp" #include "iw7/xsk/iw7.hpp" #include "iw8/xsk/iw8.hpp" +#include "iw9/xsk/iw9.hpp" #include "s1/xsk/s1.hpp" #include "s2/xsk/s2.hpp" #include "s4/xsk/s4.hpp" @@ -24,7 +25,7 @@ namespace xsk enum class encd { _, source, assembly, binary }; enum class mode { _, assemble, disassemble, compile, decompile }; -enum class game { _, iw5c, iw6c, s1c, iw5, iw6, iw7, iw8, s1, s2, s4, h1, h2, t6 }; +enum class game { _, iw5c, iw6c, s1c, iw5, iw6, iw7, iw8, iw9, s1, s2, s4, h1, h2, t6 }; const std::map exts = { @@ -51,6 +52,7 @@ const std::map games = { "iw6", game::iw6 }, { "iw7", game::iw7 }, { "iw8", game::iw8 }, + { "iw9", game::iw9 }, { "s1", game::s1 }, { "s2", game::s2 }, { "s4", game::s4 }, @@ -118,6 +120,8 @@ auto choose_resolver_file_name(uint32_t id, game& game) -> std::string return iw7::resolver::token_name(id); case game::iw8: return iw8::resolver::token_name(id); + case game::iw9: + return iw9::resolver::token_name(id); case game::s1: return s1::resolver::token_name(static_cast(id)); case game::s2: @@ -426,6 +430,8 @@ void init() contexts[game::iw7]->init(build::prod, utils::file::read); contexts[game::iw8] = std::make_unique(); contexts[game::iw8]->init(build::prod, utils::file::read); + contexts[game::iw9] = std::make_unique(); + contexts[game::iw9]->init(build::prod, utils::file::read); contexts[game::s1] = std::make_unique(); contexts[game::s1]->init(build::prod, utils::file::read); contexts[game::s2] = std::make_unique();