Import commercial nearly complete
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

This commit is contained in:
anthony@rabine.fr 2025-01-14 23:40:18 +01:00
parent 59921fe9fd
commit 879f5fbdbc
9 changed files with 235 additions and 157 deletions

View file

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector>
#include <ranges>
class IStoryDb class IStoryDb
{ {
@ -19,6 +21,8 @@ public:
virtual ~IStoryDb() {} virtual ~IStoryDb() {}
using ViewType = std::ranges::ref_view<std::vector<IStoryDb::Info>>;
virtual bool FindStory(const std::string &uuid, Info &info) = 0; virtual bool FindStory(const std::string &uuid, Info &info) = 0;
virtual void AddStory(IStoryDb::Info &info, int origin) = 0; virtual void AddStory(IStoryDb::Info &info, int origin) = 0;

View file

@ -169,6 +169,20 @@ void StoryProject::DeleteNode(const std::string_view &page_uuid, const std::stri
} }
} }
std::shared_ptr<StoryPage> 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<Connection> c) void StoryProject::DeleteLink(const std::string_view &page_uuid, std::shared_ptr<Connection> c)
{ {

View file

@ -114,6 +114,7 @@ public:
void SetTitleImage(const std::string &titleImage); void SetTitleImage(const std::string &titleImage);
void SetTitleSound(const std::string &titleSound); void SetTitleSound(const std::string &titleSound);
void SetDescription(const std::string &description) { m_description = description; }
std::string GetTitleImage() const { return m_titleImage; } std::string GetTitleImage() const { return m_titleImage; }
std::string GetTitleSound() const { return m_titleSound; } std::string GetTitleSound() const { return m_titleSound; }
@ -130,6 +131,8 @@ public:
// Node interaction // Node interaction
std::shared_ptr<StoryPage> CreatePage(const std::string &uuid); std::shared_ptr<StoryPage> CreatePage(const std::string &uuid);
std::shared_ptr<StoryPage> GetPage(const std::string &uuid);
std::shared_ptr<BaseNode> CreateNode(const std::string_view &page, const std::string &type); std::shared_ptr<BaseNode> CreateNode(const std::string_view &page, const std::string &type);
void AddConnection(const std::string_view &page, std::shared_ptr<Connection> c); void AddConnection(const std::string_view &page, std::shared_ptr<Connection> c);
void DeleteNode(const std::string_view &page, const std::string &id); void DeleteNode(const std::string_view &page, const std::string &id);

View file

@ -245,6 +245,8 @@ void LibraryManager::ParseCommercialStore(const std::string &jsonFileName)
const auto& response = j["response"]; const auto& response = j["response"];
m_storyDb.ClearCommercial(); m_storyDb.ClearCommercial();
std::cout << "Found " << response.size() << " commercial stories" << std::endl;
for (auto it = response.begin(); it != response.end(); ++it) for (auto it = response.begin(); it != response.end(); ++it)
{ {
const auto& pack = it.value(); const auto& pack = it.value();

View file

@ -21,6 +21,11 @@ struct Media {
class ResourceManager class ResourceManager
{ {
public: public:
enum KindOfInfo {
InfoFormat = 0, // mp3, jpg ...
InfoType = 1, // image or sound
};
ResourceManager(ILogger &log) ResourceManager(ILogger &log)
: m_log(log) : m_log(log)
, m_images(filter("image")) , m_images(filter("image"))

View file

@ -34,10 +34,18 @@ include(cmake/CPM.cmake)
# ========================================================================================================================= # =========================================================================================================================
# MBedTLS # 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) find_package(MbedTLS REQUIRED)
include_directories(${MbedTLS_INCLUDE_DIR}) include_directories(${mbedtls_INCLUDE_DIR})
# set(MBEDTLS_STATIC_LIBRARY ON) # set(MBEDTLS_STATIC_LIBRARY ON)
# ========================================================================================================================= # =========================================================================================================================
@ -336,7 +344,7 @@ if(UNIX)
SDL3_image::SDL3_image SDL3_image::SDL3_image
SDL3_mixer::SDL3_mixer SDL3_mixer::SDL3_mixer
libcurl_static libcurl_static
MbedTLS::mbedtls mbedtls
civetweb-cpp civetweb-cpp
pthread pthread
OpenGL::GL OpenGL::GL

View file

@ -136,7 +136,7 @@ void PackArchive::DecipherFiles(const std::string &directory, const std::string
} }
} }
std::vector<std::string> PackArchive::FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements) std::vector<std::string> PackArchive::FilesInMemory(const std::string &type, const uint8_t *data, uint32_t nb_elements)
{ {
char res_file[13]; // 12 + \0 char res_file[13]; // 12 + \0
std::vector<std::string> resList; std::vector<std::string> resList;
@ -150,111 +150,75 @@ std::vector<std::string> PackArchive::FilesToJson(const std::string &type, const
SysLib::ReplaceCharacter(res_file_string, "\\", "/"); SysLib::ReplaceCharacter(res_file_string, "\\", "/");
resList.push_back(res_file_string); resList.push_back(res_file_string);
m_resources[type + std::to_string(i)] = res_file_string;
} }
return resList; 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(); ResourceManager res(m_log);
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
*/
// RI file is not ciphered // RI file is not ciphered
uint8_t data[512]; uint8_t data[512];
uint32_t size = ni_get_ri_block(data); uint32_t size = ni_get_ri_block(data);
// WriteDataOnDisk(mPackName + "/ri", data, size);
StoryProject proj(m_log); auto page = proj.GetPage(proj.MainUuid());
ResourceManager res(m_log);
std::shared_ptr<StoryPage> page = proj.CreatePage(proj.MainUuid()); // Images resources
proj.New(uuid, outputDir);
proj.SetName(j["title"].get<std::string>());
nlohmann::json jsonImages;
{ {
std::vector<std::string> lst = FilesInMemory("ri", data, mNiFile.image_assets_count);
std::vector<std::string> lst = FilesToJson("ri", data, mNiFile.image_assets_count);
for (auto &l : lst) for (auto &l : lst)
{ {
nlohmann::json obj; // Le path est de la forme "000/AE123245" où 000 est un répertoire
obj["file"] = l;
obj["description"] = ""; auto rData = std::make_shared<Resource>();
obj["format"] = "bmp";
jsonImages.push_back(obj); // 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); size = ni_get_si_block(data);
// WriteDataOnDisk(mPackName + "/si", data, size);
// Sound files
nlohmann::json jsonSounds;
{ {
nlohmann::json obj; std::vector<std::string> lst = FilesInMemory("si", data, mNiFile.sound_assets_count);
std::vector<std::string> lst = FilesToJson("si", data, mNiFile.sound_assets_count);
for (auto &l : lst) for (auto &l : lst)
{ {
nlohmann::json obj; auto rData = std::make_shared<Resource>();
obj["file"] = l;
obj["description"] = ""; // origin
obj["format"] = "mp3"; auto from = std::filesystem::path(basePath) / mPackName / "sf" / l;
jsonSounds.push_back(obj); 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); size = ni_get_li_block(data);
// WriteDataOnDisk(mPackName + "/li", data, size);
std::vector<uint32_t> transitions; std::vector<uint32_t> transitions;
// each entry of the transitions array is a 32-bit integer // each entry of the transitions array is a 32-bit integer
@ -264,87 +228,89 @@ $MyArray DV8 10 ; array of 10 bytes
i += 4; i += 4;
} }
// Transform into JSON node_info_t node_info;
node_info_t node;
nlohmann::json jsonNodes; // key: node index, value: node uuidV4
std::map<int, std::string> nodeIds;
// key: node index, value: list of transitions
std::map<int, std::vector<uint32_t>> nodeTransitions;
for (int i = 0; i < mNiFile.node_size; i++) for (int i = 0; i < mNiFile.node_size; i++)
{ {
nlohmann::json jNode; ni_get_node_info(i, &node_info);
ni_get_node_info(i, &node);
jNode["id"] = i; auto node = proj.CreateNode(proj.MainUuid(), "media-node");
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; if (node)
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]); // 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);
// Autre cas nlohmann::json internalData;
if (node.current->ok_transition_number_of_options >= 5) auto img = SysLib::GetFileName(node_info.ri_file);
{ auto snd = SysLib::GetFileName(node_info.si_file);
// For now, consider that this is a bad format internalData["image"] = img.size() > 0 ? img + ".bmp" : "";
// In the future, consider using ok_transition_selected_option_index when ok_transition_number_of_options == 10 internalData["sound"] = snd.size() > 0 ? snd + ".mp3" : "";
// 00 00 00 00 ==> ok transition à zéro node->SetInternalData(internalData);
// 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<uint32_t> jumpArray;
std::cout << "!!!!!!!!!!!!!!!!!!" << std::endl; // Autre cas
} if (node_info.current->ok_transition_number_of_options == 10)
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 << ", "; // 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<Connection>();
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 PackArchive::LoadNiFile(const std::string &filePath)
{ {
bool success = false; bool success = false;
@ -411,12 +377,11 @@ bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std:
{ {
if (std::filesystem::is_regular_file(entry.path())) 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<Resource>(); auto rData = std::make_shared<Resource>();
rData->file = entry.path().filename().generic_string(); rData->file = entry.path().filename().generic_string();
rData->type = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), 1); rData->type = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), ResourceManager::InfoType);
rData->format = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), 0); rData->format = ResourceManager::ExtentionInfo(entry.path().extension().generic_string(), ResourceManager::InfoFormat);
res.Add(rData); res.Add(rData);
} }
} }

View file

@ -1,12 +1,17 @@
#ifndef PACK_ARCHIVE_H #ifndef PACK_ARCHIVE_H
#define PACK_ARCHIVE_H #define PACK_ARCHIVE_H
#include <ranges>
#include <string> #include <string>
#include "zip.h"
#include <vector> #include <vector>
#include "zip.h"
#include "ni_parser.h" #include "ni_parser.h"
#include "json.hpp" #include "json.hpp"
#include "uuid.h"
#include "i_logger.h" #include "i_logger.h"
#include "i_story_db.h"
#include "story_project.h"
class PackArchive class PackArchive
{ {
@ -17,7 +22,59 @@ public:
std::string OpenImage(const std::string &fileName); std::string OpenImage(const std::string &fileName);
bool ImportStudioFormat(const std::string &fileName, const std::string &outputDir); bool ImportStudioFormat(const std::string &fileName, const std::string &outputDir);
void ImportCommercialFormat(const std::string &packFileName, const std::string &outputDir);
template<typename Range>
// requires std::ranges::range<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 CurrentImage();
std::string CurrentSound(); std::string CurrentSound();
@ -46,16 +103,36 @@ private:
node_info_t mCurrentNode; node_info_t mCurrentNode;
ni_file_t mNiFile; ni_file_t mNiFile;
// key: resource tag void ConvertCommercialFormat(StoryProject &proj, const std::string &basePath);
// value: resource file name
std::map<std::string, std::string> m_resources;
bool ParseNIFile(const std::string &root); 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 DecipherFileOnDisk(const std::string &fileName);
void DecipherFiles(const std::string &directory, const std::string &suffix); void DecipherFiles(const std::string &directory, const std::string &suffix);
std::vector<std::string> FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements); std::vector<std::string> FilesInMemory(const std::string &type, const uint8_t *data, uint32_t nb_elements);
}; };
#endif // PACK_ARCHIVE_H #endif // PACK_ARCHIVE_H

View file

@ -803,7 +803,7 @@ void MainWindow::ImportProject(const std::string &fileName, int format)
} }
else else
{ {
archive.ImportCommercialFormat(fileName, m_libraryManager.LibraryPath()); archive.ImportCommercialFormat(fileName, m_libraryManager.LibraryPath(), m_libraryManager.CommercialDbView());
} }
} }