open-story-teller/story-editor/src/importers/pack_archive.cpp

621 lines
17 KiB
C++

#include "pack_archive.h"
#include "ni_parser.h"
#include "json.hpp"
#include "serializers.h"
#include <iostream>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <filesystem>
PackArchive::PackArchive()
{
}
std::vector<std::string> PackArchive::GetImages()
{
std::vector<std::string> imgList;
for (uint32_t i = 0; i < ni_get_number_of_images(); i++)
{
char buffer[13];
ni_get_image(buffer, i);
imgList.push_back(buffer);
}
return imgList;
}
std::string PackArchive::GetFileName(const std::string &path)
{
auto found = path.find_last_of("/\\");
return path.substr(found+1);
}
std::string PackArchive::GetFileExtension(const std::string &FileName)
{
if(FileName.find_last_of(".") != std::string::npos)
return FileName.substr(FileName.find_last_of(".")+1);
return "";
}
void PackArchive::ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace)
{
std::size_t found;
do
{
found = theString.find(toFind);
if (found != std::string::npos)
{
theString.replace(found, 1, toReplace);
}
}
while (found != std::string::npos);
}
void PackArchive::EraseString(std::string &theString, const std::string &toErase)
{
std::size_t found;
found = theString.find(toErase);
if (found != std::string::npos)
{
theString.erase(found, toErase.size());
}
}
std::string PackArchive::ToUpper(const std::string &input)
{
std::string str = input;
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
return str;
}
void PackArchive::Unzip(const std::string &filePath, const std::string &parent_dest_dir)
{
// std::string fileName = GetFileName(filePath);
// std::string ext = GetFileExtension(fileName);
// EraseString(fileName, "." + ext); // on retire l'extension du pack
// std::string path = parent_dest_dir + "/" + ToUpper(fileName);
if (std::filesystem::exists(parent_dest_dir))
{
// 1. First delete files
for(const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator(parent_dest_dir))
{
if (std::filesystem::is_regular_file(entry)) {
std::filesystem::remove(entry.path());
}
}
}
// 2. then delete directories
std::filesystem::remove_all(parent_dest_dir);
std::filesystem::create_directories(parent_dest_dir);
(void) Zip::Unzip(filePath, parent_dest_dir, "");
}
static bool endsWith(const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
}
bool StringToFile(const std::string &filePath, const std::string &data)
{
bool success = false;
std::ofstream outFile(filePath, std::ifstream::out);
if (outFile.is_open())
{
outFile << data << std::endl;
outFile.close();
success = true;
}
return success;
}
void PackArchive::DecipherFileOnDisk(const std::string &fileName)
{
FILE * pFile;
long lSize = 512;
char * buffer;
size_t result;
pFile = fopen ( fileName.c_str() , "rb+" );
if (pFile==NULL) {fputs ("File error",stderr); exit (1);}
// allocate memory to contain the whole file:
buffer = (char*) malloc (sizeof(char)*lSize);
if (buffer == NULL) {fprintf(stderr, "Memory error: %s", fileName.c_str()); exit (2);}
// copy the file into the buffer:
result = fread (buffer,1,lSize,pFile);
if (result != lSize) {fprintf(stderr, "Reading error: %s", fileName.c_str()); exit (3);}
/* the whole file is now loaded in the memory buffer. */
// Decipher
ni_decode_block512(reinterpret_cast<uint8_t *>(buffer));
fseek (pFile , 0 , SEEK_SET); // ensure we are at the begining
// Write back data on the disk
fwrite (buffer , sizeof(char), lSize, pFile);
// terminate
fclose (pFile);
free (buffer);
}
void WriteDataOnDisk(const std::string &fileName, const uint8_t *data, uint32_t size)
{
FILE * pFile;
pFile = fopen ( fileName.c_str() , "wb" );
fwrite (data , sizeof(char), size, pFile);
fclose (pFile);
}
void PackArchive::DecipherFiles(const std::string &directory, const std::string &suffix)
{
for (const auto & rf : std::filesystem::directory_iterator(directory))
{
std::string oldFile = rf.path();
// std::cout << oldFile << std::endl;
DecipherFileOnDisk(oldFile);
std::string newName = oldFile + suffix;
rename(oldFile.c_str(), newName.c_str());
}
}
std::vector<std::string> PackArchive::FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements)
{
char res_file[13]; // 12 + \0
std::vector<std::string> resList;
for (int i = 0; i < nb_elements; i++)
{
memcpy(res_file, &data[i*12], 12);
res_file[12] = '\0';
std::string res_file_string(res_file);
ReplaceCharacter(res_file_string, "\\", "/");
resList.push_back(res_file_string);
m_resources[type + std::to_string(i)] = res_file_string;
}
return resList;
}
void PackArchive::DecipherAll(const std::string &packFileName, const std::string &parent_dest_dir)
{
// return;
Unzip(packFileName, parent_dest_dir);
Load(packFileName);
std::string path = mPackName + "/rf";
for (const auto & entry : std::filesystem::directory_iterator(path))
{
std::cout << entry.path() << std::endl;
DecipherFiles(entry.path(), ".bmp");
}
path = mPackName + "/sf";
for (const auto & entry : std::filesystem::directory_iterator(path))
{
std::cout << entry.path() << std::endl;
DecipherFiles(entry.path(), ".mp3");
}
nlohmann::json j;
std::ofstream chip32("pack.chip32");
/*
$imageBird DC8 "example.bmp", 8 ; data
$someConstant DC32 12456789
; DSxx to declare a variable in RAM, followed by the number of elements
$RamData1 DV32 1 ; one 32-bit integer
$MyArray DV8 10 ; array of 10 bytes
; label definition
.entry: ;; comment here should work
*/
// NI file is not ciphered
uint8_t data[512];
uint32_t size = ni_get_ri_block(data);
WriteDataOnDisk(mPackName + "/ri", data, size);
nlohmann::json jsonImages;
{
std::vector<std::string> lst = FilesToJson("ri", data, mNiFile.image_assets_count);
for (auto &l : lst)
{
nlohmann::json obj;
obj["file"] = l;
obj["description"] = "";
obj["format"] = "bmp";
jsonImages.push_back(obj);
}
}
j["images"] = jsonImages;
size = ni_get_si_block(data);
WriteDataOnDisk(mPackName + "/si", data, size);
nlohmann::json jsonSounds;
{
nlohmann::json obj;
std::vector<std::string> lst = FilesToJson("si", data, mNiFile.sound_assets_count);
for (auto &l : lst)
{
nlohmann::json obj;
obj["file"] = l;
obj["description"] = "";
obj["format"] = "mp3";
jsonSounds.push_back(obj);
}
}
j["sounds"] = jsonSounds;
size = ni_get_li_block(data);
WriteDataOnDisk(mPackName + "/li", data, size);
std::vector<uint32_t> transitions;
// each entry of the transitions array is a 32-bit integer
for (int i = 0; i < size;)
{
transitions.push_back(leu32_get(&data[i]));
i += 4;
}
// Transform into JSON
node_info_t node;
nlohmann::json jsonNodes;
for (int i = 0; i < mNiFile.node_size; i++)
{
nlohmann::json jNode;
ni_get_node_info(i, &node);
jNode["id"] = i;
jNode["image"] = static_cast<int>(node.current->image_asset_index_in_ri);
jNode["sound"] = static_cast<int>(node.current->sound_asset_index_in_si);
jNode["auto_jump"] = node.current->auto_play == 1 ? true : false;
nlohmann::json jumpArray;
for (int jIndex = 0; jIndex < node.current->ok_transition_number_of_options; jIndex++)
{
jumpArray.push_back(transitions[node.current->ok_transition_action_node_index_in_li + jIndex]);
}
jNode["jumps"] = jumpArray;
// Autre cas
if (node.current->ok_transition_number_of_options >= 5)
{
// For now, consider that this is a bad format
// In the future, consider using ok_transition_selected_option_index when ok_transition_number_of_options == 10
// 00 00 00 00 ==> ok transition à zéro
// 0A 00 00 00 ==> nombre spécial, ou vraiment l'offset dans le fichier LI ?
// 01 00 00 00 ==> l'index dans le fichier LI à l'offset (disons, le premier élément)
std::cout << "!!!!!!!!!!!!!!!!!!" << std::endl;
}
chip32 << ".node" << std::to_string(i) << ":\r\n";
if (node.current->image_asset_index_in_ri != 0xFFFFFFFF)
{
// Image index is in register R0
chip32 << "\tmov " << "r0, " << "$ri" << std::to_string(node.current->image_asset_index_in_ri) << "\r\n";
// print image syscall
chip32 << "\tsyscall 1\r\n";
}
if (node.current->sound_asset_index_in_si != 0xFFFFFFFF)
{
// Image index is in register R0
chip32 << "\tmov " << "r0, " << "$si" << std::to_string(node.current->sound_asset_index_in_si) << "\r\n";
// print image syscall
chip32 << "\tsyscall 2\r\n";
}
chip32 << "$li" << std::to_string(i) << " DC8 ";
size = ni_get_li_block(data);
for (int tr = 0; tr < node.current->ok_transition_number_of_options; tr++)
{
uint32_t val = leu32_get(&data[(node.current->ok_transition_action_node_index_in_li + tr) * 4]);
chip32 << std::to_string(val);
if (tr < (node.current->ok_transition_number_of_options - 1))
{
chip32 << ", ";
}
}
chip32 << "\r\n";
chip32 << "\tsyscall 3\r\n"; // wait select
// TODO: tester le retour d'un wait event
jsonNodes.push_back(jNode);
}
j["nodes"] = jsonNodes;
j["code"] = mPackName;
j["name"] = "";
j["type"] = "lunii";
std::ofstream o("pack.json");
o << std::setw(4) << j << std::endl; // pretty print
o.close();
chip32.close();
}
bool PackArchive::Load(const std::string &filePath)
{
bool success = false;
mZip.Close();
mCurrentNodeId = 0;
std::string fileName = GetFileName(filePath);
std::string ext = GetFileExtension(fileName);
EraseString(fileName, "." + ext); // on retire l'extension du pack
mPackName = ToUpper(fileName);
std::cout << "Pack name: " << mPackName << std::endl;
if (mZip.Open(filePath, true))
{
std::cout << "Number of files: " << mZip.NumberOfEntries() << std::endl;
if (ParseNIFile(mPackName))
{
success = true;
std::cout << "Parse NI file success\r\n" << std::endl;
ni_dump(&mNiFile);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
else
{
std::cout << "Parse NI file error\r\n" << std::endl;
}
}
return success;
}
std::string PackArchive::OpenImage(const std::string &fileName)
{
std::string f;
mZip.GetFile(fileName, f);
return f;
}
std::string PackArchive::GetImage(const std::string &fileName)
{
//"C8B39950DE174EAA8E852A07FC468267/rf/000/05FB5530"
std::string imagePath = mPackName + "/rf/" + fileName;
ReplaceCharacter(imagePath, "\\", "/");
std::cout << "Loading " + imagePath << std::endl;
return OpenImage(imagePath);
}
std::string PackArchive::CurrentImage()
{
return GetImage(std::string(mCurrentNode.ri_file));
}
std::string PackArchive::CurrentSound()
{
//"C8B39950DE174EAA8E852A07FC468267/sf/000/05FB5530"
std::string soundPath = mPackName + "/sf/" + std::string(mCurrentNode.si_file);
ReplaceCharacter(soundPath, "\\", "/");
std::cout << "Loading " + soundPath << std::endl;
std::string f;
if (mZip.GetFile(soundPath, f))
{
ni_decode_block512(reinterpret_cast<uint8_t *>(f.data()));
return f;
}
else
{
std::cout << "Cannot load file from ZIP" << std::endl;
}
return "";
}
std::string PackArchive::CurrentSoundName()
{
return std::string(mCurrentNode.si_file);
}
bool PackArchive::AutoPlay()
{
return mCurrentNode.current->auto_play;
}
bool PackArchive::IsRoot() const
{
return mCurrentNodeId == 0;
}
bool PackArchive::IsWheelEnabled() const
{
return mCurrentNode.current->wheel;
}
void PackArchive::Next()
{
// L'index de circulation dans le tableau des transitions commence à 1 (pas à zéro ...)
uint32_t index = 1;
if (mCurrentNode.current->ok_transition_selected_option_index < mNodeForChoice.current->ok_transition_number_of_options)
{
index = mCurrentNode.current->ok_transition_selected_option_index + 1;
}
// sinon on revient à l'index 0 (début du tableau des transitions)
mCurrentNodeId = ni_get_node_index_in_li(mNodeForChoice.current->ok_transition_action_node_index_in_li, index - 1);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
void PackArchive::Previous()
{
// L'index de circulation dans le tableau des transitions commence à 1 (pas à zéro ...)
uint32_t index = 1;
if (mCurrentNode.current->ok_transition_selected_option_index > 1)
{
index = mCurrentNode.current->ok_transition_selected_option_index - 1;
}
else
{
index = mNodeForChoice.current->ok_transition_number_of_options;
}
mCurrentNodeId = ni_get_node_index_in_li(mNodeForChoice.current->ok_transition_action_node_index_in_li, index - 1);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
void PackArchive::OkButton()
{
if (mCurrentNode.current->home_transition_number_of_options > 0)
{
// On doit faire un choix!
// On sauvegarde ce noeud car il va servir pour naviguer dans les choix
mNodeIdForChoice = mCurrentNodeId;
ni_get_node_info(mNodeIdForChoice, &mNodeForChoice);
}
mCurrentNodeId = ni_get_node_index_in_li(mCurrentNode.current->ok_transition_action_node_index_in_li, mCurrentNode.current->ok_transition_selected_option_index);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
bool PackArchive::HasImage()
{
return std::string(mCurrentNode.ri_file).size() > 1;
}
bool PackArchive::ParseNIFile(const std::string &root)
{
bool success = true;
std::string f;
if (mZip.GetFile(root + "/li", f))
{
ni_set_li_block(reinterpret_cast<const uint8_t *>(f.data()), f.size());
}
else
{
success = false;
std::cout << "[PACK_ARCHIVE] Cannot find LI file" << std::endl;
}
if (mZip.GetFile(root + "/ri", f))
{
ni_set_ri_block(reinterpret_cast<const uint8_t *>(f.data()), f.size());
}
else
{
success = false;
std::cout << "[PACK_ARCHIVE] Cannot find RI file" << std::endl;
}
if (mZip.GetFile(root + "/si", f))
{
ni_set_si_block(reinterpret_cast<const uint8_t *>(f.data()), f.size());
}
else
{
success = false;
std::cout << "[PACK_ARCHIVE] Cannot find SI file" << std::endl;
}
if (mZip.GetFile(root + "/ni", f))
{
success = success & ni_parser(&mNiFile, reinterpret_cast<const uint8_t *>(f.data()));
}
else
{
std::cout << "[PACK_ARCHIVE] Cannot find NI file" << std::endl;
}
return success;
}
std::string PackArchive::HexDump(const char *desc, const void *addr, int len)
{
int i;
unsigned char buff[17];
const unsigned char *pc = static_cast<const unsigned char*>(addr);
std::stringstream ss;
// Output description if given.
if (desc != nullptr)
{
ss << desc << ":\n";
}
if (len == 0) {
ss << " ZERO LENGTH\n";
return ss.str();
}
if (len < 0) {
ss << " NEGATIVE LENGTH: " << len << "\n";
return ss.str();
}
// Process every byte in the data.
for (i = 0; i < len; i++) {
// Multiple of 16 means new line (with line offset).
if ((i % 16) == 0) {
// Just don't print ASCII for the zeroth line.
if (i != 0)
ss << " " << buff << "\n";
// Output the offset.
ss << " " << std::setfill('0') << std::setw(4) << std::hex << i;
}
// Now the hex byte_to_hexcode for the specific character.
ss << " " << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(pc[i]) << ", ";
// And store a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
buff[i % 16] = '.';
else
buff[i % 16] = pc[i];
buff[(i % 16) + 1] = '\0';
}
// Pad out last line if not exactly 16 characters.
while ((i % 16) != 0) {
ss << " ";
i++;
}
// And print the final ASCII bit.
ss << " "<< buff << "\n";
return ss.str();
}