Big changes

* OpenRayman can now read and extract from CNT archives (thanks Szymski/Szymekk!). Check --help for more information.
  In the future this, along with other functions, will be exported in a library called "libopenrayman". The plan
  is for libopenrayman to be able to read, inspect and extract all relevant files from the Rayman 2 engine.
  libopenrayman should be licensed as LGPL, compared to GPL for OpenRayman.
* OpenRayman can now, naturally, also read and convert GF files (CNT files are archives full of textures).
* OpenRayman no longer includes a DSB interpreter, and the decompiler has significantly changed.
  Only parts of the DSB that we need are now decompiled. Instead of direct decompilation into a .odsb,
  the decompiler produces several .json files that define the same information as the DSB in a much more
  human/mod friendly fashion.
* Some minor GTK and engine related changes.
This commit is contained in:
Hannes Mann 2016-07-02 04:24:58 +02:00
parent dfdb375801
commit e71a336693
40 changed files with 977 additions and 1246 deletions

View file

@ -30,5 +30,4 @@ endif()
set(BuildTests OFF) set(BuildTests OFF)
add_subdirectory(lib/gl3w) add_subdirectory(lib/gl3w)
add_subdirectory(lib/json) add_subdirectory(lib/json)
add_subdirectory(lib/glm)
add_subdirectory(src) add_subdirectory(src)

View file

