From ae50ccf7f68aaa627f8e6ff8aad958664644e34a Mon Sep 17 00:00:00 2001 From: Hannes Mann Date: Mon, 27 Jun 2016 23:14:12 +0200 Subject: [PATCH] Add basic DSB interpreter We might need to replace this with something more sophisticated if some scripts use more flow control (e.g. if, while) --- .editorconfig | 4 + CMakeLists.txt | 2 +- docs/README.md | 23 +- src/.clang_complete | 5 + src/data_extractor/data_extractor.cc | 26 ++- src/data_extractor/data_extractor.h | 1 + src/data_extractor/dsb/dsb_decompiler.cc | 17 +- src/data_extractor/dsb/dsb_decompiler.h | 3 +- src/dsb_interpreter/dsb_instruction.h | 217 ++++++++++++++++++ src/dsb_interpreter/dsb_interpreter.cc | 127 ++++++++++ src/dsb_interpreter/dsb_interpreter.h | 86 +++++++ .../dsb_interpreter_debugger.cc | 88 +++++++ .../dsb_interpreter_debugger.h | 23 ++ .../dsb_interpreter_string_utils.h | 83 +++++++ src/dsb_interpreter/instructions/all.h | 9 + .../dsb_instruction_begin_section.h | 24 ++ .../comment/dsb_instruction_comment.h | 24 ++ .../invalid_dsb/dsb_instruction_invalid_dsb.h | 30 +++ .../level_add/dsb_instruction_level_add.h | 21 ++ .../dsb_instruction_set_data_dir.h | 92 ++++++++ .../dsb_instruction_set_texture_file.h | 33 +++ .../dsb_instruction_vignette_create_bar.h | 20 ++ .../dsb_instruction_vignette_load_img.h | 20 ++ .../dsb_instruction_vignette_set_color.h | 75 ++++++ src/engine.cc | 6 +- src/main.cc | 78 ++++--- 26 files changed, 1065 insertions(+), 72 deletions(-) create mode 100644 .editorconfig create mode 100644 src/.clang_complete create mode 100644 src/dsb_interpreter/dsb_instruction.h create mode 100644 src/dsb_interpreter/dsb_interpreter.cc create mode 100644 src/dsb_interpreter/dsb_interpreter.h create mode 100644 src/dsb_interpreter/dsb_interpreter_debugger.cc create mode 100644 src/dsb_interpreter/dsb_interpreter_debugger.h create mode 100644 src/dsb_interpreter/dsb_interpreter_string_utils.h create mode 100644 src/dsb_interpreter/instructions/all.h create mode 100644 src/dsb_interpreter/instructions/begin_section/dsb_instruction_begin_section.h create mode 100644 src/dsb_interpreter/instructions/comment/dsb_instruction_comment.h create mode 100644 src/dsb_interpreter/instructions/invalid_dsb/dsb_instruction_invalid_dsb.h create mode 100644 src/dsb_interpreter/instructions/level_add/dsb_instruction_level_add.h create mode 100644 src/dsb_interpreter/instructions/set_data_dir/dsb_instruction_set_data_dir.h create mode 100644 src/dsb_interpreter/instructions/set_texture_file/dsb_instruction_set_texture_file.h create mode 100644 src/dsb_interpreter/instructions/vignette/create_bar/dsb_instruction_vignette_create_bar.h create mode 100644 src/dsb_interpreter/instructions/vignette/load_img/dsb_instruction_vignette_load_img.h create mode 100644 src/dsb_interpreter/instructions/vignette/set_color/dsb_instruction_vignette_set_color.h diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..023b363 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.{cc, h, cpp, hpp}] +charset = utf-8 +indent_style = tab +indent_size = 4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 42522af..86b2991 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ project(OpenRayman) cmake_minimum_required(VERSION 2.6) -set(CMAKE_BUILD_TYPE Release CACHE STRING "The type of build") +set(CMAKE_BUILD_TYPE Debug CACHE STRING "The type of build") set(USE_LIBRETRO OFF CACHE BOOL "If OpenRayman should be built as a libretro core (TODO)") set(CMAKE_CXX_STANDARD 11) diff --git a/docs/README.md b/docs/README.md index 4596f3e..84cf187 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,9 @@ This file attempts to document the Rayman 2: The Great Escape file formats and engine as best possible. +You should probably check out https://drive.google.com/folderview?id=0Bzt0AW9Obi4-Tjg2MVAyLWNUVXM&usp=sharing, too. + + Note: OpenRayman could not have been possible without the amazing szymski on GitHub (https://github.com/szymski/Rayman2Lib), who seems to have reverse engineered the encoding algorithm used in Rayman 2 and the file formats that are used within the engine. Most of this information was scrapped together from the Rayman2Lib repository. @@ -16,26 +19,6 @@ Note: some of this information may not be incorrect, as i have not verified any ## Formats -.cnt = archive - - -.gf (graphics file?) (TODO: rayman2lib has both gf3 and gf?) = textures - - -.sna = ? scripts / events / level / everything?!?! (TODO: figure out what rayman2lib is doing with seeking) - - -.dat = pointers to file?!? file database?!?! - - -.dsb = same?!? scripts?!?! - - -.gpt = ?!?!?!? - - -.bnm = probably sound files (based off of rayman2lib) - ## Decoding diff --git a/src/.clang_complete b/src/.clang_complete new file mode 100644 index 0000000..5b4975e --- /dev/null +++ b/src/.clang_complete @@ -0,0 +1,5 @@ +-I. +-I../lib/lodepng +-I../lib/gl3w/include +-I../lib/json/src +-std=gnu++11 diff --git a/src/data_extractor/data_extractor.cc b/src/data_extractor/data_extractor.cc index a7bf6d4..8b68e29 100644 --- a/src/data_extractor/data_extractor.cc +++ b/src/data_extractor/data_extractor.cc @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include #include @@ -10,14 +13,11 @@ namespace openrayman { bool data_extractor::extract(const std::string& install_folder) { -#ifdef _WIN32 - AllocConsole(); - freopen("CONOUT$", "w", stdout); -#endif return check_prerequisites(install_folder) && create_base() && - decompile_game_dsb(install_folder); + decompile_game_dsb(install_folder) && + make_game_resources(install_folder); } bool data_extractor::check_prerequisites(const std::string& install_folder) @@ -83,4 +83,20 @@ namespace openrayman dsb_decompiler decompiler; return decompiler.decompile_dsb(install_folder + "/Data/Game.dsb", m_backend_specifics.get_data_path() + "/games/rayman2/game.odsb", dsb_format::openrayman); } + + bool data_extractor::make_game_resources(const std::string& install_folder) + { + dsb_interpreter interpreter(m_backend_specifics.get_data_path() + "/games/rayman2/game.odsb"); + if(interpreter.success()) + { + return true; + } + dsb_instruction_invalid_dsb* error_reason = interpreter.get_instruction + (dsb_instruction_type::invalid_dsb); + message_box::display("[openrayman::data_extractor] Error!", "The DSB interpreter failed to interpret the file game.odsb.\n\n" + + std::to_string(error_reason->line) + " : " + std::to_string(error_reason->column) + "\n" + + error_reason->error + "\n" + + error_reason->trace, true); + return false; + } } diff --git a/src/data_extractor/data_extractor.h b/src/data_extractor/data_extractor.h index e8bc7b2..39eda97 100644 --- a/src/data_extractor/data_extractor.h +++ b/src/data_extractor/data_extractor.h @@ -22,6 +22,7 @@ private: bool check_prerequisites(const std::string& install_folder); bool create_base(); bool decompile_game_dsb(const std::string& install_folder); + bool make_game_resources(const std::string& install_folder); const backend_specifics& m_backend_specifics; }; diff --git a/src/data_extractor/dsb/dsb_decompiler.cc b/src/data_extractor/dsb/dsb_decompiler.cc index f79ac56..a6e9dc3 100644 --- a/src/data_extractor/dsb/dsb_decompiler.cc +++ b/src/data_extractor/dsb/dsb_decompiler.cc @@ -3,6 +3,7 @@ #include #include #include +#include namespace openrayman { @@ -62,11 +63,11 @@ namespace openrayman while(!in.eof() && id != 0xFFFF) { -#define DECOMPILE_SECTION(id, name, function) \ + #define DECOMPILE_SECTION(id, name, function) \ case (id): \ { \ - std::cout << "[openrayman::dsb_decompiler].decompile_sections: decompiling " \ - << std::hex << "0x" << (id) \ + std::cout << "[openrayman::dsb_decompiler] Decompiling " \ + << std::hex << "0x" << std::setfill('0') << std::setw(2) << (id) \ << " (decimal " << std::dec << id << ") \"" \ << (name) << "\"" << std::endl; \ target << "section " << (name) << "\n"; \ @@ -90,7 +91,7 @@ namespace openrayman DECOMPILE_SECTION(0x5C, "load_sound_banks", decompile_load_sound_banks); default: { - std::cout << "[openrayman::dsb_decompiler].decompile_sections: Encountered unknown id 0x" + std::cout << "[openrayman::dsb_decompiler] Encountered unknown id 0x" << std::hex << id << " (decimal " << std::dec << id << ")" << std::endl; @@ -180,7 +181,7 @@ namespace openrayman source.read((char*)&str_length, sizeof(std::uint16_t)); char str[str_length]; source.read(str, str_length); - std::cout << "[openrayman::dsb_decompiler].decompile_data_directories: " << std::hex << "0x" << id << ": " << str << std::endl; + std::cout << "[openrayman::dsb_decompiler] Data directory " << std::hex << "0x" << id << ": " << str << std::endl; // dir(name, path); if(dir != "") target << " dir(" << dir << ", \"" << str << "\")\n"; @@ -192,7 +193,7 @@ namespace openrayman // is this ever found?!?! void dsb_decompiler::decompile_unknown_blob_0x20(std::istream& source, std::ofstream& target) { - std::cout << "[openrayman::dsb_decompiler].decompile_unknown_blob_0x20: Warning! encountered 0x20" << std::endl; + std::cout << "[openrayman::dsb_decompiler] Warning! encountered 0x20" << std::endl; std::uint32_t size; source.read((char*)&size, sizeof(std::uint32_t)); target << " size(" << size << ")\n"; @@ -243,7 +244,7 @@ namespace openrayman std::uint8_t r, g, b, a; source.read((char*)&r, 1); source.read((char*)&g, 1); source.read((char*)&b, 1); source.read((char*)&a, 1); target << " color(" - << (id == 76 ? "outline" : "inline") + << (id == 76 ? "outline" : "inside") << ", " << std::to_string(r) << ", " << std::to_string(g) << ", " << std::to_string(b) @@ -311,7 +312,7 @@ namespace openrayman void dsb_decompiler::decompile_unknown_blob_0x6e(std::istream& source, std::ofstream& target) { - std::cout << "[openrayman::dsb_decompiler].decompile_unknown_blob_0x6e: Warning! encountered 0x6e" << std::endl; + std::cout << "[openrayman::dsb_decompiler] Warning! encountered 0x6e" << std::endl; std::uint8_t tmp = 0x00; while(tmp != 0xFF) source.read((char*)&tmp, 1); diff --git a/src/data_extractor/dsb/dsb_decompiler.h b/src/data_extractor/dsb/dsb_decompiler.h index 4ee5cf7..b5fec1f 100644 --- a/src/data_extractor/dsb/dsb_decompiler.h +++ b/src/data_extractor/dsb/dsb_decompiler.h @@ -26,7 +26,8 @@ namespace openrayman }; // Decodes and optionally decompiles DSB files. - // This is all heavily adapted from Rayman2Lib's DSBDecompiler + // DSB files are compiled scripts, with different sections. + // This is all heavily adapted from Rayman2Lib. class dsb_decompiler { public: diff --git a/src/dsb_interpreter/dsb_instruction.h b/src/dsb_interpreter/dsb_instruction.h new file mode 100644 index 0000000..effceb9 --- /dev/null +++ b/src/dsb_interpreter/dsb_instruction.h @@ -0,0 +1,217 @@ +#ifndef DSB_INSTRUCTION_H +#define DSB_INSTRUCTION_H + +#include +#include + +namespace openrayman +{ + enum class dsb_instruction_type + { + // Special value used to specify that no instruction could be provided. + none, + + // This instruction implies that the DSB could not be interpreted for some reason. + // It is paried with a dsb_instruction_invalid_dsb, which specifies a string that describes what went + // wrong during interpretation, and at what line / column. + // No instructions should follow or precede this one. + invalid_dsb, + + // This instruction specifies that a comment was specified in the DSB. + // It is paried with a dsb_instruction_comment, which specifies the comment encountered. + comment, + + // This instruction implies the start of a new section in the DSB. + // It is paired with a dsb_instruction_begin_section, which specifies what + // type of section is starting. + // An instruction of this type that starts a "none" section implies + // the end of the instruction array. + begin_section, + + // This instruction sets a data directory to a path. + // It is paried with a dsb_instruction_set_data_dir, which specifies what data directory to set + // and which path to set it to. + set_data_dir, + + // This instruction sets a texture file id to an archive. + // It is paried with a dsb_instruction_set_texture_file, which specifies what texture file id to set + // and which archive to set it to. + set_texture_file, + + // This instruction loads a background image into the vignette. + // This can contain "random" expression, in the format "Random". + // We don't currently know how to interpret this, so we just use the first image. + // Additionally, names can contain .bmp. Ignore this. + // TODO: is this ok?!? probably + // It is paired with a dsb_instruction_vignette_load_img, which specifies image file to load. + vignette_load_img, + + // This instruction sets one of several color values. + // It is paried with a dsb_instruction_vignette_set_color, which specifies what id to set + // and what color(s) to set. + vignette_set_color, + + // This instruction creates a bar with the current specifed colors. + // It is paried with a dsb_instruction_vignette_create_bar, which specifies the position and size of + // the vignette. + vignette_create_bar, + + // This instruction adds a bar to the vignette. + // It is not paired with a class, as it doesn't need any additional values. + vignette_add_bar, + + // This instruction displays the vignette on the screen. + // It is not paired with a class, as it doesn't need any additional values. + vignette_display, + + // This instruction adds a level. + // It is paired with a dsb_instruction_level_add, which specifies what level to add. + level_add + }; + + inline std::string instruction_to_string(dsb_instruction_type instruction) + { + #define VALID_INSTRUCTION_TO(name) \ + if(instruction == dsb_instruction_type::name) \ + return #name; + + VALID_INSTRUCTION_TO(invalid_dsb); + VALID_INSTRUCTION_TO(comment); + VALID_INSTRUCTION_TO(begin_section); + VALID_INSTRUCTION_TO(set_data_dir); + VALID_INSTRUCTION_TO(set_texture_file); + VALID_INSTRUCTION_TO(vignette_load_img); + VALID_INSTRUCTION_TO(vignette_set_color); + VALID_INSTRUCTION_TO(vignette_create_bar); + VALID_INSTRUCTION_TO(vignette_add_bar); + VALID_INSTRUCTION_TO(vignette_display); + VALID_INSTRUCTION_TO(level_add); + + return "none"; + } + + inline dsb_instruction_type string_to_instruction(const std::string& instruction) + { + #define VALID_INSTRUCTION_FROM(name) \ + if(instruction == #name) \ + return dsb_instruction_type::name; + + VALID_INSTRUCTION_FROM(invalid_dsb); + VALID_INSTRUCTION_FROM(comment); + VALID_INSTRUCTION_FROM(begin_section); + VALID_INSTRUCTION_FROM(set_data_dir); + VALID_INSTRUCTION_FROM(set_texture_file); + VALID_INSTRUCTION_FROM(vignette_load_img); + VALID_INSTRUCTION_FROM(vignette_set_color); + VALID_INSTRUCTION_FROM(vignette_create_bar); + VALID_INSTRUCTION_FROM(vignette_add_bar); + VALID_INSTRUCTION_FROM(vignette_display); + VALID_INSTRUCTION_FROM(level_add); + + return dsb_instruction_type::none; + } + + enum class dsb_section_type + { + // Special value used to specify that no section could be provided. + none, + + // Section to allocate values into slots. + // This section is not present in a raw Rayman 2: The Great Escape .pgb. + // Unused. + alloc, + + // Section to set data directories. + // This is only used by the data extractor to find game data in a + // valid Rayman 2: The Great Escape installation. + data_directories, + + // Section to set texture archives. + // This is only used by the data extractor to find game data in a + // valid Rayman 2: The Great Escape installation. + texture_files, + + // Section that builds a vignette. + vignette, + + // Unknown section. + // Unused. + unknown_blob_0x6e, + + // Unknown section. + // Unused. + unknown_blob_0x20, + + // Section that specifies what game option files to use. + // Unused. + game_options, + + // Sections that adds levels. + // This is only used by the data extractor to find game data in a + // valid Rayman 2: The Great Escape installation. + levels, + + // Unknown section (for now). + // Unused. + sound_banks, + + // Unknown section (for now). + // Unused. + load_sound_banks + }; + + inline std::string section_to_string(dsb_section_type section) + { + #define VALID_SECTION_TO(name) \ + if(section == dsb_section_type::name) \ + return #name; + + VALID_SECTION_TO(alloc); + VALID_SECTION_TO(data_directories); + VALID_SECTION_TO(texture_files); + VALID_SECTION_TO(vignette); + VALID_SECTION_TO(unknown_blob_0x6e); + VALID_SECTION_TO(unknown_blob_0x20); + VALID_SECTION_TO(game_options); + VALID_SECTION_TO(levels); + VALID_SECTION_TO(sound_banks); + VALID_SECTION_TO(load_sound_banks); + + return "none"; + } + + inline dsb_section_type string_to_section(const std::string& section) + { + #define VALID_SECTION_FROM(name) \ + if(section == #name) \ + return dsb_section_type::name; + + VALID_SECTION_FROM(alloc); + VALID_SECTION_FROM(data_directories); + VALID_SECTION_FROM(texture_files); + VALID_SECTION_FROM(vignette); + VALID_SECTION_FROM(unknown_blob_0x6e); + VALID_SECTION_FROM(unknown_blob_0x20); + VALID_SECTION_FROM(game_options); + VALID_SECTION_FROM(levels); + VALID_SECTION_FROM(sound_banks); + VALID_SECTION_FROM(load_sound_banks); + + return dsb_section_type::none; + } + + struct dsb_instruction + { + dsb_instruction(dsb_instruction_type type) : + type(type) + { + + } + + // The type of instruction that this class holds. + // The class should be cast via static_cast or similar. + dsb_instruction_type type; + }; +} + +#endif diff --git a/src/dsb_interpreter/dsb_interpreter.cc b/src/dsb_interpreter/dsb_interpreter.cc new file mode 100644 index 0000000..d0ac742 --- /dev/null +++ b/src/dsb_interpreter/dsb_interpreter.cc @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace openrayman +{ + dsb_interpreter::dsb_interpreter(const std::string& odsb_path) + { + std::ifstream stream(file::fix_string(odsb_path), std::ifstream::in); + if(!stream.is_open()) + { + set_error(0, 0, "The file \"" + odsb_path + "\" does not exist.", dsb_section_type::none, "dsb_interpreter::dsb_interpreter (constructor)"); + return; + } + interpret_sections(stream); + } + + void dsb_interpreter::set_error(std::size_t line, std::size_t column, const std::string& error, dsb_section_type current_section, const std::string& function) + { + std::stringstream trace; + trace << "Occured when section was: " << std::hex << "0x" << std::setfill('0') << std::setw(2) << (int)current_section << " (" << section_to_string(current_section) << ")" << "\n"; + trace << "Originated from function: " << function; + m_interpreted_dsb.clear(); + m_interpreted_dsb.push_back(new dsb_instruction_invalid_dsb(line, column, error, trace.str())); + } + + void dsb_interpreter::interpret_sections(std::ifstream& stream) + { + dsb_section_type current_section = dsb_section_type::none; + std::string line; + std::size_t line_n = 0; + while(std::getline(stream, line)) + { + std::size_t n = 0; + n += skip_whitespace(line, n); + // if the string wasn't all whitespace + if(line.length() > 0 && n != line.length() - 1) + { + bool comment = false; + if(line[n] == '#') + { + n++; + n += skip_whitespace(line, n); + comment = true; + } + if(comment) + { + std::string comment_string = read_until_end(line, n); + if(comment_string.length() > 0) + m_interpreted_dsb.push_back(new dsb_instruction_comment(line_n, comment_string)); + } + else + { + if(read_ahead_match(line, n, "section")) + { + n += sizeof("section"); + n += skip_whitespace(line, n); + std::string section = read_until_end(line, n); + dsb_section_type new_section = string_to_section(section); + + if(new_section == dsb_section_type::none) + { + set_error(line_n, n, "Invalid syntax (expected section type, got \"" + section + "\").", current_section, "dsb_interpreter::interpret_sections"); + return; + } + + m_interpreted_dsb.push_back(new dsb_instruction_begin_section(current_section, new_section)); + current_section = new_section; + } + else if(current_section == dsb_section_type::none) + { + set_error(line_n, n, "Invalid syntax (expected section, got \"" + read_until_whitespace(line, n) + "\").", current_section, "dsb_interpreter::interpret_sections"); + return; + } + else + interpret_line(current_section, line, n, line_n); + } + } + line_n++; + } + // end of instruction array + if(m_interpreted_dsb.size() == 0) + set_error(0, 0, "No valid instructions could be interpreted from the specified file.", current_section, "dsb_interpreter::interpret_sections"); + else + m_interpreted_dsb.push_back(new dsb_instruction_begin_section(current_section, dsb_section_type::none)); + } + + void dsb_interpreter::interpret_line(dsb_section_type current_section, const std::string& line, std::size_t statement_begins_at, std::size_t line_n) + { + std::size_t n = statement_begins_at; + std::string function_name = read_until_char(line, n, '('); + n += function_name.length() + 1; + std::vector function_args; + n += skip_whitespace(line, n); + std::string arg = ""; + while(n < line.length()) + { + if(line[n] == '"') + { + arg = read_quote_escaped_string(line, n); + n += arg.length() + 1; + } + else if(line[n] != ',' && line[n] != ')') + arg += line[n]; + if(line[n] == ',') + { + function_args.push_back(arg); + arg = ""; + n++; + n += skip_whitespace(line, n); + } + else if(line[n] == ')') + break; + else + n++; + } + if(arg.length() > 0) + function_args.push_back(arg); + std::cout << "[openrayman::dsb_interpreter] Function: \"" << function_name << "\", " << function_args.size() << std::endl; + for(const std::string& str : function_args) + std::cout << " Argument: \"" << str << "\"" << std::endl; + } +} diff --git a/src/dsb_interpreter/dsb_interpreter.h b/src/dsb_interpreter/dsb_interpreter.h new file mode 100644 index 0000000..b0cf1dd --- /dev/null +++ b/src/dsb_interpreter/dsb_interpreter.h @@ -0,0 +1,86 @@ +#ifndef DSB_INTERPRETER_H +#define DSB_INTERPRETER_H + +#include +#include +#include +#include +#include +#include + +namespace openrayman +{ + // DSB interpreter. + // Builds an array of instructions from a valid odsb file. + class dsb_interpreter + { +public: + dsb_interpreter(const std::string& odsb_path); + + ~dsb_interpreter() + { + for(dsb_instruction* instruction : m_interpreted_dsb) + delete instruction; + } + + // If this DSB was interpreted successfully. + inline bool success() const + { + return section_count(dsb_instruction_type::invalid_dsb) == 0; + } + + // Returns the number of times that the specified section type occurs in this DSB. + inline std::size_t section_count(dsb_instruction_type type) const + { + std::size_t count = 0; + for(dsb_instruction* instruction : m_interpreted_dsb) + { + if(instruction->type == type) + count++; + } + return count; + } + + // Returns the first instance of the specified instruction type, or nullptr if one doesn't exist. + template + inline T* get_instruction(dsb_instruction_type type) const + { + for(dsb_instruction* instruction : m_interpreted_dsb) + { + if(instruction->type == type) + return static_cast(instruction); + } + return nullptr; + } + + // Returns all instances of the specified instruction type. + template + inline std::vector get_instructions(dsb_instruction_type type) const + { + std::vector values; + for(dsb_instruction* instruction : m_interpreted_dsb) + { + if(instruction->type == type) + values.push_back(static_cast(instruction)); + } + return values; + } + + // Returns a reference to the raw instruction array. + // This can be manually searched and interpreted. + inline const std::vector& get_complete_instruction_array() const + { + return m_interpreted_dsb; + } +private: + void interpret_sections(std::ifstream& stream); + void interpret_line(dsb_section_type current_section, const std::string& line, std::size_t statement_begins_at, std::size_t line_n); + + // Sets the error at the top of the dsb instruction array. + void set_error(std::size_t line, std::size_t column, const std::string& error, dsb_section_type current_section, const std::string& function); + + std::vector m_interpreted_dsb; + }; +} + +#endif diff --git a/src/dsb_interpreter/dsb_interpreter_debugger.cc b/src/dsb_interpreter/dsb_interpreter_debugger.cc new file mode 100644 index 0000000..1713e77 --- /dev/null +++ b/src/dsb_interpreter/dsb_interpreter_debugger.cc @@ -0,0 +1,88 @@ +#include +#include +#include +#include + +namespace openrayman +{ + void dsb_interpreter_debugger::print_summary() const + { + if(m_interpreter.success()) + { + std::cout << std::endl << "DSB interpreted -> " + << m_interpreter.get_complete_instruction_array().size() << " instructions emitted" << std::endl << std::endl; + for(dsb_instruction* instruction : m_interpreter.get_complete_instruction_array()) + { + std::cout << " dsb_instruction_type::" << instruction_to_string(instruction->type) << std::endl; + switch(instruction->type) + { + case dsb_instruction_type::comment: + { + dsb_instruction_comment* cast = static_cast(instruction); + std::cout << " comment -> \"" << cast->comment << "\"" << std::endl; + break; + } + case dsb_instruction_type::begin_section: + { + dsb_instruction_begin_section* cast = static_cast(instruction); + std::cout << " end -> dsb_section_type::" << section_to_string(cast->end) << std::endl; + std::cout << " begin -> dsb_section_type::" << section_to_string(cast->begin) << std::endl; + break; + } + case dsb_instruction_type::set_data_dir: + { + dsb_instruction_set_data_dir* cast = static_cast(instruction); + std::cout << " directory -> dsb_data_directory::" << data_directory_to_string(cast->directory) << std::endl; + std::cout << " path -> \"" << cast->path << "\"" << std::endl; + break; + } + case dsb_instruction_type::set_texture_file: + { + dsb_instruction_set_texture_file* cast = static_cast(instruction); + std::cout << " id -> dsb_texture_file_id::" << (cast->id == dsb_texture_file_id::vignettes ? "vignettes" : "textures") << std::endl; + std::cout << " archive -> \"" << cast->archive << "\"" << std::endl; + break; + } + case dsb_instruction_type::vignette_load_img: + { + dsb_instruction_vignette_load_img* cast = static_cast(instruction); + std::cout << " image -> \"" << cast->image << "\"" << std::endl; + break; + } + case dsb_instruction_type::vignette_set_color: + { + dsb_instruction_vignette_set_color* cast = static_cast(instruction); + std::cout << " id -> dsb_vignette_id::" << vignette_id_to_string(cast->id) << std::endl; + std::cout << " pair1 -> [" << cast->r << ", " << cast->g << ", " << cast->b << ", " << cast->a << "]" << std::endl; + std::cout << " pair2 -> [" << cast->pair2_r << ", " << cast->pair2_g << ", " << cast->pair2_b << ", " << cast->pair2_a << "]" << std::endl; + std::cout << " pair3 -> [" << cast->pair3_r << ", " << cast->pair3_g << ", " << cast->pair3_b << ", " << cast->pair3_a << "]" << std::endl; + std::cout << " pair4 -> [" << cast->pair4_r << ", " << cast->pair4_g << ", " << cast->pair4_b << ", " << cast->pair4_a << "]" << std::endl; + break; + } + case dsb_instruction_type::vignette_create_bar: + { + dsb_instruction_vignette_create_bar* cast = static_cast(instruction); + std::cout << " pos -> [" << cast->x << ", " << cast->y << ", " << cast->w << ", " << cast->h << "]" << std::endl; + break; + } + case dsb_instruction_type::level_add: + { + dsb_instruction_level_add* cast = static_cast(instruction); + std::cout << " level -> \"" << cast->level << "\"" << std::endl; + break; + } + } + std::cout << std::endl; + } + } + else + { + std::cout << "Operation failed" << std::endl; + dsb_instruction_invalid_dsb* error_reason = m_interpreter.get_instruction + (dsb_instruction_type::invalid_dsb); + std::cout << "At " << error_reason->line << " : " << error_reason->column << std::endl; + std::cout << error_reason->error << std::endl; + std::cout << error_reason->trace << std::endl; + } + } +} diff --git a/src/dsb_interpreter/dsb_interpreter_debugger.h b/src/dsb_interpreter/dsb_interpreter_debugger.h new file mode 100644 index 0000000..61f6859 --- /dev/null +++ b/src/dsb_interpreter/dsb_interpreter_debugger.h @@ -0,0 +1,23 @@ +#ifndef DSB_INTERPRETER_DEBUGGER_H +#define DSB_INTERPRETER_DEBUGGER_H + +#include + +namespace openrayman +{ + // Used for printing some information about an interpreted DSB. + class dsb_interpreter_debugger + { +public: + dsb_interpreter_debugger(const dsb_interpreter& interpreter) : + m_interpreter(interpreter) + { + } + + void print_summary() const; +private: + const dsb_interpreter& m_interpreter; + }; +} + +#endif diff --git a/src/dsb_interpreter/dsb_interpreter_string_utils.h b/src/dsb_interpreter/dsb_interpreter_string_utils.h new file mode 100644 index 0000000..15067c6 --- /dev/null +++ b/src/dsb_interpreter/dsb_interpreter_string_utils.h @@ -0,0 +1,83 @@ +#ifndef DSB_INTERPRETER_STRING_UTILS_H +#define DSB_INTERPRETER_STRING_UTILS_H + +#include + +namespace openrayman +{ + // Reads ahead in the string and returns true if a match is found. + bool read_ahead_match(const std::string& source, std::size_t begin_at, const std::string& match) + { + if((int)source.length() - (int)begin_at + (int)match.length() < 0) + return false; + for(std::size_t n = 0; n < match.length(); n++) + { + if(source[n + begin_at] != match[n]) + return false; + } + return true; + } + + // Returns the difference needed to skip whitespace from the point "at". + std::size_t skip_whitespace(const std::string& source, std::size_t at) + { + std::size_t new_at = at; + while((source[new_at] == ' ' || source[new_at] == '\t') && new_at < source.length()) + new_at++; + return new_at - at; + } + + // Returns the string in the range from point "at" to whitespace. + std::string read_until_whitespace(const std::string& source, std::size_t at) + { + std::string result = ""; + while((source[at] != ' ' && source[at] != '\t') && at < source.length()) + { + result += source[at]; + at++; + } + return result; + } + + // Returns the string from point "at" until the specified character is found. + std::string read_until_char(const std::string& source, std::size_t at, char until) + { + std::string result = ""; + while((source[at] != until) && at < source.length()) + { + result += source[at]; + at++; + } + return result; + } + + // Returns the string in the range from point "at" to end. + std::string read_until_end(const std::string& source, std::size_t at) + { + std::string result = ""; + while(at < source.length()) + { + result += source[at]; + at++; + } + return result; + } + + // Reads a quote escaped string that starts at point "at". + std::string read_quote_escaped_string(const std::string& source, std::size_t at) + { + std::string result = ""; + at++; + bool next_escaped = false; + while(at < source.length()) + { + if(source[at] == '"') + break; + result += source[at]; + at++; + } + return result; + } +} + +#endif diff --git a/src/dsb_interpreter/instructions/all.h b/src/dsb_interpreter/instructions/all.h new file mode 100644 index 0000000..9ccc002 --- /dev/null +++ b/src/dsb_interpreter/instructions/all.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/dsb_interpreter/instructions/begin_section/dsb_instruction_begin_section.h b/src/dsb_interpreter/instructions/begin_section/dsb_instruction_begin_section.h new file mode 100644 index 0000000..3b0b29c --- /dev/null +++ b/src/dsb_interpreter/instructions/begin_section/dsb_instruction_begin_section.h @@ -0,0 +1,24 @@ +#ifndef DSB_INSTRUCTION_BEGIN_SECTION_H +#define DSB_INSTRUCTION_BEGIN_SECTION_H + +#include + +namespace openrayman +{ + struct dsb_instruction_begin_section : public dsb_instruction + { + dsb_instruction_begin_section(dsb_section_type end, dsb_section_type begin) : + dsb_instruction(dsb_instruction_type::begin_section), + end(end), begin(begin) + { + } + + // The previous section that is ending. + dsb_section_type end; + + // The section that is following this instruction. + dsb_section_type begin; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/comment/dsb_instruction_comment.h b/src/dsb_interpreter/instructions/comment/dsb_instruction_comment.h new file mode 100644 index 0000000..2befc83 --- /dev/null +++ b/src/dsb_interpreter/instructions/comment/dsb_instruction_comment.h @@ -0,0 +1,24 @@ +#ifndef DSB_INSTRUCTION_COMMENT_H +#define DSB_INSTRUCTION_COMMENT_H + +#include + +namespace openrayman +{ + struct dsb_instruction_comment : public dsb_instruction + { + dsb_instruction_comment(std::size_t line, const std::string& comment) : + dsb_instruction(dsb_instruction_type::comment), + line(line), comment(comment) + { + } + + // The line that the interpreter encountered the comment. + std::size_t line; + + // The user specified comment that was found. + std::string comment; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/invalid_dsb/dsb_instruction_invalid_dsb.h b/src/dsb_interpreter/instructions/invalid_dsb/dsb_instruction_invalid_dsb.h new file mode 100644 index 0000000..2f271e8 --- /dev/null +++ b/src/dsb_interpreter/instructions/invalid_dsb/dsb_instruction_invalid_dsb.h @@ -0,0 +1,30 @@ +#ifndef DSB_INSTRUCTION_INVALID_DSB_H +#define DSB_INSTRUCTION_INVALID_DSB_H + +#include + +namespace openrayman +{ + struct dsb_instruction_invalid_dsb : public dsb_instruction + { + dsb_instruction_invalid_dsb(std::size_t line, std::size_t column, const std::string& error, const std::string& trace) : + dsb_instruction(dsb_instruction_type::invalid_dsb), + line(line), column(column), error(error), trace(trace) + { + } + + // The line that the interpreter encountered the error. + std::size_t line; + + // The column that the interpreter encountered the error. + std::size_t column; + + // The error that the interpreter encountered. + std::string error; + + // Trace info about the error. + std::string trace; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/level_add/dsb_instruction_level_add.h b/src/dsb_interpreter/instructions/level_add/dsb_instruction_level_add.h new file mode 100644 index 0000000..737867e --- /dev/null +++ b/src/dsb_interpreter/instructions/level_add/dsb_instruction_level_add.h @@ -0,0 +1,21 @@ +#ifndef DSB_INSTRUCTION_LEVEL_ADD_H +#define DSB_INSTRUCTION_LEVEL_ADD_H + +#include + +namespace openrayman +{ + struct dsb_instruction_level_add : public dsb_instruction + { + dsb_instruction_level_add(const std::string& level) : + dsb_instruction(dsb_instruction_type::level_add), + level(level) + { + } + + // The level name, relative to the levels folder. + std::string level; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/set_data_dir/dsb_instruction_set_data_dir.h b/src/dsb_interpreter/instructions/set_data_dir/dsb_instruction_set_data_dir.h new file mode 100644 index 0000000..80df521 --- /dev/null +++ b/src/dsb_interpreter/instructions/set_data_dir/dsb_instruction_set_data_dir.h @@ -0,0 +1,92 @@ +#ifndef DSB_INSTRUCTION_SET_DATA_DIR_H +#define DSB_INSTRUCTION_SET_DATA_DIR_H + +#include + +namespace openrayman +{ + enum class dsb_data_directory + { + // Special value used to specify that no directory could be provided. + none, + + // Specifies the directory where engine dlls are located. + dll, + + // Specifies the root of game data. + // This seems to be incorrect in Rayman 2?!? + root, + + // Specifies the directory where data related to the game world is located. + world, + + // Specifies the directory where levels are located. + levels, + + // Specifies the directory where sound files are located. + sound, + + // Specifies the directory where save files are located. + saves, + + // Specifies the directory where "vignettes" are located. + // Vignettes are 2D textures that are used in loading screens. + vignette, + + // Specifies the directory where user options are saved. + options + }; + + inline std::string data_directory_to_string(dsb_data_directory dir) + { + #define VALID_DIRECTORY_TO(name) \ + if(dir == dsb_data_directory::name) \ + return #name; + + VALID_DIRECTORY_TO(dll); + VALID_DIRECTORY_TO(root); + VALID_DIRECTORY_TO(world); + VALID_DIRECTORY_TO(levels); + VALID_DIRECTORY_TO(sound); + VALID_DIRECTORY_TO(saves); + VALID_DIRECTORY_TO(vignette); + VALID_DIRECTORY_TO(options); + + return "none"; + } + + inline dsb_data_directory string_to_data_directory(const std::string& dir) + { + #define VALID_DIRECTORY_FROM(name) \ + if(dir == #name) \ + return dsb_data_directory::name; + + VALID_DIRECTORY_FROM(dll); + VALID_DIRECTORY_FROM(root); + VALID_DIRECTORY_FROM(world); + VALID_DIRECTORY_FROM(levels); + VALID_DIRECTORY_FROM(sound); + VALID_DIRECTORY_FROM(saves); + VALID_DIRECTORY_FROM(vignette); + VALID_DIRECTORY_FROM(options); + + return dsb_data_directory::none; + } + + struct dsb_instruction_set_data_dir : public dsb_instruction + { + dsb_instruction_set_data_dir(dsb_data_directory directory, const std::string& path) : + dsb_instruction(dsb_instruction_type::set_data_dir), + directory(directory), path(path) + { + } + + // The data directory to set. + dsb_data_directory directory; + + // The path which it should be set to. + std::string path; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/set_texture_file/dsb_instruction_set_texture_file.h b/src/dsb_interpreter/instructions/set_texture_file/dsb_instruction_set_texture_file.h new file mode 100644 index 0000000..f2cd3d1 --- /dev/null +++ b/src/dsb_interpreter/instructions/set_texture_file/dsb_instruction_set_texture_file.h @@ -0,0 +1,33 @@ +#ifndef DSB_INSTRUCTION_SET_TEXTURE_FILE_H +#define DSB_INSTRUCTION_SET_TEXTURE_FILE_H + +#include + +namespace openrayman +{ + enum class dsb_texture_file_id + { + // Stores vignettes. + vignettes, + + // Stores all other (world, etc) textures. + textures + }; + + struct dsb_instruction_set_texture_file : public dsb_instruction + { + dsb_instruction_set_texture_file(dsb_texture_file_id id, const std::string& archive) : + dsb_instruction(dsb_instruction_type::set_texture_file), + id(id), archive(archive) + { + } + + // The texture file to set. + dsb_texture_file_id id; + + // The archive which it should be set to. + std::string archive; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/vignette/create_bar/dsb_instruction_vignette_create_bar.h b/src/dsb_interpreter/instructions/vignette/create_bar/dsb_instruction_vignette_create_bar.h new file mode 100644 index 0000000..51f565b --- /dev/null +++ b/src/dsb_interpreter/instructions/vignette/create_bar/dsb_instruction_vignette_create_bar.h @@ -0,0 +1,20 @@ +#ifndef DSB_INSTRUCTION_VIGNETTE_CREATE_BAR_H +#define DSB_INSTRUCTION_VIGNETTE_CREATE_BAR_H + +#include + +namespace openrayman +{ + struct dsb_instruction_vignette_create_bar : public dsb_instruction + { + dsb_instruction_vignette_create_bar(int x, int y, int w, int h) : + dsb_instruction(dsb_instruction_type::vignette_create_bar), + x(x), y(y), w(w), h(h) + { + } + + int x, y, w, h; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/vignette/load_img/dsb_instruction_vignette_load_img.h b/src/dsb_interpreter/instructions/vignette/load_img/dsb_instruction_vignette_load_img.h new file mode 100644 index 0000000..71e8b19 --- /dev/null +++ b/src/dsb_interpreter/instructions/vignette/load_img/dsb_instruction_vignette_load_img.h @@ -0,0 +1,20 @@ +#ifndef DSB_INSTRUCTION_VIGNETTE_LOAD_IMG_H +#define DSB_INSTRUCTION_VIGNETTE_LOAD_IMG_H + +#include + +namespace openrayman +{ + struct dsb_instruction_vignette_load_img : public dsb_instruction + { + dsb_instruction_vignette_load_img(const std::string& image) : + dsb_instruction(dsb_instruction_type::vignette_load_img), + image(image) + { + } + + std::string image; + }; +} + +#endif diff --git a/src/dsb_interpreter/instructions/vignette/set_color/dsb_instruction_vignette_set_color.h b/src/dsb_interpreter/instructions/vignette/set_color/dsb_instruction_vignette_set_color.h new file mode 100644 index 0000000..8e805a1 --- /dev/null +++ b/src/dsb_interpreter/instructions/vignette/set_color/dsb_instruction_vignette_set_color.h @@ -0,0 +1,75 @@ +#ifndef DSB_INSTRUCTION_VIGNETTE_SET_COLOR_H +#define DSB_INSTRUCTION_VIGNETTE_SET_COLOR_H + +#include +#include + +namespace openrayman +{ + enum class dsb_vignette_id + { + none, + // TODO: document these + outline, + inside, + bar + }; + + inline std::string vignette_id_to_string(dsb_vignette_id id) + { + #define VALID_ID_TO(name) \ + if(id == dsb_vignette_id::name) \ + return #name; + + VALID_ID_TO(outline); + VALID_ID_TO(inside); + VALID_ID_TO(bar); + + return "none"; + } + + inline dsb_vignette_id string_to_vignette_id(const std::string& id) + { + #define VALID_ID_FROM(name) \ + if(id == #name) \ + return dsb_vignette_id::name; + + VALID_ID_FROM(outline); + VALID_ID_FROM(inside); + VALID_ID_FROM(bar); + + return dsb_vignette_id::none; + } + + struct dsb_instruction_vignette_set_color : public dsb_instruction + { + // TODO: refactor this.......... + // ugh + dsb_instruction_vignette_set_color(dsb_vignette_id id, + std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a) : + dsb_instruction(dsb_instruction_type::vignette_set_color), + r(r), g(g), b(b), a(a) + { + } + + // The id of the color to set. + dsb_vignette_id id; + + // The first pair of colors. + std::uint8_t r, g, b, a; + + // The second pair of colors. + // Only used when id is bar, to specify gradient. + std::uint8_t pair2_r, pair2_g, pair2_b, pair2_a; + + // The third pair of colors. + // Only used when id is bar, to specify gradient. + std::uint8_t pair3_r, pair3_g, pair3_b, pair3_a; + + // The fourth pair of colors. + // Only used when id is bar, to specify gradient. + std::uint8_t pair4_r, pair4_g, pair4_b, pair4_a; + }; +} + +#endif diff --git a/src/engine.cc b/src/engine.cc index 0685324..d9e9097 100644 --- a/src/engine.cc +++ b/src/engine.cc @@ -103,18 +103,18 @@ namespace openrayman if(m_current_input.command(input_command::toggle_fullscreen) && !m_last_input.command(input_command::toggle_fullscreen)) m_config.fullscreen = !m_config.fullscreen; - std::cout << "Update: " << m_current_delta_time * 1000 << "ms, " << m_total_frames << std::endl; + std::cout << "[openrayman::engine] Update: " << m_current_delta_time * 1000 << "ms, " << m_total_frames << std::endl; m_total_frames++; m_accumulated_frames_fps++; if(m_accumulated_time_fps >= 1) { m_fps = m_accumulated_frames_fps; - std::cout << "FPS: " << m_fps << std::endl; + std::cout << "[openrayman::engine] FPS: " << m_fps << std::endl; m_accumulated_time_fps = m_accumulated_frames_fps = 0; } while(m_accumulated_time_fixed >= 1 / 60.0) { - std::cout << "Fixed update: " << m_total_fixed_updates << std::endl; + std::cout << "[openrayman::engine] Fixed update: " << m_total_fixed_updates << std::endl; m_total_fixed_updates++; m_accumulated_time_fixed -= 1 / 60.0; } diff --git a/src/main.cc b/src/main.cc index c3e1ca3..4018404 100755 --- a/src/main.cc +++ b/src/main.cc @@ -3,6 +3,8 @@ #include #include #include +#include +#include bool console_open = false; @@ -19,6 +21,12 @@ void make_sure_console_open() #endif } +int fail_and_print(const std::string& msg) +{ + make_sure_console_open(); std::cout << msg << std::endl; + return EXIT_FAILURE; +} + int main(int argc, char** argv) { std::string selected_game = ""; @@ -30,10 +38,7 @@ int main(int argc, char** argv) { n++; if(n >= argc) - { - make_sure_console_open(); std::cout << "No game was specified." << std::endl; - return EXIT_FAILURE; - } + return fail_and_print("No game was specified");; std::string game(argv[n]); selected_game = game; } @@ -41,59 +46,61 @@ int main(int argc, char** argv) { n++; if(n >= argc) - { - make_sure_console_open(); std::cout << "No install folder was specified." << std::endl; - return EXIT_FAILURE; - } + return fail_and_print("No install folder was specified"); std::string install_folder(argv[n]); selected_install_folder = install_folder; } - if(str == "--decompile") + if(str == "--convert-to") { n++; if(n >= argc) - { - make_sure_console_open(); std::cout << "No type was specified." << std::endl; - return EXIT_FAILURE; - } - std::string type(argv[n]); + return fail_and_print("No format was specified"); + std::string format(argv[n]); n++; if(n >= argc) - { - make_sure_console_open(); std::cout << "No path was specified." << std::endl; - return EXIT_FAILURE; - } + return fail_and_print("No path was specified"); std::string path(argv[n]); n++; if(n >= argc) - { - make_sure_console_open(); std::cout << "No target was specified." << std::endl; - return EXIT_FAILURE; - } + return fail_and_print("No target was specified"); std::string target(argv[n]); - if(type == "odsb" || type == "rdsb") + if(format == "odsb" || format == "rdsb") { openrayman::dsb_decompiler decompiler; if(decompiler.decompile_dsb( path, target, - type == "odsb" ? + format == "odsb" ? openrayman::dsb_format::openrayman : openrayman::dsb_format::rayman2_decoded)) { return EXIT_SUCCESS; } else - { - std::cout << "Operation failed" << std::endl; - return EXIT_FAILURE; - } + return fail_and_print("Operation failed"); } else + return fail_and_print("Invalid format specified"); + } + if(str == "--inspect") + { + n++; + if(n >= argc) + return fail_and_print("No format was specified"); + std::string format(argv[n]); + n++; + if(n >= argc) + return fail_and_print("No path was specified"); + std::string path(argv[n]); + if(format == "odsb") { - make_sure_console_open(); std::cout << "Invalid type specified." << std::endl; - return EXIT_FAILURE; + openrayman::dsb_interpreter interpreter(path); + openrayman::dsb_interpreter_debugger debugger(interpreter); + debugger.print_summary(); + return interpreter.success() ? EXIT_SUCCESS : EXIT_FAILURE; } + else + return fail_and_print("Invalid format was specified"); } // Follow GNU format if(str == "--help") @@ -108,10 +115,13 @@ int main(int argc, char** argv) std::cout << " This is needed to ease modding support" << std::endl; std::cout << " This can also be done by starting the game without extracted data" << std::endl; std::cout << " In that case, you will get a directory picker" << std::endl; - std::cout << " --decompile \"type\" \"path\" \"target\" Decompiles the specified file into target." << std::endl; - std::cout << " Type can be any of:" << std::endl; - std::cout << " \"odsb\": Creates an OpenRayman dsb file" << std::endl; - std::cout << " \"rdsb\": Decodes a dsb file" << std::endl; + std::cout << " --convert-to \"format\" \"path\" \"target\" Converts the specified file into the target format" << std::endl; + std::cout << " Format can be any of:" << std::endl; + std::cout << " \"odsb\": Creates an OpenRayman DSB file" << std::endl; + std::cout << " \"rdsb\": Decodes a DSB file" << std::endl; + std::cout << " --inspect \"format\" \"path\" Inspects and prints info about the specified file" << std::endl; + std::cout << " Format can be any of:" << std::endl; + std::cout << " \"odsb\": Interprets the DSB and outputs all instructions" << std::endl; return EXIT_SUCCESS; } if(str == "--version")