#include "pack_archive.h" #include "ni_parser.h" #include "json.hpp" #include "serializers.h" #include #include #include #include #include #include PackArchive::PackArchive() { } std::vector PackArchive::GetImages() { std::vector 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(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 PackArchive::FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements) { char res_file[13]; // 12 + \0 std::vector 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 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 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 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(node.current->image_asset_index_in_ri); jNode["sound"] = static_cast(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(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(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(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(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(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(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(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(); }