@ -1,5 +1,7 @@
{ {
"dependencies": ["rayman2"], "dependencies": [
"rayman2"
],
"info": { "info": {
"description": "Modifications for OpenRayman", "description": "Modifications for OpenRayman",
"name": "Rayman 2: The Great Escape (OpenRayman)" "name": "Rayman 2: The Great Escape (OpenRayman)"

View file

@ -11,7 +11,6 @@ add_executable(openrayman WIN32 ${SOURCES} "${CMAKE_SOURCE_DIR}/lib/lodepng/lode
set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin") set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin")
target_link_libraries(openrayman gl3w) target_link_libraries(openrayman gl3w)
target_link_libraries(openrayman nlohmann_json) target_link_libraries(openrayman nlohmann_json)
target_link_libraries(openrayman glm)
if(WIN32) if(WIN32)
target_link_libraries(openrayman shlwapi) target_link_libraries(openrayman shlwapi)

View file

@ -0,0 +1,164 @@
#include <data_extractor/cnt/cnt_archive.h>
namespace openrayman
{
std::string cnt_file::get_full_path() const
{
return parent.get_full_path() + "/" + name;
}
cnt_file* cnt_directory_node::find_file(const std::string& name)
{
cnt_directory_node& archive_node = owner.get_archive_node();
if(name.find(archive_node.name) == 0)
return archive_node.find_file(std::string(name).erase(0, archive_node.name.size()));
std::string in_this = std::string(name).substr(0, name.find("/"));
for(cnt_file& file : m_files)
{
if(file.name == in_this)
return &file;
}
for(cnt_directory_node& child : m_children)
{
if(child.name == in_this)
return child.find_file(std::string(name).erase(0, std::min(name.size(), in_this.size() + 1)));
}
return nullptr;
}
cnt_directory_node* cnt_directory_node::find_child(const std::string& name)
{
if(name == "")
return this;
cnt_directory_node& archive_node = owner.get_archive_node();
if(name.find(archive_node.name) == 0)
return archive_node.find_child(std::string(name).erase(0, archive_node.name.size()));
std::string in_this = std::string(name).substr(0, name.find("/"));
for(cnt_directory_node& child : m_children)
{
if(child.name == in_this)
return child.find_child(std::string(name).erase(0, std::min(name.size(), in_this.size() + 1)));
}
return nullptr;
}
std::vector<std::uint8_t> cnt_file::read(std::int32_t start, std::int32_t count) const
{
std::vector<std::uint8_t> read(0, 0);
read.reserve(count);
std::size_t start_in_archive = ptr_in_archive + start;
std::size_t end = ptr_in_archive + start + size;
std::size_t pos = start_in_archive;
parent.owner.get_raw_stream().seekg(pos);
std::uint8_t c;
while(pos < end && pos < ptr_in_archive + start + count)
{
if(!parent.owner.get_raw_stream().read((char*)&c, 1))
break;
c ^= xor_key[(pos - start_in_archive) % 4];
read.push_back(c);
pos++;
}
return read;
}
cnt_archive::cnt_archive(const std::string& path) :
cnt_archive(*(new std::ifstream(path, std::ifstream::in | std::ifstream::binary)))
{
}
cnt_archive::cnt_archive(std::ifstream& stream) :
m_archive_node(*(new cnt_directory_node(*this, nullptr, "/"))), m_stream(stream), m_valid(false)
{
if(!stream.is_open())
return;
std::int32_t num_directories, num_files;
m_stream.read((char*)&num_directories, sizeof(std::int32_t));
m_stream.read((char*)&num_files, sizeof(std::int32_t));
if(num_directories <= 0 && num_files <= 0)
return;
char signature[2];
m_stream.read(signature, 2);
if(signature[0] != 0x1 || signature[1] != 0x1)
return;
char xor_key;
m_stream.read(&xor_key, 1);
for(std::int32_t dir = 0; dir < num_directories; dir++)
{
std::int32_t dir_name_len;
m_stream.read((char*)&dir_name_len, sizeof(std::int32_t));
std::string dir_name = "";
for(std::int32_t n = 0; n < dir_name_len; n++)
{
char c;
m_stream.read(&c, 1);
dir_name += c ^ xor_key;
}
std::vector<std::string> components;
std::size_t last_begun = 0;
for(std::size_t n = 0; n < dir_name.size(); n++)
{
if(dir_name[n] == '\\' || dir_name[n] == '/')
{
if(n - last_begun > 0)
components.push_back(dir_name.substr(last_begun, n - last_begun));
n++;
last_begun = n;
}
}
if((dir_name.size() - 1 - last_begun) > 0)
components.push_back(dir_name.substr(last_begun, dir_name.size() - 1 - last_begun));
cnt_directory_node* parent = &m_archive_node;
for(std::size_t n = 0; n < components.size() - 1; n++)
{
if(!parent->has_child(components[n]))
parent->push_child(components[n]);
parent = parent->find_child(components[n]);
}
parent->push_child(components[components.size() - 1], dir);
}
std::vector<cnt_directory_node*> all_directories = m_archive_node.get_children();
char tmp;
m_stream.read(&tmp, 1);
for(std::int32_t file = 0; file < num_files; file++)
{
std::int32_t dir_index, file_name_len;
m_stream.read((char*)&dir_index, sizeof(std::int32_t));
m_stream.read((char*)&file_name_len, sizeof(std::int32_t));
cnt_directory_node* parent = &m_archive_node;
for(cnt_directory_node* node : all_directories)
{
if(node->cnt_index == dir_index)
parent = node;
}
std::string file_name = "";
for(std::int32_t n = 0; n < file_name_len; n++)
{
char c;
m_stream.read(&c, 1);
file_name += c ^ xor_key;
}
char f_xor_key[4];
m_stream.read(f_xor_key, 4);
std::int32_t tmp, ptr_in_archive, file_size;
m_stream.read((char*)&tmp, sizeof(std::int32_t));
m_stream.read((char*)&ptr_in_archive, sizeof(std::int32_t));
m_stream.read((char*)&file_size, sizeof(std::int32_t));
parent->push_file(*parent, file_name, ptr_in_archive, file_size, f_xor_key);
}
m_valid = true;
}
}

View file

@ -0,0 +1,187 @@
#ifndef CNT_ARCHIVE_H
#define CNT_ARCHIVE_H
#include <data_extractor/data_decoder.h>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
namespace openrayman
{
// Forward declare
class cnt_archive;
class cnt_file;
class cnt_directory_node;
// CNT archives are used for storing textures.
// This is all heavily adapted from Rayman2Lib.
class cnt_archive
{
public:
cnt_archive(const std::string& path);
cnt_archive(std::ifstream& stream);
~cnt_archive()
{
if(m_stream.is_open())
m_stream.close();
}
// Returns true if the archive was loaded successfully and was valid.
bool valid() const
{
return m_valid;
}
// Returns a reference to the top most directory node, known as "/".
inline cnt_directory_node& get_archive_node()
{
return m_archive_node;
}
// Returns a reference to underlying stream used by the CNT archive.
inline std::ifstream& get_raw_stream()
{
return m_stream;
}
private:
cnt_directory_node& m_archive_node;
std::ifstream& m_stream;
bool m_valid;
};
class cnt_file
{
public:
cnt_file(cnt_directory_node& parent, const std::string& name, std::int32_t ptr_in_archive, std::int32_t size, char* xor_key) :
parent(parent), name(name), ptr_in_archive(ptr_in_archive), size(size)
{
for(std::size_t n = 0; n < 4; n++)
this->xor_key[n] = xor_key[n];
}
// Returns the full path specifier of this file.
// Cant inline because incomplete.
std::string get_full_path() const;
// Reads a max of "count" bytes from this file, starting at "start".
std::vector<std::uint8_t> read(std::int32_t start, std::int32_t count) const;
cnt_directory_node& parent;
std::string name;
std::int32_t size;
private:
std::int32_t ptr_in_archive;
char xor_key[4];
};
class cnt_directory_node
{
friend class cnt_archive;
public:
cnt_directory_node(cnt_archive& owner, cnt_directory_node* parent, const std::string& name, std::int32_t cnt_index = -1) :
owner(owner), parent(parent), name(name), cnt_index(cnt_index)
{
}
// Pushes a new directory as a child of this node.
inline void push_child(const std::string& child)
{
m_children.push_back(cnt_directory_node(owner, this, child));
}
// Pushes a new directory as a child of this node.
inline void push_child(const std::string& child, std::int32_t cnt_index)
{
m_children.push_back(cnt_directory_node(owner, this, child, cnt_index));
}
// Pushes a new file as a child of this node.
inline void push_file(cnt_directory_node& parent, const std::string& name, std::int32_t ptr_in_archive, std::int32_t size, char* xor_key)
{
m_files.push_back(cnt_file(parent, name, ptr_in_archive, size, xor_key));
}
// Returns all children of this directory node, recursively.
inline std::vector<cnt_directory_node*> get_children()
{
std::vector<cnt_directory_node*> values;
for(cnt_directory_node& node : m_children)
{
values.push_back(&node);
std::vector<cnt_directory_node*> node_children = node.get_children();
for(std::size_t n = 0; n < node_children.size(); n++)
values.push_back(node_children[n]);
}
return values;
}
// Returns a reference to all files that are children of this node.
inline std::vector<cnt_file*> get_files()
{
std::vector<cnt_file*> values;
for(cnt_file& file : m_files)
values.push_back(&file);
for(cnt_directory_node* child : get_children())
{
for(cnt_file& file : child->m_files)
values.push_back(&file);
}
return values;
}
// Returns a reference to all children that are explicit children of this node.
inline std::vector<cnt_directory_node>& get_local_children()
{
return m_children;
}
// Returns a reference to all files that are explicit children of this node.
inline std::vector<cnt_file>& get_local_files()
{
return m_files;
}
// Returns the full path specifier of this directory.
inline std::string get_full_path() const
{
if(parent == nullptr)
return name;
if(parent == &owner.get_archive_node())
return parent->get_full_path() + name;
return parent->get_full_path() + "/" + name;
}
// Returns true if the file exists in this directory.
inline bool has_file(const std::string& name)
{
return find_file(name) != nullptr;
}
// Returns true if the subdirectory exists in this directory.
inline bool has_child(const std::string& name)
{
return find_child(name) != nullptr;
}
// Returns a reference to the file if it was found, or nullptr if it was not found.
cnt_file* find_file(const std::string& name);
// Returns a reference to the child directory if it was found, or nullptr if it was not found.
cnt_directory_node* find_child(const std::string& name);
cnt_archive& owner;
cnt_directory_node* parent;
std::string name;
std::int32_t cnt_index;
private:
std::vector<cnt_directory_node> m_children;
std::vector<cnt_file> m_files;
};
}
#endif

View file

@ -1,8 +1,7 @@
#include <data_extractor/data_extractor.h> #include <data_extractor/data_extractor.h>
#include <data_extractor/dsb/dsb_decompiler.h> #include <data_extractor/dsb/dsb_decompiler.h>
#include <dsb_interpreter/dsb_instruction.h> #include <data_extractor/cnt/cnt_archive.h>
#include <dsb_interpreter/dsb_interpreter.h> #include <data_extractor/gf/gf_converter.h>
#include <dsb_interpreter/instructions/all.h>
#include <platform/message_box.h> #include <platform/message_box.h>
#include <platform/file.h> #include <platform/file.h>
#include <json.hpp> #include <json.hpp>
@ -13,11 +12,19 @@ namespace openrayman
{ {
bool data_extractor::extract(const std::string& install_folder) bool data_extractor::extract(const std::string& install_folder)
{ {
return if(check_prerequisites(install_folder) &&
check_prerequisites(install_folder) &&
create_base() && create_base() &&
decompile_game_dsb(install_folder) && decompile_game_dsb(install_folder) &&
make_game_resources(install_folder); make_game_resources(install_folder))
{
std::cout << "[openrayman::data_extractor] All tasks succeeded" << std::endl;
return true;
}
else
{
file::delete_directory(m_backend_specifics.get_data_path() + "/games/rayman2");
return false;
}
} }
bool data_extractor::check_prerequisites(const std::string& install_folder) bool data_extractor::check_prerequisites(const std::string& install_folder)
@ -81,22 +88,75 @@ namespace openrayman
{ {
std::cout << "[openrayman::data_extractor] Decompiling Game.dsb" << std::endl; std::cout << "[openrayman::data_extractor] Decompiling Game.dsb" << std::endl;
dsb_decompiler decompiler; 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); return decompiler.decompile_dsb(install_folder + "/Data/Game.dsb", m_backend_specifics.get_data_path() + "/games/rayman2", dsb_format::openrayman);
}
bool data_extractor::extract_recursive(std::string base, cnt_directory_node& parent)
{
gf_converter converter;
for(cnt_file& file : parent.get_local_files())
{
std::cout << "Extracting " << file.name << " which is " << (file.size / 1024.0) << " KB... ";
std::ofstream stream(file::fix_string(base + "/" + file.name.substr(0, file.name.length() - 3) + ".png"), std::ofstream::out | std::ofstream::binary);
if(!stream.is_open())
return false;
std::vector<std::uint8_t> array;
array.reserve(file.size);
std::size_t at = 0;
while(at < file.size)
{
std::vector<std::uint8_t> read = file.read(at, 1024 * 4096);
for(char c : read)
array.push_back(c);
at += read.size();
if(read.size() == 0)
break;
}
std::cout << "OK" << std::endl;
std::cout << "Converting " << file.name << " to png via gf_converter::convert_to_png... ";
std::vector<std::uint8_t> data = converter.convert_to_png(array);
stream.write((char*)data.data(), data.size());
std::cout << "OK" << std::endl;
}
for(cnt_directory_node& child : parent.get_local_children())
{
file::create_directory(base + "/" + child.name);
if(!extract_recursive(base + "/" + child.name, child))
return false;
}
return true;
} }
bool data_extractor::make_game_resources(const std::string& install_folder) bool data_extractor::make_game_resources(const std::string& install_folder)
{ {
dsb_interpreter interpreter(m_backend_specifics.get_data_path() + "/games/rayman2/game.odsb"); message_box::display("[openrayman::data_extractor] Info", "OpenRayman will now extract and convert all texture files used within the game."
if(interpreter.success()) "\nThis can take a couple of minutes depending on your disk and CPU speed.", false);
file::create_directory(m_backend_specifics.get_data_path() + "/games/rayman2/textures");
file::create_directory(m_backend_specifics.get_data_path() + "/games/rayman2/textures/vignettes");
cnt_archive textures(install_folder + "/Data/Textures.cnt");
cnt_archive vignettes(install_folder + "/Data/Vignette.cnt");
if(!textures.valid())
{ {
return true; message_box::display("[openrayman::data_extractor] Error!", "The texture archive could not be read.", true);
return false;
} }
dsb_instruction_invalid_dsb* error_reason = interpreter.get_instruction<dsb_instruction_invalid_dsb> if(!vignettes.valid())
(dsb_instruction_type::invalid_dsb); {
message_box::display("[openrayman::data_extractor] Error!", "The DSB interpreter failed to interpret the file game.odsb.\n\n" + message_box::display("[openrayman::data_extractor] Error!", "The texture archive containing vignettes could not be read.", true);
std::to_string(error_reason->line) + " : " + std::to_string(error_reason->column) + "\n" + return false;
error_reason->error + "\n" + }
error_reason->trace, true); if(!extract_recursive(m_backend_specifics.get_data_path() + "/games/rayman2/textures", textures.get_archive_node()))
return false; {
message_box::display("[openrayman::data_extractor] Error!", "A texture file could not be extracted or read.", true);
return false;
}
if(!extract_recursive(m_backend_specifics.get_data_path() + "/games/rayman2/textures/vignettes", vignettes.get_archive_node()))
{
message_box::display("[openrayman::data_extractor] Error!", "A texture file could not be extracted or read.", true);
return false;
}
return true;
} }
} }

View file

@ -6,6 +6,8 @@
namespace openrayman namespace openrayman
{ {
class cnt_directory_node;
// Extracts data from a valid Rayman 2: The Great Escape installation. // Extracts data from a valid Rayman 2: The Great Escape installation.
class data_extractor class data_extractor
{ {
@ -22,6 +24,7 @@ private:
bool check_prerequisites(const std::string& install_folder); bool check_prerequisites(const std::string& install_folder);
bool create_base(); bool create_base();
bool decompile_game_dsb(const std::string& install_folder); bool decompile_game_dsb(const std::string& install_folder);
bool extract_recursive(std::string base, cnt_directory_node& parent);
bool make_game_resources(const std::string& install_folder); bool make_game_resources(const std::string& install_folder);
const backend_specifics& m_backend_specifics; const backend_specifics& m_backend_specifics;

View file

@ -4,6 +4,7 @@
#include <cstdint> #include <cstdint>
#include <unordered_map> #include <unordered_map>
#include <iomanip> #include <iomanip>
#include <json.hpp>
namespace openrayman namespace openrayman
{ {
@ -31,15 +32,16 @@ namespace openrayman
decoder.set_virtual_position(0); decoder.set_virtual_position(0);
decoder.decode_array(buffer, length - 4); decoder.decode_array(buffer, length - 4);
std::ofstream target_stream(target, std::ofstream::out | std::ofstream::binary); if(fmt == dsb_format::rayman2_decoded)
if(target_stream.is_open())
{ {
if(fmt == dsb_format::openrayman) std::ofstream target_stream(target, std::ofstream::out | std::ofstream::binary);
decompile_sections(buffer, length - 4, target_stream); if(target_stream.is_open())
else
target_stream.write(buffer, length - 4); target_stream.write(buffer, length - 4);
return true; return target_stream.is_open();
} }
else
decompile_sections(buffer, length - 4, target);
return true;
} }
return false; return false;
} }
@ -52,10 +54,8 @@ namespace openrayman
} }
}; };
void dsb_decompiler::decompile_sections(char* source, std::size_t source_length, std::ofstream& target) void dsb_decompiler::decompile_sections(char* source, std::size_t source_length, const std::string& target)
{ {
target << "# Generated by OpenRayman " << openrayman::version << "\n";
target << "\n";
memorybuf streambuf(source, source + source_length); memorybuf streambuf(source, source + source_length);
std::istream in(&streambuf); std::istream in(&streambuf);
std::int32_t id; std::int32_t id;
@ -64,15 +64,13 @@ namespace openrayman
{ {
#define DECOMPILE_SECTION(id, name, function) \ #define DECOMPILE_SECTION(id, name, function) \
case (id): \ case id: \
{ \ { \
std::cout << "[openrayman::dsb_decompiler] Decompiling " \ std::cout << "[openrayman::dsb_decompiler] Decompiling " \
<< std::hex << "0x" << std::setfill('0') << std::setw(2) << (id) \ << std::hex << "0x" << std::setfill('0') << std::setw(2) << id\
<< " (decimal " << std::dec << id << ") \"" \ << " (decimal " << std::dec << id << ") \"" \
<< (name) << "\"" << std::endl; \ << name << "\"" << std::endl; \
target << "section " << (name) << "\n"; \ function(in, target); \
(function)(in, target); \
target << "\n"; \
break; \ break; \
} }
@ -102,8 +100,7 @@ namespace openrayman
} }
} }
// this allocates variables?!?! void dsb_decompiler::decompile_alloc(std::istream& source, const std::string& target)
void dsb_decompiler::decompile_alloc(std::istream& source, std::ofstream& target)
{ {
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
@ -111,30 +108,27 @@ namespace openrayman
{ {
if(id == 0x10) if(id == 0x10)
{ {
// skip(?, ?);
target << " skip(";
std::int32_t a, b; std::int32_t a, b;
source.read((char*)&a, sizeof(std::int32_t)); source.read((char*)&a, sizeof(std::int32_t));
source.read((char*)&b, sizeof(std::int32_t)); source.read((char*)&b, sizeof(std::int32_t));
target << std::to_string(a) << ", " << std::to_string(b) << ")\n";
} }
else else
{ {
// alloc(slot, value);
target << " alloc(" << std::to_string(id) << ", ";
std::int32_t a; std::int32_t a;
source.read((char*)&a, sizeof(std::int32_t)); source.read((char*)&a, sizeof(std::int32_t));
if(a == 0xFFFF) if(a == 0xFFFF)
return; return;
target << std::to_string(a) << ")\n";
} }
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
} }
// Adds levels void dsb_decompiler::decompile_lvl_list(std::istream& source, const std::string& target)
void dsb_decompiler::decompile_lvl_list(std::istream& source, std::ofstream& target)
{ {
std::ofstream levels_file(target + "/levels.json");
nlohmann::json levels_json;
levels_json["levels"] = nlohmann::json::array();
levels_json["initial"] = "";
std::int32_t num_levels; std::int32_t num_levels;
source.read((char*)&num_levels, sizeof(std::int32_t)); source.read((char*)&num_levels, sizeof(std::int32_t));
std::int32_t id; std::int32_t id;
@ -143,17 +137,21 @@ namespace openrayman
{ {
if(id == 0x1F) if(id == 0x1F)
{ {
// add(name); std::string str = read_string_with_length_u16(source);
target << " add(\""; // levels have four spaced appended for some reason, trim them
push_string_with_length_u16(source, target); str.erase(str.find_last_not_of(" \t") + 1);
target << " \")\n"; // this seems to be the case for all games, makes sense too
if(levels_json["initial"] == "")
levels_json["initial"] = str;
levels_json["levels"].push_back(str);
} }
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
if(levels_file.is_open())
levels_file << std::setw(4) << levels_json;
} }
// Data directories void dsb_decompiler::decompile_data_directories(std::istream& source, const std::string& target)
void dsb_decompiler::decompile_data_directories(std::istream& source, std::ofstream& target)
{ {
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
@ -181,22 +179,15 @@ namespace openrayman
source.read((char*)&str_length, sizeof(std::uint16_t)); source.read((char*)&str_length, sizeof(std::uint16_t));
char str[str_length]; char str[str_length];
source.read(str, str_length); source.read(str, str_length);
std::cout << "[openrayman::dsb_decompiler] Data directory " << std::hex << "0x" << id << ": " << str << std::endl;
// dir(name, path);
if(dir != "")
target << " dir(" << dir << ", \"" << str << "\")\n";
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
} }
// TODO: what is this?!? void dsb_decompiler::decompile_unknown_blob_0x20(std::istream& source, const std::string& target)
// is this ever found?!?!
void dsb_decompiler::decompile_unknown_blob_0x20(std::istream& source, std::ofstream& target)
{ {
std::cout << "[openrayman::dsb_decompiler] Warning! encountered 0x20" << std::endl; std::cout << "[openrayman::dsb_decompiler] Warning! encountered 0x20" << std::endl;
std::uint32_t size; std::uint32_t size;
source.read((char*)&size, sizeof(std::uint32_t)); source.read((char*)&size, sizeof(std::uint32_t));
target << " size(" << size << ")\n";
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
std::unordered_map<std::int32_t, int> encountered_ids; std::unordered_map<std::int32_t, int> encountered_ids;
@ -207,13 +198,14 @@ namespace openrayman
encountered_ids[id]++; encountered_ids[id]++;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
for(std::pair<std::int32_t, int> pair : encountered_ids)
target << " id_encounter(" << std::hex << "0x" << pair.first << std::dec << ", " << pair.second << ")\n";
} }
// vignettes are basically loading screens void dsb_decompiler::decompile_vignette(std::istream& source, const std::string& target)
void dsb_decompiler::decompile_vignette(std::istream& source, std::ofstream& target)
{ {
std::ofstream vignette_file(target + "/vignette.json");
nlohmann::json vignette_json;
vignette_json["bar"] = nlohmann::json::object();
vignette_json["image"] = "";
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
while(id != 0xFFFF) while(id != 0xFFFF)
@ -223,14 +215,22 @@ namespace openrayman
case 71: case 71:
case 72: case 72:
{ {
target << " load_img(\""; std::string str = read_string_with_length_u16(source);
push_string_with_length_u16(source, target); if(str.find_first_of("Random<") == 0)
target << "\")\n"; {
// this is supposed to select a random vignette
// but we just use the first one
str = str.substr(7, std::string::npos);
str = str.substr(0, str.find_first_of(','));
}
if(str.find_last_of(".bmp") != std::string::npos)
str = str.substr(0, str.find_last_of(".bmp") - 3);
vignette_json["image"] = str;
break; break;
} }
case 75: case 75:
{ {
target << " display()\n"; // display
break; break;
} }
case 76: case 76:
@ -243,74 +243,62 @@ namespace openrayman
source.read((char*)&tmp, sizeof(std::int32_t)); source.read((char*)&tmp, sizeof(std::int32_t));
std::uint8_t r, g, b, a; 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); source.read((char*)&r, 1); source.read((char*)&g, 1); source.read((char*)&b, 1); source.read((char*)&a, 1);
target << " color(" if(id == 76)
<< (id == 76 ? "outline" : "inside") vignette_json["bar"]["outside"] = { r, g, b, a };
<< ", " << std::to_string(r) else
<< ", " << std::to_string(g) vignette_json["bar"]["inside"] = { r, g, b, a };
<< ", " << std::to_string(b)
<< ", " << std::to_string(a)
<< ")\n";
break; break;
} }
case 78: case 78:
{ {
std::uint8_t colors[(sizeof(std::uint8_t) * 4) * 4]; std::uint8_t colors[(sizeof(std::uint8_t) * 4) * 4];
source.read((char*)colors, (sizeof(std::uint8_t) * 4) * 4); source.read((char*)colors, (sizeof(std::uint8_t) * 4) * 4);
target << " color(bar"; vignette_json["bar"]["main"] = nlohmann::json::object();
for(int n = 0; n < 4; n++) // TODO: are these coordinates right?!?
{ vignette_json["bar"]["main"]["(x, -)"] = { colors[0], colors[1], colors[2], colors[3] };
target << ", " << std::to_string(colors[(n * 4)]) vignette_json["bar"]["main"]["(y, -)"] = { colors[4], colors[5], colors[6], colors[7] };
<< ", " << std::to_string(colors[(n * 4) + 1]) vignette_json["bar"]["main"]["(x, +)"] = { colors[8], colors[9], colors[10], colors[11] };
<< ", " << std::to_string(colors[(n * 4) + 2]) vignette_json["bar"]["main"]["(y, +)"] = { colors[12], colors[13], colors[14], colors[15] };
<< ", " << std::to_string(colors[(n * 4) + 3]);
}
target << ")\n";
break; break;
} }
case 79: case 79:
{ {
std::int32_t values[4]; std::int32_t vec[4];
source.read((char*)values, sizeof(std::int32_t) * 4); source.read((char*)vec, sizeof(std::int32_t) * 4);
target << " create_bar("; vignette_json["bar"]["pos"] = { vec[0], vec[1], vec[2], vec[3] };
for(int n = 0; n < 4; n++)
target << (n == 0 ? "" : ", ") << values[n];
target << ")\n";
break; break;
} }
case 80: case 80:
{ {
target << " add_bar()\n"; // add_bar
break; break;
} }
case 81: case 81:
{ {
std::int32_t max; std::int32_t max;
source.read((char*)&max, sizeof(std::int32_t)); source.read((char*)&max, sizeof(std::int32_t));
target << " bar_max(" << max << ")\n"; vignette_json["bar"]["max"] = max;
break; break;
} }
} }
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
if(vignette_file.is_open())
vignette_file << std::setw(4) << vignette_json;
} }
void dsb_decompiler::decompile_texture_files(std::istream& source, std::ofstream& target) void dsb_decompiler::decompile_texture_files(std::istream& source, const std::string& target)
{ {
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
while(id != 0xFFFF) while(id != 0xFFFF)
{ {
if(id == 66) read_string_with_length_u16(source);
target << " add(textures, \"";
else
target << " add(vignettes, \"";
push_string_with_length_u16(source, target);
target << "\")\n";
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
} }
void dsb_decompiler::decompile_unknown_blob_0x6e(std::istream& source, std::ofstream& target) void dsb_decompiler::decompile_unknown_blob_0x6e(std::istream& source, const std::string& target)
{ {
std::cout << "[openrayman::dsb_decompiler] Warning! encountered 0x6e" << std::endl; std::cout << "[openrayman::dsb_decompiler] Warning! encountered 0x6e" << std::endl;
std::uint8_t tmp = 0x00; std::uint8_t tmp = 0x00;
@ -320,7 +308,7 @@ namespace openrayman
source.read((char*)&tmp, 1); source.read((char*)&tmp, 1);
} }
void dsb_decompiler::decompile_game_options(std::istream& source, std::ofstream& target) void dsb_decompiler::decompile_game_options(std::istream& source, const std::string& target)
{ {
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
@ -329,17 +317,9 @@ namespace openrayman
switch(id) switch(id)
{ {
case 0x65: case 0x65:
{
target << " default_file(\"";
push_string_with_length_u16(source, target);
target << "\")\n";
break;
}
case 0x66: case 0x66:
{ {
target << " current_file(\""; read_string_with_length_u16(source);
push_string_with_length_u16(source, target);
target << "\")\n";
break; break;
} }
case 0x67: case 0x67:
@ -353,8 +333,7 @@ namespace openrayman
} }
} }
// idk what this is?!?! void dsb_decompiler::decompile_sound_banks(std::istream& source, const std::string& target)
void dsb_decompiler::decompile_sound_banks(std::istream& source, std::ofstream& target)
{ {
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
@ -366,14 +345,12 @@ namespace openrayman
{ {
std::int32_t unknown; std::int32_t unknown;
source.read((char*)&unknown, sizeof(std::int32_t)); source.read((char*)&unknown, sizeof(std::int32_t));
target << " unknown(" << unknown << ")\n";
} }
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
} }
// idk what this is either?! void dsb_decompiler::decompile_load_sound_banks(std::istream& source, const std::string& target)
void dsb_decompiler::decompile_load_sound_banks(std::istream& source, std::ofstream& target)
{ {
std::int32_t id; std::int32_t id;
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
@ -383,7 +360,6 @@ namespace openrayman
{ {
std::int32_t bank; std::int32_t bank;
source.read((char*)&bank, sizeof(std::int32_t)); source.read((char*)&bank, sizeof(std::int32_t));
target << " add_bank2(" << bank << ")\n";
} }
else else
{ {
@ -391,17 +367,14 @@ namespace openrayman
source.read((char*)&size, sizeof(std::int32_t)); source.read((char*)&size, sizeof(std::int32_t));
char bytes[size * 4]; char bytes[size * 4];
source.read((char*)&bytes, size * 4); source.read((char*)&bytes, size * 4);
target << " add_bank(";
for(int n = 0; n < size * 4; n++)
target << (n == 0 ? "" : ", ") << std::hex << "0x" << (int)bytes[n];
target << ")\n";
} }
source.read((char*)&id, sizeof(std::int32_t)); source.read((char*)&id, sizeof(std::int32_t));
} }
} }
void dsb_decompiler::push_string_null_terminated(std::istream& source, std::ofstream& target) const std::string dsb_decompiler::read_string_null_terminated(std::istream& source)
{ {
std::stringstream target;
char c = 0x00; char c = 0x00;
source.read(&c, 1); source.read(&c, 1);
while(c != '\0') while(c != '\0')
@ -409,14 +382,15 @@ namespace openrayman
target << c; target << c;
source.read(&c, 1); source.read(&c, 1);
} }
return target.str();
} }
void dsb_decompiler::push_string_with_length_u16(std::istream& source, std::ofstream& target) const std::string dsb_decompiler::read_string_with_length_u16(std::istream& source)
{ {
std::uint16_t length; std::uint16_t length;
source.read((char*)&length, sizeof(std::uint16_t)); source.read((char*)&length, sizeof(std::uint16_t));
char str[length]; char str[length];
source.read(str, length); source.read(str, length);
target << str; return std::string(str);
} }
} }

