diff --git a/core/interfaces/i_story_db.h b/core/interfaces/i_story_db.h index 7a77bd5..92aceff 100644 --- a/core/interfaces/i_story_db.h +++ b/core/interfaces/i_story_db.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include class IStoryDb { @@ -19,6 +21,8 @@ public: virtual ~IStoryDb() {} + using ViewType = std::ranges::ref_view>; + virtual bool FindStory(const std::string &uuid, Info &info) = 0; virtual void AddStory(IStoryDb::Info &info, int origin) = 0; diff --git a/core/src/story_project.cpp b/core/src/story_project.cpp index 8f23062..610d0d2 100644 --- a/core/src/story_project.cpp +++ b/core/src/story_project.cpp @@ -169,6 +169,20 @@ void StoryProject::DeleteNode(const std::string_view &page_uuid, const std::stri } } +std::shared_ptr StoryProject::GetPage(const std::string &uuid) +{ + for (const auto & p : m_pages) + { + if (p->Uuid() == uuid) + { + return p; + } + } + + return nullptr; + +} + void StoryProject::DeleteLink(const std::string_view &page_uuid, std::shared_ptr c) { diff --git a/core/src/story_project.h b/core/src/story_project.h index 01400ce..1c69c53 100644 --- a/core/src/story_project.h +++ b/core/src/story_project.h @@ -114,6 +114,7 @@ public: void SetTitleImage(const std::string &titleImage); void SetTitleSound(const std::string &titleSound); + void SetDescription(const std::string &description) { m_description = description; } std::string GetTitleImage() const { return m_titleImage; } std::string GetTitleSound() const { return m_titleSound; } @@ -130,6 +131,8 @@ public: // Node interaction std::shared_ptr CreatePage(const std::string &uuid); + std::shared_ptr GetPage(const std::string &uuid); + std::shared_ptr CreateNode(const std::string_view &page, const std::string &type); void AddConnection(const std::string_view &page, std::shared_ptr c); void DeleteNode(const std::string_view &page, const std::string &id); diff --git a/shared/library_manager.cpp b/shared/library_manager.cpp index e975889..ae0493a 100644 --- a/shared/library_manager.cpp +++ b/shared/library_manager.cpp @@ -245,6 +245,8 @@ void LibraryManager::ParseCommercialStore(const std::string &jsonFileName) const auto& response = j["response"]; m_storyDb.ClearCommercial(); + std::cout << "Found " << response.size() << " commercial stories" << std::endl; + for (auto it = response.begin(); it != response.end(); ++it) { const auto& pack = it.value(); diff --git a/shared/resource_manager.h b/shared/resource_manager.h index efd0652..381741d 100644 --- a/shared/resource_manager.h +++ b/shared/resource_manager.h @@ -20,7 +20,12 @@ struct Media { class ResourceManager { -public: +public: + enum KindOfInfo { + InfoFormat = 0, // mp3, jpg ... + InfoType = 1, // image or sound + }; + ResourceManager(ILogger &log) : m_log(log) , m_images(filter("image")) diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index e6ad62c..d5e31c6 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -34,10 +34,18 @@ include(cmake/CPM.cmake) # ========================================================================================================================= # MBedTLS # ========================================================================================================================= -CPMAddPackage("gh:Mbed-TLS/mbedtls#v3.6.2") +CPMAddPackage( + NAME mbedtls + GITHUB_REPOSITORY Mbed-TLS/mbedtls + VERSION 3.6.2 + OPTIONS + "USE_STATIC_MBEDTLS_LIBRARY ON" + "ENABLE_PROGRAMS OFF" + "ENABLE_TESTING OFF" +) find_package(MbedTLS REQUIRED) -include_directories(${MbedTLS_INCLUDE_DIR}) +include_directories(${mbedtls_INCLUDE_DIR}) # set(MBEDTLS_STATIC_LIBRARY ON) # ========================================================================================================================= @@ -336,7 +344,7 @@ if(UNIX) SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer libcurl_static - MbedTLS::mbedtls + mbedtls civetweb-cpp pthread OpenGL::GL diff --git a/story-editor/src/importers/pack_archive.cpp b/story-editor/src/importers/pack_archive.cpp index 2a22449..677e54c 100644 --- a/story-editor/src/importers/pack_archive.cpp +++ b/story-editor/src/importers/pack_archive.cpp @@ -136,7 +136,7 @@ void PackArchive::DecipherFiles(const std::string &directory, const std::string } } -std::vector PackArchive::FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements) +std::vector PackArchive::FilesInMemory(const std::string &type, const uint8_t *data, uint32_t nb_elements) { char res_file[13]; // 12 + \0 std::vector resList; @@ -150,111 +150,75 @@ std::vector PackArchive::FilesToJson(const std::string &type, const SysLib::ReplaceCharacter(res_file_string, "\\", "/"); resList.push_back(res_file_string); - - m_resources[type + std::to_string(i)] = res_file_string; } return resList; } -void PackArchive::ImportCommercialFormat(const std::string &packFileName, const std::string &outputDir) +void PackArchive::ConvertCommercialFormat(StoryProject &proj, const std::string &basePath) { - auto uuid = Uuid().String(); - std::string basePath = outputDir + "/" + uuid; - - Unzip(packFileName, basePath); - LoadNiFile(packFileName); - - std::string path = basePath + "/" + mPackName + "/rf"; - for (const auto & entry : std::filesystem::directory_iterator(path)) - { - if (entry.is_directory()) - { - std::cout << entry.path() << std::endl; - DecipherFiles(entry.path().generic_string(), ".bmp"); - } - } - - path = basePath + "/" + mPackName + "/sf"; - for (const auto & entry : std::filesystem::directory_iterator(path)) - { - if (entry.is_directory()) - { - std::cout << entry.path() << std::endl; - DecipherFiles(entry.path().generic_string(), ".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 -*/ - + ResourceManager res(m_log); // RI file is not ciphered uint8_t data[512]; uint32_t size = ni_get_ri_block(data); - // WriteDataOnDisk(mPackName + "/ri", data, size); - StoryProject proj(m_log); - ResourceManager res(m_log); + auto page = proj.GetPage(proj.MainUuid()); - std::shared_ptr page = proj.CreatePage(proj.MainUuid()); - - - proj.New(uuid, outputDir); - proj.SetName(j["title"].get()); - - nlohmann::json jsonImages; + // Images resources { - - std::vector lst = FilesToJson("ri", data, mNiFile.image_assets_count); + std::vector lst = FilesInMemory("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); + // Le path est de la forme "000/AE123245" où 000 est un répertoire + + auto rData = std::make_shared(); + + // origin + auto from = std::filesystem::path(basePath) / mPackName / "rf" / l; + from += ".bmp"; + + // destination + auto filename = SysLib::GetFileName(l) + ".bmp"; + auto to = proj.AssetsPath() / filename; + + rData->file = filename; + rData->type = ResourceManager::ExtentionInfo(rData->file, ResourceManager::InfoType); + rData->format = ResourceManager::ExtentionInfo(rData->file, ResourceManager::InfoFormat); + res.Add(rData); + + std::filesystem::copy(from, to, std::filesystem::copy_options::overwrite_existing); } } - j["images"] = jsonImages; size = ni_get_si_block(data); - // WriteDataOnDisk(mPackName + "/si", data, size); - - nlohmann::json jsonSounds; + // Sound files { - nlohmann::json obj; - std::vector lst = FilesToJson("si", data, mNiFile.sound_assets_count); + std::vector lst = FilesInMemory("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); + auto rData = std::make_shared(); + + // origin + auto from = std::filesystem::path(basePath) / mPackName / "sf" / l; + from += ".mp3"; + + // destination + auto filename = SysLib::GetFileName(l) + ".mp3"; + auto to = proj.AssetsPath() / filename; + + rData->file = filename; + rData->type = ResourceManager::ExtentionInfo(rData->file, ResourceManager::InfoType); + rData->format = ResourceManager::ExtentionInfo(rData->file, ResourceManager::InfoFormat); + res.Add(rData); + + std::filesystem::copy(from, to, std::filesystem::copy_options::overwrite_existing); } } - 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 @@ -264,87 +228,89 @@ $MyArray DV8 10 ; array of 10 bytes i += 4; } - // Transform into JSON - node_info_t node; - nlohmann::json jsonNodes; + node_info_t node_info; + + // key: node index, value: node uuidV4 + std::map nodeIds; + + // key: node index, value: list of transitions + std::map> nodeTransitions; + for (int i = 0; i < mNiFile.node_size; i++) { - nlohmann::json jNode; - ni_get_node_info(i, &node); + ni_get_node_info(i, &node_info); - 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; + auto node = proj.CreateNode(proj.MainUuid(), "media-node"); - nlohmann::json jumpArray; - - for (int jIndex = 0; jIndex < node.current->ok_transition_number_of_options; jIndex++) + if (node) { - jumpArray.push_back(transitions[node.current->ok_transition_action_node_index_in_li + jIndex]); - } + // On sauvegarde la relation entre l'index du noeud et son UUID + nodeIds[i] = node->GetId(); - jNode["jumps"] = jumpArray; + node->SetPosition(80 * i, 80 * i); + + nlohmann::json internalData; + auto img = SysLib::GetFileName(node_info.ri_file); + auto snd = SysLib::GetFileName(node_info.si_file); + internalData["image"] = img.size() > 0 ? img + ".bmp" : ""; + internalData["sound"] = snd.size() > 0 ? snd + ".mp3" : ""; - // 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 + node->SetInternalData(internalData); - // 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::vector jumpArray; - - 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)) + // Autre cas + if (node_info.current->ok_transition_number_of_options == 10) { - chip32 << ", "; + // 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) + + + jumpArray.push_back(transitions[node_info.current->ok_transition_action_node_index_in_li]); } + else + { + // Vraies transitions + for (int jIndex = 0; jIndex < node_info.current->ok_transition_number_of_options; jIndex++) + { + jumpArray.push_back(transitions[node_info.current->ok_transition_action_node_index_in_li + jIndex]); + } + } + + nodeTransitions[i] = jumpArray; + } + else + { + std::cout << "Node not created" << std::endl; + m_log.Log("Node not created"); } - 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"; + // Create links, parse again the nodes + for (int i = 0; i < mNiFile.node_size; i++) + { + for (auto &j : nodeTransitions[i]) + { + auto c = std::make_shared(); + + c->outNodeId = nodeIds[i]; + c->outPortIndex = 0; + c->inNodeId = nodeIds[j]; + c->inPortIndex = 0; + + page->AddLink(c); + } + } + + proj.Save(res); } + bool PackArchive::LoadNiFile(const std::string &filePath) { bool success = false; @@ -411,12 +377,11 @@ bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std: { if (std::filesystem::is_regular_file(entry.path())) { - // Si c'est un sous-répertoire, récursivement scanner le contenu auto rData = std::make_shared(); rData->file = entry.path().filename().generic_string(); - rData->type = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), 1); - rData->format = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), 0); + rData->type = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), ResourceManager::InfoType); + rData->format = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), ResourceManager::InfoFormat); res.Add(rData); } } diff --git a/story-editor/src/importers/pack_archive.h b/story-editor/src/importers/pack_archive.h index 45f57c9..db863a9 100644 --- a/story-editor/src/importers/pack_archive.h +++ b/story-editor/src/importers/pack_archive.h @@ -1,12 +1,17 @@ #ifndef PACK_ARCHIVE_H #define PACK_ARCHIVE_H +#include #include -#include "zip.h" #include + +#include "zip.h" #include "ni_parser.h" #include "json.hpp" +#include "uuid.h" #include "i_logger.h" +#include "i_story_db.h" +#include "story_project.h" class PackArchive { @@ -17,7 +22,59 @@ public: std::string OpenImage(const std::string &fileName); bool ImportStudioFormat(const std::string &fileName, const std::string &outputDir); - void ImportCommercialFormat(const std::string &packFileName, const std::string &outputDir); + + template + // requires std::ranges::range + void ImportCommercialFormat(const std::string &packFileName, const std::string &outputDir, Range&& range) + { + auto uuid = Uuid().String(); + std::string basePath = outputDir + "/" + uuid; + + Unzip(packFileName, basePath); + LoadNiFile(packFileName); + + StoryProject proj(m_log); + proj.New(uuid, outputDir); + + auto packUuidv4 = normalizeUUID(mPackName); + for (auto &info : range) + { + std::cout << info.uuid << std::endl; + if (info.uuid == packUuidv4) + { + m_log.Log("Found commercial story: " + info.title); + proj.SetName(info.title); + proj.SetDescription(info.description); + + // FIXME: download image and sound + proj.SetTitleImage(info.image_url); + proj.SetTitleSound(info.sound); + break; + } + } + + std::string path = basePath + "/" + mPackName + "/rf"; + for (const auto & entry : std::filesystem::directory_iterator(path)) + { + if (entry.is_directory()) + { + std::cout << entry.path() << std::endl; + DecipherFiles(entry.path().generic_string(), ".bmp"); + } + } + + path = basePath + "/" + mPackName + "/sf"; + for (const auto & entry : std::filesystem::directory_iterator(path)) + { + if (entry.is_directory()) + { + std::cout << entry.path() << std::endl; + DecipherFiles(entry.path().generic_string(), ".mp3"); + } + } + + ConvertCommercialFormat(proj, basePath); + } std::string CurrentImage(); std::string CurrentSound(); @@ -46,16 +103,36 @@ private: node_info_t mCurrentNode; ni_file_t mNiFile; - // key: resource tag - // value: resource file name - std::map m_resources; + void ConvertCommercialFormat(StoryProject &proj, const std::string &basePath); bool ParseNIFile(const std::string &root); + // Convertit un UUID de type "3ADE540306254FFFA22B9025AC3678D9" + // en standard : 3ade5403-0625-4fff-a22b-9025ac3678d9 + std::string normalizeUUID(const std::string& uuid) { + // Check if the input length is correct for a UUID without dashes + if (uuid.length() != 32) { + throw std::invalid_argument("Invalid UUID length"); + } + + // Convert to lowercase + std::string lowerUuid = uuid; + std::transform(lowerUuid.begin(), lowerUuid.end(), lowerUuid.begin(), ::tolower); + + // Insert dashes at appropriate positions + std::ostringstream oss; + oss << lowerUuid.substr(0, 8) << '-' + << lowerUuid.substr(8, 4) << '-' + << lowerUuid.substr(12, 4) << '-' + << lowerUuid.substr(16, 4) << '-' + << lowerUuid.substr(20); + + return oss.str(); + } void DecipherFileOnDisk(const std::string &fileName); void DecipherFiles(const std::string &directory, const std::string &suffix); - std::vector FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements); + std::vector FilesInMemory(const std::string &type, const uint8_t *data, uint32_t nb_elements); }; #endif // PACK_ARCHIVE_H diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 2dfc625..ca9fa17 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -803,11 +803,11 @@ void MainWindow::ImportProject(const std::string &fileName, int format) } else { - archive.ImportCommercialFormat(fileName, m_libraryManager.LibraryPath()); + archive.ImportCommercialFormat(fileName, m_libraryManager.LibraryPath(), m_libraryManager.CommercialDbView()); } } - + void MainWindow::RefreshProjectInformation() { std::string fullText = "Story Editor " + LibraryManager::GetVersion();