View file

@ -13,7 +13,7 @@ namespace openrayman
// The default format. // The default format.
// This is a more human readable/editable format than the binary format used by Rayman 2. // This is a more human readable/editable format than the binary format used by Rayman 2.
// This can be interpreted by the dsb engine. // This can be interpreted by the dsb engine.
// Uses the extension .odsb // Creates several files such as vignette.json, levels.json
openrayman, openrayman,
// The format used by the Rayman 2: The Great Escape engine. // The format used by the Rayman 2: The Great Escape engine.
@ -25,34 +25,34 @@ namespace openrayman
rayman2_decoded rayman2_decoded
}; };
// Decodes and optionally decompiles DSB files. // Decodes and decompiles DSB files.
// DSB files are compiled scripts, with different sections. // DSB files are compiled scripts, with different sections.
// This is all heavily adapted from Rayman2Lib. // This is all heavily adapted from Rayman2Lib.
class dsb_decompiler class dsb_decompiler
{ {
public: public:
// Decodes and optionally decompiles the specified DSB file into the target file. // Decodes and optionally decompiles the specified DSB file into several files in the target directory.
// This function assumes that the source file exists and that the target location is accessible. // This function assumes that the source file exists and that the target directory is accessible.
// Returns true if the function succeeded. // Returns true if the function succeeded.
bool decompile_dsb(const std::string& source, const std::string& target, dsb_format fmt); bool decompile_dsb(const std::string& source, const std::string& target, dsb_format fmt);
private: private:
void decompile_sections(char* source, std::size_t source_length, std::ofstream& target); void decompile_sections(char* source, std::size_t source_length, const std::string& target);
void decompile_alloc(std::istream& source, std::ofstream& target); void decompile_alloc(std::istream& source, const std::string& target);
void decompile_lvl_list(std::istream& source, std::ofstream& target); void decompile_lvl_list(std::istream& source, const std::string& target);
void decompile_data_directories(std::istream& source, std::ofstream& target); void decompile_data_directories(std::istream& source, const std::string& target);
void decompile_unknown_blob_0x20(std::istream& source, std::ofstream& target); void decompile_unknown_blob_0x20(std::istream& source, const std::string& target);
void decompile_vignette(std::istream& source, std::ofstream& target); void decompile_vignette(std::istream& source, const std::string& target);
void decompile_texture_files(std::istream& source, std::ofstream& target); void decompile_texture_files(std::istream& source, const std::string& target);
void decompile_unknown_blob_0x6e(std::istream& source, std::ofstream& target); void decompile_unknown_blob_0x6e(std::istream& source, const std::string& target);
void decompile_game_options(std::istream& source, std::ofstream& target); void decompile_game_options(std::istream& source, const std::string& target);
void decompile_sound_banks(std::istream& source, std::ofstream& target); void decompile_sound_banks(std::istream& source, const std::string& target);
void decompile_load_sound_banks(std::istream& source, std::ofstream& target); void decompile_load_sound_banks(std::istream& source, const std::string& target);
// Reads a null-terminated string from source to target. // Reads a null-terminated string from source.
void push_string_null_terminated(std::istream& source, std::ofstream& target); const std::string read_string_null_terminated(std::istream& source);
// Reads the length of the string from source and then writes it to target. // Reads the length of the string from source.
void push_string_with_length_u16(std::istream& source, std::ofstream& target); const std::string read_string_with_length_u16(std::istream& source);
data_decoder m_decoder; data_decoder m_decoder;
}; };

View file

@ -0,0 +1,131 @@
#include <data_extractor/gf/gf_converter.h>
#include <lodepng.h>
namespace openrayman
{
struct memorybuf : std::streambuf
{
memorybuf(char* begin, char* end)
{
setg(begin, begin, end);
}
};
bool gf_converter::get_gf_info(std::vector<std::uint8_t>& in, std::size_t* w, std::size_t* h, std::uint8_t* num_channels, std::uint8_t* repeat_byte)
{
memorybuf streambuf((char*)in.data(), (char*)in.data() + in.size());
std::istream in_stream(&streambuf);
if(in.size() <= 0)
return false;
std::int32_t signature;
in_stream.read((char*)&signature, sizeof(std::int32_t));
if(signature != 0x378 && signature != 0x22b8)
return false;
std::int32_t width, height;
in_stream.read((char*)&width, sizeof(std::int32_t));
in_stream.read((char*)&height, sizeof(std::int32_t));
*w = width;
*h = height;
in_stream.read((char*)num_channels, 1);
in_stream.read((char*)repeat_byte, 1);
return true;
}
std::vector<std::uint8_t> gf_converter::convert_to_rgba(std::vector<std::uint8_t>& in, std::size_t* w, std::size_t* h)
{
#define FAIL \
{ \
w = h = 0; \
return std::vector<std::uint8_t>(0, 0); \
}
memorybuf streambuf((char*)in.data(), (char*)in.data() + in.size());
std::istream in_stream(&streambuf);
if(in.size() <= 0)
FAIL;
std::int32_t signature;
in_stream.read((char*)&signature, sizeof(std::int32_t));
if(signature != 0x378 && signature != 0x22b8)
FAIL;
std::int32_t width, height;
in_stream.read((char*)&width, sizeof(std::int32_t));
in_stream.read((char*)&height, sizeof(std::int32_t));
*w = width;
*h = height;
std::vector<std::uint8_t> data(width * height * 4, 255);
std::uint8_t channels, repeat_byte;
in_stream.read((char*)&channels, 1);
in_stream.read((char*)&repeat_byte, 1);
if(channels != 3 && channels != 4)
FAIL;
std::cout << "size: " << width << "x" << height << " channels: " << std::to_string(channels) << " ";
std::vector<std::uint8_t> r_ch(width * height, 0), g_ch(width * height, 0), b_ch(width * height, 0), a_ch(width * height, 255);
read_channel(in_stream, repeat_byte, b_ch);
read_channel(in_stream, repeat_byte, g_ch);
read_channel(in_stream, repeat_byte, r_ch);
if(channels == 4)
read_channel(in_stream, repeat_byte, a_ch);
std::size_t x, y;
for(y = 0; y < height; y++)
{
for(x = 0; x < width; x++)
{
data[(((y * width) + x) * 4)] = r_ch[((height - 1 - y) * width) + x];
data[(((y * width) + x) * 4) + 1] = g_ch[((height - 1 - y) * width) + x];
data[(((y * width) + x) * 4) + 2] = b_ch[((height - 1 - y) * width) + x];
data[(((y * width) + x) * 4) + 3] = a_ch[((height - 1 - y) * width) + x];
}
}
return data;
}
void gf_converter::read_channel(std::istream& data, std::uint8_t repeat_byte, std::vector<std::uint8_t>& channel)
{
std::size_t at = 0;
while(at < channel.size())
{
std::uint8_t value;
data.read((char*)&value, 1);
if(value == repeat_byte)
{
data.read((char*)&value, 1);
std::uint8_t count;
data.read((char*)&count, 1);
for(std::size_t n = 0; n < count; n++)
{
// either we are wrong or some files are formatted weird?
// this seems to turn out ok
if(at + n < channel.size())
channel[at + n] = value;
}
at += count;
}
else
{
channel[at] = value;
at++;
}
}
}
std::vector<std::uint8_t> gf_converter::convert_to_png(std::vector<std::uint8_t>& in)
{
std::size_t w, h;
std::vector<std::uint8_t> data = convert_to_rgba(in, &w, &h);
if(data.size() == 0)
return std::vector<std::uint8_t>(0, 0);
std::vector<std::uint8_t> png;
std::uint32_t error = lodepng::encode(png, data, w, h);
if(error)
return std::vector<std::uint8_t>(0, 0);
return png;
}
}

View file

@ -0,0 +1,30 @@
#ifndef GF_CONVERTER_H
#define GF_CONVERTER_H
#include <vector>
#include <iostream>
#include <cstdint>
namespace openrayman
{
// GF (graphics texture) files are used as textures.
// This is all heavily adapted from Rayman2Lib.
class gf_converter
{
public:
// Returns true if information about the GF file could be extracted.
bool get_gf_info(std::vector<std::uint8_t>& in, std::size_t* w, std::size_t* h, std::uint8_t* num_channels, std::uint8_t* repeat_byte);
// Converts a GF file in memory into a raw array of bytes, consisting of a RGBA32 image.
// This function will return an array of size 0 if it fails.
// Alternatively, check w or h if they have a value of 0.
std::vector<std::uint8_t> convert_to_rgba(std::vector<std::uint8_t>& in, std::size_t* w, std::size_t* h);
// Converts a GF file in memory into a raw array of bytes, consisting of a PNG image.
std::vector<std::uint8_t> convert_to_png(std::vector<std::uint8_t>& in);
private:
void read_channel(std::istream& data, std::uint8_t repeat_byte, std::vector<std::uint8_t>& channel);
};
}
#endif

View file

@ -1,217 +0,0 @@
#ifndef DSB_INSTRUCTION_H
#define DSB_INSTRUCTION_H
#include <cstdlib>
#include <string>
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<img, ?, ?, ...>".
// 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<dsb_instruction_?> or similar.
dsb_instruction_type type;
};
}
#endif

View file

@ -1,219 +0,0 @@
#include <dsb_interpreter/dsb_interpreter.h>
#include <dsb_interpreter/dsb_interpreter_string_utils.h>
#include <dsb_interpreter/instructions/all.h>
#include <platform/file.h>
#include <glm/glm.hpp>
#include <cstdint>
#include <sstream>
#include <iomanip>
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))
{
if(m_interpreted_dsb.size() > 0 && m_interpreted_dsb[0]->type == dsb_instruction_type::invalid_dsb)
return;
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 instruction_name = read_until_char(line, n, '(');
n += instruction_name.length() + 1;
std::size_t instr_begins_at = n - 1;
std::vector<std::string> instruction_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] == ',')
{
instruction_args.push_back(arg);
arg = "";
n++;
n += skip_whitespace(line, n);
}
else if(line[n] == ')')
break;
else
n++;
}
if(arg.length() > 0)
instruction_args.push_back(arg);
interpret_instruction(current_section, instruction_name, instruction_args, instr_begins_at, line_n);
}
void dsb_interpreter::interpret_instruction(dsb_section_type current_section, const std::string& name, const std::vector<std::string>& args, std::size_t instr_begins_at, std::size_t line_n)
{
dsb_instruction* instr = nullptr;
std::vector<float> args_float(args.size(), 0);
std::stringstream stream;
for(const std::string& arg : args)
{
stream << arg;
float val = 0;
stream >> val;
args_float.push_back(val);
}
#define MAP_INSTRUCTION(section, from, to, min_num_args, ...) \
if(current_section == section && name == from) \
{ \
if(args.size() < min_num_args) \
{ \
set_error(line_n, instr_begins_at, "Instruction " + name + " requires at least " + std::to_string(min_num_args) + " arguments.", current_section, "dsb_interpreter::interpret_instruction"); \
return; \
} \
instr = new to(__VA_ARGS__); \
}
#define IGNORE_INSTRUCTION(section, instr_name) \
if(current_section == section && name == instr_name) \
{ \
std::cout << "[openrayman::dsb_interpreter] Warning! Ignored instruction with name " << instr_name << std::endl; \
return; \
}
// dir
MAP_INSTRUCTION(dsb_section_type::data_directories,
"dir",
dsb_instruction_set_data_dir,
2, string_to_data_directory(args[0]), args[1]);
// add
MAP_INSTRUCTION(dsb_section_type::texture_files,
"add",
dsb_instruction_set_texture_file,
2, args[0] == "vignettes" ? dsb_texture_file_id::vignettes : dsb_texture_file_id::textures, args[1]);
// load_img
MAP_INSTRUCTION(dsb_section_type::vignette,
"load_img",
dsb_instruction_vignette_load_img,
1, args[0]);
// color
MAP_INSTRUCTION(dsb_section_type::vignette,
"color",
dsb_instruction_vignette_set_color,
5, string_to_vignette_id(args[0]),
glm::vec4(args_float[1] / 255, args_float[2] / 255, args_float[3] / 255, args_float[4] / 255),
args.size() > 5 ? glm::vec4(args_float[5] / 255, args_float[6] / 255, args_float[7] / 255, args_float[8] / 255) : glm::vec4(0, 0, 0, 0),
args.size() > 5 ? glm::vec4(args_float[9] / 255, args_float[10] / 255, args_float[11] / 255, args_float[12] / 255) : glm::vec4(0, 0, 0, 0),
args.size() > 5 ? glm::vec4(args_float[13] / 255, args_float[14] / 255, args_float[15] / 255, args_float[16] / 255) : glm::vec4(0, 0, 0, 0));
// create_bar
MAP_INSTRUCTION(dsb_section_type::vignette,
"create_bar",
dsb_instruction_vignette_create_bar,
4, args_float[0], args_float[1], args_float[2], args_float[3]);
// level_add
MAP_INSTRUCTION(dsb_section_type::levels,
"add",
dsb_instruction_level_add,
1, args[0]);
// TODO: some of these at least
IGNORE_INSTRUCTION(dsb_section_type::alloc, "alloc");
IGNORE_INSTRUCTION(dsb_section_type::alloc, "skip");
IGNORE_INSTRUCTION(dsb_section_type::vignette, "bar_max");
IGNORE_INSTRUCTION(dsb_section_type::vignette, "add_bar");
IGNORE_INSTRUCTION(dsb_section_type::vignette, "display");
IGNORE_INSTRUCTION(dsb_section_type::unknown_blob_0x20, "size");
IGNORE_INSTRUCTION(dsb_section_type::unknown_blob_0x20, "id_encounter");
IGNORE_INSTRUCTION(dsb_section_type::game_options, "default_file");
IGNORE_INSTRUCTION(dsb_section_type::game_options, "current_file");
IGNORE_INSTRUCTION(dsb_section_type::sound_banks, "unknown");
IGNORE_INSTRUCTION(dsb_section_type::load_sound_banks, "add_bank");
if(instr == nullptr)
set_error(line_n, instr_begins_at, "Invalid instruction (got \"" + name + "\" in section " + section_to_string(current_section) + ").", current_section, "dsb_interpreter::interpret_instruction");
else
m_interpreted_dsb.push_back(instr);
}
}

View file

@ -1,87 +0,0 @@
#ifndef DSB_INTERPRETER_H
#define DSB_INTERPRETER_H
#include <dsb_interpreter/dsb_instruction.h>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
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<typename T>
inline T* get_instruction(dsb_instruction_type type) const
{
for(dsb_instruction* instruction : m_interpreted_dsb)
{
if(instruction->type == type)
return static_cast<T*>(instruction);
}
return nullptr;
}
// Returns all instances of the specified instruction type.
template<typename T>
inline std::vector<T*> get_instructions(dsb_instruction_type type) const
{
std::vector<T*> values;
for(dsb_instruction* instruction : m_interpreted_dsb)
{
if(instruction->type == type)
values.push_back(static_cast<T*>(instruction));
}
return values;
}
// Returns a reference to the raw instruction array.
// This can be manually searched and interpreted.
inline const std::vector<dsb_instruction*>& 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);
void interpret_instruction(dsb_section_type current_section, const std::string& name, const std::vector<std::string>& args, std::size_t func_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<dsb_instruction*> m_interpreted_dsb;
};
}
#endif

View file

@ -1,89 +0,0 @@
#include <dsb_interpreter/dsb_interpreter_debugger.h>
#include <dsb_interpreter/dsb_instruction.h>
#include <dsb_interpreter/instructions/all.h>
#include <glm/gtx/string_cast.hpp>
#include <iostream>
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<dsb_instruction_comment*>(instruction);
std::cout << " comment -> \"" << cast->comment << "\"" << std::endl;
break;
}
case dsb_instruction_type::begin_section:
{
dsb_instruction_begin_section* cast = static_cast<dsb_instruction_begin_section*>(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<dsb_instruction_set_data_dir*>(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<dsb_instruction_set_texture_file*>(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<dsb_instruction_vignette_load_img*>(instruction);
std::cout << " image -> \"" << cast->image << "\"" << std::endl;
break;
}
case dsb_instruction_type::vignette_set_color:
{
dsb_instruction_vignette_set_color* cast = static_cast<dsb_instruction_vignette_set_color*>(instruction);
std::cout << " id -> dsb_vignette_id::" << vignette_id_to_string(cast->id) << std::endl;
std::cout << " color -> [" << cast->color.r << ", " << cast->color.g << ", " << cast->color.b << ", " << cast->color.a << "]" << std::endl;
std::cout << " color2 -> [" << cast->color2.r << ", " << cast->color2.g << ", " << cast->color2.b << ", " << cast->color2.a << "]" << std::endl;
std::cout << " color3 -> [" << cast->color3.r << ", " << cast->color3.g << ", " << cast->color3.b << ", " << cast->color3.a << "]" << std::endl;
std::cout << " color4 -> [" << cast->color4.r << ", " << cast->color4.g << ", " << cast->color4.b << ", " << cast->color4.a << "]" << std::endl;
break;
}
case dsb_instruction_type::vignette_create_bar:
{
dsb_instruction_vignette_create_bar* cast = static_cast<dsb_instruction_vignette_create_bar*>(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<dsb_instruction_level_add*>(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_invalid_dsb>
(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;
}
}
}

View file

@ -1,23 +0,0 @@
#ifndef DSB_INTERPRETER_DEBUGGER_H
#define DSB_INTERPRETER_DEBUGGER_H
#include <dsb_interpreter/dsb_interpreter.h>
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

View file

@ -1,83 +0,0 @@
#ifndef DSB_INTERPRETER_STRING_UTILS_H
#define DSB_INTERPRETER_STRING_UTILS_H
#include <string>
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

View file

@ -1,9 +0,0 @@
#include <dsb_interpreter/instructions/begin_section/dsb_instruction_begin_section.h>
#include <dsb_interpreter/instructions/comment/dsb_instruction_comment.h>
#include <dsb_interpreter/instructions/invalid_dsb/dsb_instruction_invalid_dsb.h>
#include <dsb_interpreter/instructions/level_add/dsb_instruction_level_add.h>
#include <dsb_interpreter/instructions/set_data_dir/dsb_instruction_set_data_dir.h>
#include <dsb_interpreter/instructions/set_texture_file/dsb_instruction_set_texture_file.h>
#include <dsb_interpreter/instructions/vignette/create_bar/dsb_instruction_vignette_create_bar.h>
#include <dsb_interpreter/instructions/vignette/load_img/dsb_instruction_vignette_load_img.h>
#include <dsb_interpreter/instructions/vignette/set_color/dsb_instruction_vignette_set_color.h>

View file

@ -1,24 +0,0 @@
#ifndef DSB_INSTRUCTION_BEGIN_SECTION_H
#define DSB_INSTRUCTION_BEGIN_SECTION_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,24 +0,0 @@
#ifndef DSB_INSTRUCTION_COMMENT_H
#define DSB_INSTRUCTION_COMMENT_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,30 +0,0 @@
#ifndef DSB_INSTRUCTION_INVALID_DSB_H
#define DSB_INSTRUCTION_INVALID_DSB_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,21 +0,0 @@
#ifndef DSB_INSTRUCTION_LEVEL_ADD_H
#define DSB_INSTRUCTION_LEVEL_ADD_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,92 +0,0 @@
#ifndef DSB_INSTRUCTION_SET_DATA_DIR_H
#define DSB_INSTRUCTION_SET_DATA_DIR_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,33 +0,0 @@
#ifndef DSB_INSTRUCTION_SET_TEXTURE_FILE_H
#define DSB_INSTRUCTION_SET_TEXTURE_FILE_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,20 +0,0 @@
#ifndef DSB_INSTRUCTION_VIGNETTE_CREATE_BAR_H
#define DSB_INSTRUCTION_VIGNETTE_CREATE_BAR_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,20 +0,0 @@
#ifndef DSB_INSTRUCTION_VIGNETTE_LOAD_IMG_H
#define DSB_INSTRUCTION_VIGNETTE_LOAD_IMG_H
#include <dsb_interpreter/dsb_instruction.h>
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

View file

@ -1,62 +0,0 @@
#ifndef DSB_INSTRUCTION_VIGNETTE_SET_COLOR_H
#define DSB_INSTRUCTION_VIGNETTE_SET_COLOR_H
#include <dsb_interpreter/dsb_instruction.h>
#include <cstdint>
#include <glm/glm.hpp>
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
{
dsb_instruction_vignette_set_color(dsb_vignette_id id,
glm::vec4 color, glm::vec4 color2, glm::vec4 color3, glm::vec4 color4) :
dsb_instruction(dsb_instruction_type::vignette_set_color),
color(color), color2(color2), color3(color3), color4(color4)
{
}
// The id of the color to set.
dsb_vignette_id id;
// Color gradient, all are only used when id is "bar".
glm::vec4 color, color2, color3, color4;
};
}
#endif

View file

@ -103,18 +103,16 @@ namespace openrayman
if(m_current_input.command(input_command::toggle_fullscreen) && !m_last_input.command(input_command::toggle_fullscreen)) if(m_current_input.command(input_command::toggle_fullscreen) && !m_last_input.command(input_command::toggle_fullscreen))
m_config.fullscreen = !m_config.fullscreen; m_config.fullscreen = !m_config.fullscreen;
std::cout << "[openrayman::engine] Update: " << m_current_delta_time * 1000 << "ms, " << m_total_frames << std::endl; std::cout << "[openrayman::engine] FPS: " << get_fps() << std::endl;
m_total_frames++; m_total_frames++;
m_accumulated_frames_fps++; m_accumulated_frames_fps++;
if(m_accumulated_time_fps >= 1) if(m_accumulated_time_fps >= 1)
{ {
m_fps = m_accumulated_frames_fps; m_fps = m_accumulated_frames_fps;
std::cout << "[openrayman::engine] FPS: " << m_fps << std::endl;
m_accumulated_time_fps = m_accumulated_frames_fps = 0; m_accumulated_time_fps = m_accumulated_frames_fps = 0;
} }
while(m_accumulated_time_fixed >= 1 / 60.0) while(m_accumulated_time_fixed >= 1 / 60.0)
{ {
std::cout << "[openrayman::engine] Fixed update: " << m_total_fixed_updates << std::endl;
m_total_fixed_updates++; m_total_fixed_updates++;
m_accumulated_time_fixed -= 1 / 60.0; m_accumulated_time_fixed -= 1 / 60.0;
} }

View file

@ -117,10 +117,11 @@ public:
return m_total_fixed_updates; return m_total_fixed_updates;
} }
// Returns the amount of frames that were executed the previous second. // Returns the amount of frames that were executed during the previous second.
inline std::uint64_t get_fps() const inline double get_fps() const
{ {
return m_fps; // this is more accurate
return (m_fps + (1 / m_current_delta_time)) / 2;
} }
// Returns a reference to the active config. // Returns a reference to the active config.

View file

@ -82,15 +82,19 @@ namespace openrayman
return false; return false;
} }
const std::string game::find_file(const std::string& file) const const std::string game::find_file(const std::string& file, bool show_error_msg) const
{ {
if(has_file(file, true)) if(has_file(file, true))
return m_location + "/" + file; return m_location + "/" + file;
for(const game& game : m_dependencies) for(const game& game : m_dependencies)
{ {
if(game.has_file(file, false)) if(game.has_file(file, false))
return game.find_file(file); return game.find_file(file, show_error_msg);
} }
if(show_error_msg)
message_box::display("[openrayman::game] Error!", "The file \"" + file + "\" could not be found in the game \"" + m_info.name + "\"."
"\nor any of its dependencies."
"\nThis could be the result of a broken dependency.", true);
return ""; return "";
} }
} }

View file

@ -53,7 +53,7 @@ public:
// Dependencies are searched from first to last specified in the manifest. // Dependencies are searched from first to last specified in the manifest.
// An empty string is returned if the file was not found. // An empty string is returned if the file was not found.
// File existence can be checked via the function has_file. // File existence can be checked via the function has_file.
const std::string find_file(const std::string& file) const; const std::string find_file(const std::string& file, bool show_error_msg) const;
private: private:
bool m_valid; bool m_valid;
std::string m_location; std::string m_location;

View file

@ -1,32 +1,13 @@
#include <info.h> #include <info.h>
#include <engine.h> #include <engine.h>
#include <tools/common_tools.h>
#include <data_extractor/dsb/dsb_decompiler.h> #include <data_extractor/dsb/dsb_decompiler.h>
#include <dsb_interpreter/dsb_interpreter.h> #include <tools/gf_tools.h>
#include <dsb_interpreter/dsb_interpreter_debugger.h> #include <tools/cnt_tools.h>
#include <cstdlib> #include <vector>
#include <iostream> #include <iostream>
#include <locale> #include <platform/file.h>
#include <platform/standalone_backend_specifics.h>
bool console_open = false;
void make_sure_console_open()
{
#ifdef _WIN32
if(!console_open)
{
if(!AttachConsole(-1))
AllocConsole();
freopen("CONOUT$", "w", stdout);
console_open = true;
}
#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) int main(int argc, char** argv)
{ {
@ -80,8 +61,9 @@ int main(int argc, char** argv)
else else
return fail_and_print("Operation failed"); return fail_and_print("Operation failed");
} }
else else if(format == "png")
return fail_and_print("Invalid format specified"); return openrayman::gf_tools::convert_to_png(path, target);
return fail_and_print("Invalid format specified");
} }
if(str == "--inspect") if(str == "--inspect")
{ {
@ -93,15 +75,32 @@ int main(int argc, char** argv)
if(n >= argc) if(n >= argc)
return fail_and_print("No path was specified"); return fail_and_print("No path was specified");
std::string path(argv[n]); std::string path(argv[n]);
if(format == "odsb") if(format == "cnt")
{ return openrayman::cnt_tools::print_hierarchy(path);
openrayman::dsb_interpreter interpreter(path); else if(format == "gf")
openrayman::dsb_interpreter_debugger debugger(interpreter); return openrayman::gf_tools::print_info(path);
debugger.print_summary(); return fail_and_print("Invalid format was specified");
return interpreter.success() ? EXIT_SUCCESS : EXIT_FAILURE; }
} if(str == "--force-reset-rayman2")
else {
return fail_and_print("Invalid format was specified"); openrayman::standalone_backend_specifics backend_specifics;
openrayman::file::delete_directory(backend_specifics.get_data_path() + "/games/rayman2");
}
if(str == "--extract-cnt-to")
{
n++;
if(n >= argc)
return fail_and_print("No archive was specified");
std::string archive(argv[n]);
n++;
if(n >= argc)
return fail_and_print("No path was specified");
std::string path(argv[n]);
n++;
if(n >= argc)
return fail_and_print("No target was specified");
std::string target(argv[n]);
return openrayman::cnt_tools::extract(archive, path, target);
} }
// Follow GNU format // Follow GNU format
if(str == "--help") if(str == "--help")
@ -116,13 +115,15 @@ int main(int argc, char** argv)
std::cout << " This is needed to ease modding support" << std::endl; 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 << " 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 << " In that case, you will get a directory picker" << std::endl;
std::cout << " --convert-to \"format\" \"path\" \"target\" Converts the specified file into the target format" << 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\": Decompiles the DSB into the specified directory (several files will be created)" << std::endl;
std::cout << " \"odsb\": Creates an OpenRayman DSB file" << std::endl;
std::cout << " \"rdsb\": Decodes a 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 << " \"png\": Decodes a GF (graphics texture) file into a png file" << std::endl;
std::cout << " Format can be any of:" << std::endl; std::cout << " --inspect \"format\" \"path\" Inspects and prints info about the specified file or directory" << std::endl;
std::cout << " \"odsb\": Interprets the DSB and outputs all instructions" << std::endl; std::cout << " \"cnt\": Prints file hierarchy" << std::endl;
std::cout << " \"gf\": Prints width, height and number of channels" << std::endl;
std::cout << " --extract-cnt-to \"archive\" \"path\" \"target\" Extracts a file from a CNT archive" << std::endl;
std::cout << " --force-reset-rayman2 Forces a removal of the rayman2 base game, if it exists" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
if(str == "--version") if(str == "--version")
@ -146,7 +147,6 @@ int main(int argc, char** argv)
INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow)
{ {
// fuck windows
return main(__argc, __argv); return main(__argc, __argv);
} }

View file

@ -2,6 +2,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <shlwapi.h> #include <shlwapi.h>
#include <shellapi.h>
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
#else #else
@ -10,6 +11,8 @@
#include <libgen.h> #include <libgen.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <ftw.h>
#include <cstdio>
#endif #endif
namespace openrayman namespace openrayman
@ -43,4 +46,33 @@ namespace openrayman
mkdir(fix_string(path).c_str(), 0775); mkdir(fix_string(path).c_str(), 0775);
#endif #endif
} }
#ifndef _WIN32
// http://stackoverflow.com/questions/3184445/how-to-clear-directory-contents-in-c-on-linux-basically-i-want-to-do-rm-rf/3184915#3184915
int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
int rv = remove(fpath);
if (rv)
perror(fpath);
return rv;
}
#endif
void file::delete_directory(const std::string& path)
{
#ifdef _WIN32
SHFILEOPSTRUCT fop;
fop.hwnd = nullptr;
fop.wFunc = FO_DELETE;
fop.pFrom = std::string(path + "\0").c_str();
fop.pTo = nullptr;
fop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO;
fop.fAnyOperationsAborted = false;
fop.lpszProgressTitle = nullptr;
fop.hNameMappings = nullptr;
SHFileOperation(&fop);
#else
nftw(path.c_str(), &unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
#endif
}
} }

View file

@ -18,6 +18,9 @@ public:
// Creates a new directory with the specified path. // Creates a new directory with the specified path.
static void create_directory(const std::string& path); static void create_directory(const std::string& path);
// Deletes the directory with the specified path.
static void delete_directory(const std::string& path);
// The path separator of the current platform. // The path separator of the current platform.
#ifdef _WIN32 #ifdef _WIN32
static const char path_separator = '\\'; static const char path_separator = '\\';

View file

@ -30,6 +30,7 @@ namespace openrayman
GtkWidget* dialog = gtk_message_dialog_new(nullptr, flags, error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_OK, msg.c_str()); GtkWidget* dialog = gtk_message_dialog_new(nullptr, flags, error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_OK, msg.c_str());
gtk_window_set_title(GTK_WINDOW(dialog), title.c_str()); gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
gtk_dialog_run(GTK_DIALOG(dialog)); gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
gtk_widget_destroy(dialog); gtk_widget_destroy(dialog);
} }
#endif #endif
@ -53,6 +54,7 @@ namespace openrayman
GtkWidget* dialog = gtk_message_dialog_new(nullptr, flags, error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_YES_NO, msg.c_str()); GtkWidget* dialog = gtk_message_dialog_new(nullptr, flags, error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_YES_NO, msg.c_str());
gtk_window_set_title(GTK_WINDOW(dialog), title.c_str()); gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
gint result = gtk_dialog_run(GTK_DIALOG(dialog)); gint result = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
gtk_widget_destroy(dialog); gtk_widget_destroy(dialog);
return result == GTK_RESPONSE_YES; return result == GTK_RESPONSE_YES;
} }
@ -99,9 +101,11 @@ namespace openrayman
char* directory_cstr = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); char* directory_cstr = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
std::string directory = std::string(directory_cstr); std::string directory = std::string(directory_cstr);
g_free(directory_cstr); g_free(directory_cstr);
gtk_widget_hide(dialog);
gtk_widget_destroy(dialog); gtk_widget_destroy(dialog);
return directory; return directory;
} }
gtk_widget_hide(dialog);
gtk_widget_destroy(dialog); gtk_widget_destroy(dialog);
} }
#endif #endif

55
src/tools/cnt_tools.cc Normal file
View file

@ -0,0 +1,55 @@
#include <tools/cnt_tools.h>
#include <data_extractor/cnt/cnt_archive.h>
#include <tools/common_tools.h>
#include <cstdlib>
namespace openrayman
{
namespace cnt_tools
{
int extract(const std::string& path, const std::string& name_in, const std::string& target)
{
cnt_archive archive(path);
if(!archive.valid())
return fail_and_print("Operation failed");
cnt_file* file;
if((file = archive.get_archive_node().find_file(name_in)) == nullptr)
return fail_and_print("Operation failed");
std::ofstream output(target, std::ofstream::out | std::ofstream::binary);
if(!output.is_open())
return fail_and_print("Operation failed");
std::size_t at = 0;
while(at < file->size)
{
std::vector<std::uint8_t> read = file->read(at, 1024 * 4096);
output.write((char*)read.data(), read.size());
at += read.size();
if(read.size() == 0)
break;
}
return EXIT_SUCCESS;
}
void print_recursive(cnt_directory_node& node, int indent)
{
std::string indent_str(indent, ' ');
std::cout << indent_str << node.name << " -> " << std::endl;
for(cnt_file& file : node.get_local_files())
{
std::cout << indent_str << " " << file.name << " -> " << std::endl;
std::cout << indent_str << " " << " " << (file.size / 1024.0) << " KB" << std::endl;
}
for(cnt_directory_node& child : node.get_local_children())
print_recursive(child, indent + 4);
}
int print_hierarchy(const std::string& path)
{
cnt_archive archive(path);
if(!archive.valid())
return fail_and_print("Operation failed");
print_recursive(archive.get_archive_node(), 0);
return EXIT_SUCCESS;
}
}
}

17
src/tools/cnt_tools.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CNT_TOOLS_H
#define CNT_TOOLS_H
#include <string>
namespace openrayman
{
// Tools for extracting from and printing information about CNT files.
// Used by main.cc
namespace cnt_tools
{
int extract(const std::string& path, const std::string& name_in, const std::string& target);
int print_hierarchy(const std::string& path);
}
}
#endif

35
src/tools/common_tools.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef COMMON_TOOLS_H
#define COMMON_TOOLS_H
#ifdef _WIN32
#include <windows.h>
#endif
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <string>
// Common tools used by main.cc and other tools
static bool console_open = false;
inline void make_sure_console_open()
{
#ifdef _WIN32
if(!console_open)
{
if(!AttachConsole(-1))
AllocConsole();
freopen("CONOUT$", "w", stdout);
console_open = true;
}
#endif
}
inline int fail_and_print(const std::string& msg)
{
make_sure_console_open(); std::cout << msg << std::endl;
return EXIT_FAILURE;
}
#endif

69
src/tools/gf_tools.cc Normal file
View file

@ -0,0 +1,69 @@
#include <tools/gf_tools.h>
#include <data_extractor/gf/gf_converter.h>
#include <tools/common_tools.h>
#include <cstdlib>
#include <cstdint>
#include <iostream>
#include <fstream>
#include <iomanip>
namespace openrayman
{
namespace gf_tools
{
int convert_to_png(const std::string& path, const std::string& target)
{
openrayman::gf_converter converter;
std::ifstream gf_stream(path, std::ifstream::in | std::ifstream::binary);
if(!gf_stream.is_open())
return fail_and_print("Operation failed");
gf_stream.seekg(0, std::ifstream::end);
std::size_t size = gf_stream.tellg();
gf_stream.seekg(0);
std::uint8_t gf_data[size];
gf_stream.read((char*)gf_data, size);
std::vector<std::uint8_t> vector(gf_data, gf_data + size);
std::vector<std::uint8_t> png_data = converter.convert_to_png(vector);
if(png_data.size() == 0)
return fail_and_print("Operation failed");
std::cout << std::endl;
std::ofstream png_stream(target, std::ofstream::out | std::ofstream::binary);
if(!png_stream.is_open())
return fail_and_print("Operation failed");
return png_stream.write((char*)png_data.data(), png_data.size()) ? EXIT_SUCCESS : EXIT_FAILURE;
}
int print_info(const std::string& path)
{
openrayman::gf_converter converter;
std::ifstream gf_stream(path, std::ifstream::in | std::ifstream::binary);
if(!gf_stream.is_open())
return fail_and_print("Operation failed");
gf_stream.seekg(0, std::ifstream::end);
std::size_t size = gf_stream.tellg();
gf_stream.seekg(0);
std::uint8_t gf_data[size];
gf_stream.read((char*)gf_data, size);
std::size_t w, h;
std::uint8_t num_channels, repeat_byte;
std::vector<std::uint8_t> vector(gf_data, gf_data + size);
if(!converter.get_gf_info(vector, &w, &h, &num_channels, &repeat_byte))
return fail_and_print("Operation failed");
std::cout << "Width: " << w << std::endl;
std::cout << "Height: " << h << std::endl;
std::cout << "Channels: " << (num_channels == 4 ? "Blue, Green, Red, Alpha" : "Blue, Green, Red") << std::endl;
std::cout << "Repeat byte (for compression): 0x" << std::hex << std::setfill('0') << std::setw(2) << (std::int32_t)repeat_byte << std::endl;
return EXIT_SUCCESS;
}
}
}

17
src/tools/gf_tools.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef GF_TOOLS_H
#define GF_TOOLS_H
#include <string>
namespace openrayman
{
// Tools for converting and printing information about GF files.
// Used by main.cc
namespace gf_tools
{
int convert_to_png(const std::string& path, const std::string& target);
int print_info(const std::string& path);
}
}
#endif