diff --git a/shared/i_logger.h b/shared/i_logger.h new file mode 100644 index 0000000..d3d0c1e --- /dev/null +++ b/shared/i_logger.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class ILogger +{ + +public: + virtual void Log(const std::string &txt, bool critical = false) = 0; +}; diff --git a/shared/library_manager.cpp b/shared/library_manager.cpp index 7d09fef..db0a6c1 100644 --- a/shared/library_manager.cpp +++ b/shared/library_manager.cpp @@ -8,7 +8,19 @@ #include "uuid.h" -LibraryManager::LibraryManager() {} +LibraryManager::LibraryManager(ILogger &log) + : m_log(log) +{ + +} + +LibraryManager::~LibraryManager() +{ + if (m_copyWorker.joinable()) + { + m_copyWorker.join(); + } +} void LibraryManager::Initialize(const std::string &library_path) { @@ -43,7 +55,7 @@ void LibraryManager::Scan() if (std::filesystem::exists(p)) { // okay, open it - auto proj = std::make_shared(); + auto proj = std::make_shared(m_log); try { std::ifstream f(p); nlohmann::json j = nlohmann::json::parse(f); @@ -55,7 +67,7 @@ void LibraryManager::Scan() m_projectsList.push_back(proj); } } - catch(std::exception &e) + catch(nlohmann::json::exception &e) { std::cout << e.what() << std::endl; } @@ -68,7 +80,7 @@ void LibraryManager::Scan() std::shared_ptr LibraryManager::NewProject() { - auto story = std::make_shared(); + auto story = std::make_shared(m_log); std::string uuid = Uuid().String(); story->New(uuid, m_library_path); @@ -92,10 +104,15 @@ std::shared_ptr LibraryManager::GetStory(const std::string &uuid) return current; } -void LibraryManager::Save() +std::string LibraryManager::IndexFileName() const { auto p = std::filesystem::path(m_library_path) / "index.ost"; - Tlv tlv(p.string()); + return p.string(); +} + +void LibraryManager::Save() +{ + Tlv tlv; tlv.add_object(1); tlv.add_string(GetVersion()); @@ -115,6 +132,8 @@ void LibraryManager::Save() } } + tlv.Save(IndexFileName()); + /* @@ -130,16 +149,37 @@ void LibraryManager::Save() void LibraryManager::CopyToDevice(const std::string &outputDir) { - std::thread myThread([&]() { - myThread.detach(); + try + { + // Generate TLV file (index of all stories) + Save(); + // Copy TLV to the directory root + std::filesystem::copy(IndexFileName(), outputDir, std::filesystem::copy_options::overwrite_existing); - std::cout << "Starting to copy elements" << std::endl; - - for (auto p : *this) + if (m_copyWorker.joinable()) { - + m_copyWorker.join(); } - }); + + m_copyWorker = std::thread([&, outputDir]() { + // myThread.detach(); + + std::cout << "Starting to copy elements" << std::endl; + + for (auto p : *this) + { + if (p->IsSelected()) + { + p->CopyToDevice(outputDir); + } + } + }); + } catch (const std::filesystem::filesystem_error& e) { + std::cerr << "Filesystem error: " << e.what() << std::endl; + } catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + } } bool LibraryManager::IsInitialized() const diff --git a/shared/library_manager.h b/shared/library_manager.h index 9786607..b354f68 100644 --- a/shared/library_manager.h +++ b/shared/library_manager.h @@ -4,12 +4,15 @@ #include #include #include -#include "story_project.h" +#include +#include "story_project.h" +#include "i_logger.h" class LibraryManager { public: - LibraryManager(); + LibraryManager(ILogger &log); + ~LibraryManager(); void Initialize(const std::string &library_path); bool IsInitialized() const; @@ -34,13 +37,17 @@ public: std::shared_ptr GetStory(const std::string &uuid); + std::string IndexFileName() const; + void SetStoreUrl(const std::string &store_url) { m_storeUrl = store_url; } std::string GetStoreUrl() const { return m_storeUrl; } private: + ILogger &m_log; std::string m_library_path; std::vector> m_projectsList; std::string m_storeUrl; + std::thread m_copyWorker; }; #endif // LIBRARYMANAGER_H diff --git a/shared/resource_manager.cpp b/shared/resource_manager.cpp index ebacf0c..1868653 100644 --- a/shared/resource_manager.cpp +++ b/shared/resource_manager.cpp @@ -1,6 +1,10 @@ -#include "resource_manager.h" #include +#include + +#include "resource_manager.h" +#include "media_converter.h" +#include "sys_lib.h" struct Media { std::string type; std::string format; @@ -32,3 +36,59 @@ std::string ResourceManager::ExtentionInfo(std::string extension, int info_type) } } +void ResourceManager::ConvertResources(const std::filesystem::path &assetsPath, const std::string &destAssetsPath) +{ + auto [b, e] = Items(); + for (auto it = b; it != e; ++it) + { + std::filesystem::path inputfile = std::filesystem::path(assetsPath / (*it)->file); + std::filesystem::path outputfile = std::filesystem::path(assetsPath / SysLib::RemoveFileExtension((*it)->file)); + + int retCode = 0; + if (!std::filesystem::exists(outputfile)) + { + if ((*it)->format == "PNG") + { + outputfile += ".qoi"; // FIXME: prendre la config en cours désirée + retCode = MediaConverter::ImageToQoi(inputfile.generic_string(), outputfile.generic_string()); + } + else if ((*it)->format == "MP3") + { + outputfile += ".wav"; // FIXME: prendre la config en cours désirée + retCode = MediaConverter::Mp3ToWav(inputfile.generic_string(), outputfile.generic_string()); + } + else if ((*it)->format == "OGG") + { + outputfile += ".wav"; // FIXME: prendre la config en cours désirée + retCode = MediaConverter::OggToWav(inputfile.generic_string(), outputfile.generic_string()); + } + else + { + // Log("Skipped: " + inputfile + ", unknown format" + outputfile, true); + } + } + + if (retCode < 0) + { + //m_log.Log("Failed to convert media file " + inputfile + ", error code: " + std::to_string(retCode) + " to: " + outputfile, true); + } + else if (retCode == 0) + { + // Log("Convertered file: " + inputfile); + + // Conversion success, now copy to output directory if necessary (ie: to an external device) + if ((destAssetsPath != assetsPath.generic_string()) && (std::filesystem::exists(destAssetsPath))) + { + // destination filename + auto destFile = destAssetsPath / outputfile.filename(); + + if (!std::filesystem::exists(destFile)) + { + std::filesystem::copy(outputfile, destAssetsPath, std::filesystem::copy_options::overwrite_existing); + } + } + } + } +} + + diff --git a/shared/resource_manager.h b/shared/resource_manager.h index b52ccb7..276dc8e 100644 --- a/shared/resource_manager.h +++ b/shared/resource_manager.h @@ -9,6 +9,7 @@ #include #include "resource.h" +#include "i_logger.h" class ResourceManager { @@ -24,6 +25,8 @@ public: static std::string ExtentionInfo(std::string extension, int info_type); + void ConvertResources(const std::filesystem::path &assetsPath, const std::string &destAssetsPath); + ~ResourceManager() { } diff --git a/shared/story_project.cpp b/shared/story_project.cpp index 21f3602..eabbcfc 100644 --- a/shared/story_project.cpp +++ b/shared/story_project.cpp @@ -1,14 +1,17 @@ -#include "story_project.h" + #include #include #include #include +#include "story_project.h" #include "json.hpp" #include "media_node.h" +#include "sys_lib.h" -StoryProject::StoryProject() +StoryProject::StoryProject(ILogger &log) + : m_log(log) { registerNode("media-node"); } @@ -31,9 +34,26 @@ void StoryProject::SetPaths(const std::string &uuid, const std::string &library_ void StoryProject::CopyToDevice(const std::string &outputDir) { ResourceManager manager; - - Load(manager); + Load(manager); + + // Output dir is the root. Build an assets directory to the device location + std::filesystem::path destRootDir = std::filesystem::path(outputDir) / m_uuid; + std::filesystem::path destAssetsDir = destRootDir / "assets"; + std::filesystem::create_directories(destAssetsDir); + + // Generate and copy binary + std::string code; + GenerateScript(code); + + Chip32::Assembler::Error err; + if (GenerateBinary(code, err)) + { + std::filesystem::copy(BinaryFileName(), destRootDir, std::filesystem::copy_options::overwrite_existing); + + // Convert resources (if necessary) and copy them to destination assets + manager.ConvertResources(AssetsPath(), destAssetsDir); + } } void StoryProject::New(const std::string &uuid, const std::string &library_path) @@ -51,10 +71,15 @@ void StoryProject::New(const std::string &uuid, const std::string &library_path) m_initialized = true; } - -void StoryProject::SaveBinary(const std::vector &m_program) +std::filesystem::path StoryProject::BinaryFileName() const { - std::ofstream o(m_working_dir / "story.c32", std::ios::out | std::ios::binary); + return m_working_dir / "story.c32"; +} + + +void StoryProject::SaveBinary() +{ + std::ofstream o(BinaryFileName() , std::ios::out | std::ios::binary); o.write(reinterpret_cast(m_program.data()), m_program.size()); o.close(); } @@ -192,13 +217,47 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model) } success = true; } - catch(std::exception &e) + catch(nlohmann::json::exception &e) { std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl; } return success; } +bool StoryProject::CopyProgramTo(uint8_t *memory, uint32_t size) +{ + bool success = false; + // Update ROM memory + if (m_program.size() < size) + { + std::copy(m_program.begin(), m_program.end(), memory); + success = true; + } + return success; +} + +bool StoryProject::GetAssemblyLine(uint32_t pointer_counter, uint32_t &line) +{ + bool success = false; + // On recherche quelle est la ligne qui possède une instruction à cette adresse + std::vector::const_iterator ptr = m_assembler.Begin(); + for (; ptr != m_assembler.End(); ++ptr) + { + if ((ptr->addr == pointer_counter) && ptr->isRomCode()) + { + break; + } + } + + if (ptr != m_assembler.End()) + { + line = ptr->line; + success = true; + } + + return success; +} + int StoryProject::OutputsCount(const std::string &nodeId) { int count = 0; @@ -258,7 +317,7 @@ std::string StoryProject::FindFirstNode() const } -bool StoryProject::Build(std::string &codeStr) +bool StoryProject::GenerateScript(std::string &codeStr) { std::stringstream code; std::stringstream chip32; @@ -267,7 +326,7 @@ bool StoryProject::Build(std::string &codeStr) if (firstNode == "") { - std::cout << "First node not found, there must be only one node with a free input." << std::endl; + m_log.Log("First node not found, there must be only one node with a free input."); return false; } @@ -285,9 +344,51 @@ bool StoryProject::Build(std::string &codeStr) } codeStr = code.str(); + + // Add our utility functions + std::string buffer; + + std::ifstream f("scripts/media.asm"); + f.seekg(0, std::ios::end); + buffer.resize(f.tellg()); + f.seekg(0); + f.read(buffer.data(), buffer.size()); + codeStr += buffer; + return true; } +bool StoryProject::GenerateBinary(const std::string &code, Chip32::Assembler::Error &err) +{ + Chip32::Result result; + bool success = false; + + if (m_assembler.Parse(code) == true) + { + if (m_assembler.BuildBinary(m_program, result) == true) + { + result.Print(); + + m_log.Log("Binary successfully generated."); + SaveBinary(); + success = true; + } + else + { + err = m_assembler.GetLastError(); + + } + } + else + { + err = m_assembler.GetLastError(); + m_log.Log(err.ToString(), true); + } + return success; +} + + + bool StoryProject::Load(ResourceManager &manager) { try { @@ -320,69 +421,8 @@ bool StoryProject::Load(ResourceManager &manager) } } } - - /* - - if (j.contains("nodes")) - { - for (auto& element : j["nodes"]) - { - StoryNode n; - - n.auto_jump = element["auto_jump"]; - - for (auto& jump : element["jumps"]) - { - n.jumps.push_back(jump.get()); - } - - n.id = element["id"]; - n.image = element["image"]; - n.sound = element["sound"]; - - m_nodes.push_back(n); - } - } - - m_images.clear(); - if (j.contains("images")) - { - for (auto& element : j["images"]) - { - Resource r; - - r.file = element["file"]; - r.description = element["description"]; - r.format = element["format"]; - - m_images.push_back(r); - } - } - - m_sounds.clear(); - if (j.contains("sounds")) - { - for (auto& element : j["sounds"]) - { - Resource r; - - r.file = element["file"]; - r.description = element["description"]; - r.format = element["format"]; - - m_sounds.push_back(r); - } - } - - m_type = j["type"]; - m_code = j["code"]; - m_name = j["name"]; - - success = true; - -*/ } - catch(std::exception &e) + catch(nlohmann::json::exception &e) { std::cout << e.what() << std::endl; } @@ -463,74 +503,7 @@ void StoryProject::Clear() m_links.clear(); } -void StoryProject::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 StoryProject::ToUpper(const std::string &input) -{ - std::string str = input; - std::transform(str.begin(), str.end(), str.begin(), ::toupper); - return str; -} - -std::string StoryProject::GetFileName(const std::string &path) -{ - auto found = path.find_last_of("/\\"); - return path.substr(found+1); -} - - -void StoryProject::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); -} - -std::string StoryProject::RemoveFileExtension(const std::string &FileName) -{ - std::string f = GetFileName(FileName); - std::string ext = GetFileExtension(f); - EraseString(f, "." + ext); - return f; -} - -std::string StoryProject::FileToConstant(const std::string &FileName, const std::string &extension) -{ - std::string f = RemoveFileExtension(FileName); - return "$" + f + " DC8 \"" + f + extension + "\", 8\r\n"; -} - -std::string StoryProject::Normalize(const std::string &input) -{ - std::string valid_file = input; - - std::replace(valid_file.begin(), valid_file.end(), '\\', '_'); - std::replace(valid_file.begin(), valid_file.end(), '/', '_'); - std::replace(valid_file.begin(), valid_file.end(), ':', '_'); - std::replace(valid_file.begin(), valid_file.end(), '?', '_'); - std::replace(valid_file.begin(), valid_file.end(), '\"', '_'); - std::replace(valid_file.begin(), valid_file.end(), '<', '_'); - std::replace(valid_file.begin(), valid_file.end(), '>', '_'); - std::replace(valid_file.begin(), valid_file.end(), '|', '_'); - std::replace(valid_file.begin(), valid_file.end(), ' ', '_'); - - return valid_file; -} void StoryProject::SetTitleImage(const std::string &titleImage) { @@ -542,12 +515,7 @@ void StoryProject::SetTitleSound(const std::string &titleSound) m_titleSound = titleSound; } -std::string StoryProject::GetFileExtension(const std::string &fileName) -{ - if(fileName.find_last_of(".") != std::string::npos) - return fileName.substr(fileName.find_last_of(".")+1); - return ""; -} + void StoryProject::SetImageFormat(ImageFormat format) { @@ -580,5 +548,11 @@ std::string StoryProject::BuildFullAssetsPath(const std::string &fileName) const return (AssetsPath() / fileName).generic_string(); } +std::string StoryProject::FileToConstant(const std::string &FileName, const std::string &extension) +{ + std::string f = SysLib::RemoveFileExtension(FileName); + return "$" + f + " DC8 \"" + f + extension + "\", 8\r\n"; +} + diff --git a/shared/story_project.h b/shared/story_project.h index ae051a0..575df39 100644 --- a/shared/story_project.h +++ b/shared/story_project.h @@ -11,6 +11,7 @@ #include "connection.h" #include "base_node.h" #include "i_story_project.h" +#include "chip32_assembler.h" // FIXME : Structure très Lunii style, à utiliser pour la conversion peut-être ... struct StoryNode @@ -51,7 +52,7 @@ public: enum ImageFormat { IMG_FORMAT_BMP_4BITS, IMG_FORMAT_QOIF, IMG_FORMAT_COUNT }; enum SoundFormat { SND_FORMAT_WAV, SND_FORMAT_QOAF, SND_FORMAT_COUNT }; - StoryProject(); + StoryProject(ILogger &log); ~StoryProject(); bool *Selected() { @@ -66,20 +67,26 @@ public: StoryNode *m_tree; */ void New(const std::string &uuid, const std::string &library_path); - bool Build(std::string &codeStr); + std::filesystem::path BinaryFileName() const; + bool GenerateScript(std::string &codeStr); + bool GenerateBinary(const std::string &code, Chip32::Assembler::Error &err); bool Load(ResourceManager &manager); void Save(ResourceManager &manager); - void SaveBinary(const std::vector &m_program); + void SaveBinary(); void SetPaths(const std::string &uuid, const std::string &library_path); void CopyToDevice(const std::string &outputDir); void ModelToJson(nlohmann::json &model); bool ModelFromJson(const nlohmann::json &model); + bool CopyProgramTo(uint8_t *memory, uint32_t size); + +// returns >= 0 on success + bool GetAssemblyLine(uint32_t pointer_counter, uint32_t &line); + void CreateTree(); void Clear(); - std::pair>::iterator, std::list>::iterator> Nodes() { return std::make_pair(m_nodes.begin(), m_nodes.end()); } @@ -102,18 +109,13 @@ public: std::string GetName() const { return m_name; } std::string GetUuid() const { return m_uuid; } std::string GetDescription() const { return m_description; } - int GetVersion() const { return m_version; } + uint32_t GetVersion() const { return m_version; } std::string BuildFullAssetsPath(const std::string &fileName) const; - std::filesystem::path AssetsPath() const { return m_assetsPath; } - - static std::string GetFileExtension(const std::string &FileName); - static std::string GetFileName(const std::string &path); - static std::string RemoveFileExtension(const std::string &FileName); - static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace); static std::string FileToConstant(const std::string &FileName, const std::string &extension); - static std::string Normalize(const std::string &input); + + std::filesystem::path AssetsPath() const { return m_assetsPath; } void SetTitleImage(const std::string &titleImage); void SetTitleSound(const std::string &titleSound); @@ -124,9 +126,6 @@ public: // Initialize with an existing project const bool IsInitialized() const { return m_initialized; } - static void EraseString(std::string &theString, const std::string &toErase); - static std::string ToUpper(const std::string &input); - bool ParseStoryInformation(nlohmann::json &j); // From IStoryProject @@ -140,17 +139,22 @@ public: void DeleteLink(std::shared_ptr c); private: + ILogger &m_log; + // Project properties and location std::string m_name; /// human readable name std::string m_uuid; std::string m_titleImage; std::string m_titleSound; std::string m_description; - int m_version; + uint32_t m_version; bool m_selected{false}; std::filesystem::path m_assetsPath; + Chip32::Assembler m_assembler; + std::vector m_program; + // Model in memory std::list> m_links; std::list> m_nodes; diff --git a/shared/sys_lib.cpp b/shared/sys_lib.cpp new file mode 100644 index 0000000..131b8b4 --- /dev/null +++ b/shared/sys_lib.cpp @@ -0,0 +1,99 @@ + +#include "sys_lib.h" +#include +#include + +void SysLib::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 SysLib::ToUpper(const std::string &input) +{ + std::string str = input; + std::transform(str.begin(), str.end(), str.begin(), ::toupper); + return str; +} + + + +std::string SysLib::GetFileExtension(const std::string &fileName) +{ + auto idx = fileName.find_last_of("."); + if(idx != std::string::npos) + { + return fileName.substr(idx + 1); + } + return ""; +} + +std::string SysLib::GetFileName(const std::string &path) +{ + if (path.size() > 0) + { + auto found = path.find_last_of("/\\"); + + if (found != std::string::npos) + { + return path.substr(found+1); + } + else + { + return ""; + } + } + else + { + return ""; + } +} + + +void SysLib::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); +} + +std::string SysLib::RemoveFileExtension(const std::string &filename) +{ + // Trouver la dernière occurrence du point + std::size_t dotPos = filename.rfind('.'); + if (dotPos == std::string::npos) { + // Pas d'extension trouvée, retourner le nom tel quel + return filename; + } + // Retourner la sous-chaîne avant le point + return filename.substr(0, dotPos); +} + + +std::string SysLib::Normalize(const std::string &input) +{ + std::string valid_file = input; + + std::replace(valid_file.begin(), valid_file.end(), '\\', '_'); + std::replace(valid_file.begin(), valid_file.end(), '/', '_'); + std::replace(valid_file.begin(), valid_file.end(), ':', '_'); + std::replace(valid_file.begin(), valid_file.end(), '?', '_'); + std::replace(valid_file.begin(), valid_file.end(), '\"', '_'); + std::replace(valid_file.begin(), valid_file.end(), '<', '_'); + std::replace(valid_file.begin(), valid_file.end(), '>', '_'); + std::replace(valid_file.begin(), valid_file.end(), '|', '_'); + std::replace(valid_file.begin(), valid_file.end(), ' ', '_'); + + return valid_file; +} diff --git a/shared/sys_lib.h b/shared/sys_lib.h new file mode 100644 index 0000000..7e0039f --- /dev/null +++ b/shared/sys_lib.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class SysLib +{ +public: + + + static std::string GetFileExtension(const std::string &FileName); + static std::string GetFileName(const std::string &path); + static std::string RemoveFileExtension(const std::string &FileName); + static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace); + static std::string Normalize(const std::string &input); + static void EraseString(std::string &theString, const std::string &toErase); + static std::string ToUpper(const std::string &input); +}; diff --git a/shared/tlv.h b/shared/tlv.h index 14b4659..7b6b680 100644 --- a/shared/tlv.h +++ b/shared/tlv.h @@ -15,34 +15,41 @@ class Tlv { public: - explicit Tlv(const std::string &filename) + explicit Tlv() { - m_file = std::ofstream(filename, std::ios::out | std::ios::binary); } ~Tlv() { - m_file.close(); + + } + + void Save(const std::string &filename) + { + auto f = std::ofstream(filename, std::ios::out | std::ios::binary); + f << m_mem; + std::flush(f); + f.close(); } void add_array(uint16_t size) { - m_file.write(reinterpret_cast(&m_objectType), sizeof(m_objectType)); - m_file.write(reinterpret_cast(&size), sizeof(size)); + m_mem.append(reinterpret_cast(&m_arrayType), sizeof(m_arrayType)); + m_mem.append(reinterpret_cast(&size), sizeof(size)); } void add_string(const char *s, uint16_t size) { - m_file.write(reinterpret_cast(&m_stringType), sizeof(m_stringType)); - - m_file.write(s, size); + m_mem.append(reinterpret_cast(&m_stringType), sizeof(m_stringType)); + m_mem.append(reinterpret_cast(&size), sizeof(size)); + m_mem.append(s, size); } void add_integer(uint32_t value) { static const uint16_t size = 4; - m_file.write(reinterpret_cast(&m_integerType), sizeof(m_integerType)); - m_file.write(reinterpret_cast(&size), sizeof(size)); - m_file.write(reinterpret_cast(&value), sizeof(value)); + m_mem.append(reinterpret_cast(&m_integerType), sizeof(m_integerType)); + m_mem.append(reinterpret_cast(&size), sizeof(size)); + m_mem.append(reinterpret_cast(&value), size); } void add_string(const std::string &s) @@ -52,12 +59,12 @@ public: void add_object(uint16_t entries) { - m_file.write(reinterpret_cast(&m_arrayType), sizeof(m_arrayType)); - m_file.write(reinterpret_cast(&entries), sizeof(entries)); + m_mem.append(reinterpret_cast(&m_objectType), sizeof(m_objectType)); + m_mem.append(reinterpret_cast(&entries), sizeof(entries)); } private: - std::ofstream m_file; + std::string m_mem; uint8_t m_arrayType = TLV_ARRAY_TYPE; uint8_t m_objectType = TLV_OBJECT_TYPE; diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index ec7b14c..ba5e314 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -239,6 +239,8 @@ set(SRCS ../shared/thread_safe_queue.h ../shared/library_manager.h ../shared/library_manager.cpp + ../shared/sys_lib.cpp + ../shared/sys_lib.h ) if(WIN32) diff --git a/story-editor/src/importers/pack_archive.cpp b/story-editor/src/importers/pack_archive.cpp index 55f06a5..f7407fb 100644 --- a/story-editor/src/importers/pack_archive.cpp +++ b/story-editor/src/importers/pack_archive.cpp @@ -13,8 +13,10 @@ #include "story_project.h" #include "resource_manager.h" #include "uuid.h" +#include "sys_lib.h" -PackArchive::PackArchive() +PackArchive::PackArchive(ILogger &log) + : m_log(log) { } @@ -34,50 +36,6 @@ std::vector PackArchive::GetImages() } -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); @@ -189,7 +147,7 @@ std::vector PackArchive::FilesToJson(const std::string &type, const res_file[12] = '\0'; std::string res_file_string(res_file); - ReplaceCharacter(res_file_string, "\\", "/"); + SysLib::ReplaceCharacter(res_file_string, "\\", "/"); resList.push_back(res_file_string); @@ -381,10 +339,10 @@ bool PackArchive::Load(const std::string &filePath) 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::string fileName = SysLib::GetFileName(filePath); + std::string ext = SysLib::GetFileExtension(fileName); + SysLib::EraseString(fileName, "." + ext); // on retire l'extension du pack + mPackName = SysLib::ToUpper(fileName); std::cout << "Pack name: " << mPackName << std::endl; @@ -427,7 +385,7 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str // STUDIO format std::ifstream f(basePath + "/story.json"); nlohmann::json j = nlohmann::json::parse(f); - StoryProject proj; + StoryProject proj(m_log); ResourceManager res; if (j.contains("title")) @@ -517,7 +475,7 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str } catch(std::exception &e) { - std::cout << e.what() << std::endl; + m_log.Log(std::string("Import failure: ") + e.what()); } return false; @@ -527,7 +485,7 @@ std::string PackArchive::GetImage(const std::string &fileName) { //"C8B39950DE174EAA8E852A07FC468267/rf/000/05FB5530" std::string imagePath = mPackName + "/rf/" + fileName; - ReplaceCharacter(imagePath, "\\", "/"); + SysLib::ReplaceCharacter(imagePath, "\\", "/"); std::cout << "Loading " + imagePath << std::endl; return OpenImage(imagePath); @@ -542,7 +500,7 @@ std::string PackArchive::CurrentSound() { //"C8B39950DE174EAA8E852A07FC468267/sf/000/05FB5530" std::string soundPath = mPackName + "/sf/" + std::string(mCurrentNode.si_file); - ReplaceCharacter(soundPath, "\\", "/"); + SysLib::ReplaceCharacter(soundPath, "\\", "/"); std::cout << "Loading " + soundPath << std::endl; diff --git a/story-editor/src/importers/pack_archive.h b/story-editor/src/importers/pack_archive.h index 688933e..a7783ec 100644 --- a/story-editor/src/importers/pack_archive.h +++ b/story-editor/src/importers/pack_archive.h @@ -6,11 +6,12 @@ #include #include "ni_parser.h" #include "json.hpp" +#include "i_logger.h" class PackArchive { public: - PackArchive(); + PackArchive(ILogger &log); bool Load(const std::string &filePath); std::string OpenImage(const std::string &fileName); @@ -34,6 +35,7 @@ public: std::string HexDump(const char *desc, const void *addr, int len); private: + ILogger &m_log; Zip mZip; std::string mPackName; uint32_t mCurrentNodeId = 0; @@ -47,11 +49,7 @@ private: std::map m_resources; bool ParseNIFile(const std::string &root); - std::string GetFileName(const std::string &path); - std::string GetFileExtension(const std::string &FileName); - void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace); - void EraseString(std::string &theString, const std::string &toErase); - std::string ToUpper(const std::string &input); + void DecipherFileOnDisk(const std::string &fileName); void DecipherFiles(const std::string &directory, const std::string &suffix); diff --git a/story-editor/src/library_window.cpp b/story-editor/src/library_window.cpp index c02d8d5..1b0013a 100644 --- a/story-editor/src/library_window.cpp +++ b/story-editor/src/library_window.cpp @@ -1,11 +1,13 @@ +#include +#include + +#include "IconsMaterialDesignIcons.h" +#include "i_story_manager.h" +#include "base64.hpp" +#include "sys_lib.h" #include "library_window.h" #include "gui.h" #include "ImGuiFileDialog.h" -#include -#include "IconsMaterialDesignIcons.h" -#include "i_story_manager.h" -#include -#include "base64.hpp" typedef int (*xfer_callback_t)(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); @@ -237,19 +239,21 @@ void LibraryWindow::SharePointJsonDownloadedCallback(bool success, const std::st std::ifstream f(filename); nlohmann::json j = nlohmann::json::parse(f); - std::string archive = j["@content.downloadUrl"].get(); std::string name = j["name"].get(); - m_downloadQueue.push({ "dl", archive, - ToLocalStoreFile(StoryProject::Normalize(name)), + ToLocalStoreFile(SysLib::Normalize(name)), std::bind(&LibraryWindow::StoryFileDownloadedCallback, this, std::placeholders::_1, std::placeholders::_2) }); } + catch(nlohmann::json::exception &e) + { + std::cout << "Json parse error: " << e.what() << std::endl; + } catch(std::exception &e) { std::cout << e.what() << std::endl; @@ -261,7 +265,7 @@ void LibraryWindow::StoryFileDownloadedCallback(bool success, const std::string std::cout << "Finished to download: " << filename << std::endl; - std::string ext = StoryProject::GetFileExtension(filename); + std::string ext = SysLib::GetFileExtension(filename); if (ext == "zip") { @@ -354,7 +358,7 @@ inline void InfosPane(const char *vFilter, IGFDUserDatas vUserDatas, bool *vCant std::string LibraryWindow::ToLocalStoreFile(const std::string &url) { - auto filename = StoryProject::GetFileName(url); + auto filename = SysLib::GetFileName(url); filename = m_libraryManager.LibraryPath() + "/store/" + filename; std::cout << "Store file: " << filename << std::endl; @@ -576,7 +580,7 @@ void LibraryWindow::Draw() m_downloadQueue.push({ "dl", obj.download, - ToLocalStoreFile(StoryProject::Normalize(obj.title)), + ToLocalStoreFile(SysLib::Normalize(obj.title)), std::bind(&LibraryWindow::StoryFileDownloadedCallback, this, std::placeholders::_1, std::placeholders::_2) }); } @@ -647,9 +651,6 @@ void LibraryWindow::Draw() if (std::filesystem::is_directory(outputDir)) { - // Generate TLV file (index of all stories) - m_libraryManager.Save(); - // Copy all files to device m_libraryManager.CopyToDevice(outputDir); } diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 4a89be3..6303011 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -21,7 +21,8 @@ #include "ImGuiFileDialog.h" MainWindow::MainWindow() - : m_emulatorWindow(*this) + : m_libraryManager(*this) + , m_emulatorWindow(*this) , m_resourcesWindow(*this) , m_nodeEditorWindow(*this) , m_libraryWindow(*this, m_libraryManager) @@ -658,7 +659,7 @@ void MainWindow::OpenProject(const std::string &uuid) void MainWindow::ImportProject(const std::string &fileName, int format) { - PackArchive archive; + PackArchive archive(*this); archive.ImportStudioFormat(fileName, m_libraryManager.LibraryPath()); @@ -710,7 +711,7 @@ void MainWindow::Loop() while (!done) { - auto time = SDL_GetTicks(); + Uint64 frameStart = SDL_GetTicks(); bool aboutToClose = m_gui.PollEvent(); m_gui.StartFrame(); @@ -755,8 +756,10 @@ void MainWindow::Loop() // Rendering and event handling - if ((SDL_GetTicks() - time) < 10) { - SDL_Delay(10); + Uint64 frameTime = SDL_GetTicks() - frameStart; // Temps écoulé pour la frame + + if (frameTime < 16) { // Limite de 60 FPS + SDL_Delay(16 - frameTime); // Attendez pour compléter la frame } @@ -819,19 +822,46 @@ std::shared_ptr MainWindow::CreateNode(const std::string &type) void MainWindow::Build(bool compileonly) { - // 1. First compile nodes to assembly - if (CompileToAssembler()) + if (m_story->GenerateScript(m_currentCode)) { - - // 2. Compile the assembly to machine binary - GenerateBinary(); + m_editorWindow.SetScript(m_currentCode); + m_dbg.run_result = VM_FINISHED; + m_dbg.free_run = false; if (!compileonly) { // 3. Convert all media to desired type format - ConvertResources(); + m_resources.ConvertResources(m_story->AssetsPath(), ""); // pas de répertoire de destination + } + + Chip32::Assembler::Error err; + if (m_story->GenerateBinary(m_currentCode, err)) + { + m_result.Print(); + + Log("Binary successfully generated."); + + if (m_story->CopyProgramTo(m_rom_data, sizeof (m_rom_data))) + { + // m_ramView->SetMemory(m_ram_data, sizeof(m_ram_data)); +// m_romView->SetMemory(m_rom_data, m_program.size()); + m_story->SaveBinary(); + chip32_initialize(&m_chip32_ctx); + m_dbg.run_result = VM_READY; + UpdateVmView(); + } + else + { + Log("Program too big. Expand ROM memory."); + } + } + else + { + Log(err.ToString(), true); + m_editorWindow.AddError(err.line, err.message); // show also the error in the code editor } } + } void MainWindow::DeleteNode(const std::string &id) @@ -849,94 +879,18 @@ std::list> MainWindow::GetNodeConnections(const std: return m_story->GetNodeConnections(nodeId); } -bool MainWindow::CompileToAssembler() -{ - // 1. Check if the model can be compiled, check for errors and report - // FIXME - - // 2. Generate the assembly code from the model - bool ret = m_story->Build(m_currentCode); - - // Add global functions - if (ret) - { - std::string buffer; - - std::ifstream f("scripts/media.asm"); - f.seekg(0, std::ios::end); - buffer.resize(f.tellg()); - f.seekg(0); - f.read(buffer.data(), buffer.size()); - m_currentCode += buffer; - - m_editorWindow.SetScript(m_currentCode); - } - - return ret; -} - -void MainWindow::GenerateBinary() -{ - m_dbg.run_result = VM_FINISHED; - m_dbg.free_run = false; - - if (m_assembler.Parse(m_currentCode) == true ) - { - if (m_assembler.BuildBinary(m_program, m_result) == true) - { - m_result.Print(); - - Log("Binary successfully generated."); - - // Update ROM memory - std::copy(m_program.begin(), m_program.end(), m_rom_data); - - // FIXME -// m_ramView->SetMemory(m_ram_data, sizeof(m_ram_data)); -// m_romView->SetMemory(m_rom_data, m_program.size()); - m_story->SaveBinary(m_program); - chip32_initialize(&m_chip32_ctx); - m_dbg.run_result = VM_READY; - UpdateVmView(); - // DebugContext::DumpCodeAssembler(m_assembler); - } - else - { - Chip32::Assembler::Error err = m_assembler.GetLastError(); - Log(err.ToString(), true); - m_editorWindow.AddError(err.line, err.message); // show also the error in the code editor - } - } - else - { - Chip32::Assembler::Error err = m_assembler.GetLastError(); - Log(err.ToString(), true); - m_editorWindow.AddError(err.line, err.message); // show also the error in the code editor - } -} - void MainWindow::UpdateVmView() { // FIXME // m_vmDock->updateRegistersView(m_chip32_ctx); - // Highlight next line in the test editor uint32_t pcVal = m_chip32_ctx.registers[PC]; - // On recherche quelle est la ligne qui possède une instruction à cette adresse - std::vector::const_iterator ptr = m_assembler.Begin(); - for (; ptr != m_assembler.End(); ++ptr) + uint32_t line = 1; + if (m_story->GetAssemblyLine(pcVal, line)) { - if ((ptr->addr == pcVal) && ptr->isRomCode()) - { - break; - } - } - - if (ptr != m_assembler.End()) - { - m_dbg.line = (ptr->line - 1); + m_dbg.line = (line - 1); m_editorWindow.HighlightLine(m_dbg.line); } else @@ -944,51 +898,10 @@ void MainWindow::UpdateVmView() // Not found Log("Reached end or instruction not found line: " + std::to_string(m_dbg.line)); } - - // Refresh RAM content // m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size); } -void MainWindow::ConvertResources() -{ - auto [b, e] = m_resources.Items(); - for (auto it = b; it != e; ++it) - { - std::string inputfile = m_story->BuildFullAssetsPath((*it)->file.c_str()); - std::string outputfile = std::filesystem::path(m_story->AssetsPath() / StoryProject::RemoveFileExtension((*it)->file)).string(); - - int retCode = 0; - if ((*it)->format == "PNG") - { - outputfile += ".qoi"; // FIXME: prendre la config en cours désirée - retCode = MediaConverter::ImageToQoi(inputfile, outputfile); - } - else if ((*it)->format == "MP3") - { - outputfile += ".wav"; // FIXME: prendre la config en cours désirée - retCode = MediaConverter::Mp3ToWav(inputfile, outputfile); - } - else if ((*it)->format == "OGG") - { - outputfile += ".wav"; // FIXME: prendre la config en cours désirée - retCode = MediaConverter::OggToWav(inputfile, outputfile); - } - else - { - Log("Skipped: " + inputfile + ", unknown format" + outputfile, true); - } - - if (retCode < 0) - { - Log("Failed to convert media file " + inputfile + ", error code: " + std::to_string(retCode) + " to: " + outputfile, true); - } - else if (retCode == 0) - { - Log("Convertered file: " + inputfile); - } - } -} void MainWindow::SaveParams() { diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index 4410a70..d52e556 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -63,7 +63,7 @@ struct DebugContext }; -class MainWindow : public IStoryManager, public IAudioEvent +class MainWindow : public IStoryManager, public IAudioEvent, public ILogger { public: MainWindow(); @@ -82,9 +82,7 @@ private: uint8_t m_ram_data[16*1024]; chip32_ctx_t m_chip32_ctx; - // Assembleur & Debugger - std::vector m_program; - Chip32::Assembler m_assembler; + Chip32::Result m_result; DebugContext m_dbg; std::string m_currentCode; @@ -124,7 +122,6 @@ private: // From IStoryManager (proxy to StoryProject class) virtual void OpenProject(const std::string &uuid) override; virtual void ImportProject(const std::string &fileName, int format); - virtual void Log(const std::string &txt, bool critical = false) override; virtual void PlaySoundFile(const std::string &fileName) override;; virtual std::string BuildFullAssetsPath(const std::string &fileName) const override; virtual std::pair Images() override; @@ -148,6 +145,10 @@ private: virtual void Next() override; virtual void Previous() override; + + // From ILogger + virtual void Log(const std::string &txt, bool critical = false) override; + // From IAudioEvent virtual void EndOfAudio() override; @@ -162,9 +163,6 @@ private: void CloseProject(); void DrawStatusBar(); - bool CompileToAssembler(); - void ConvertResources(); - void GenerateBinary(); void UpdateVmView(); uint8_t Syscall(chip32_ctx_t *ctx, uint8_t code); std::string GetFileNameFromMemory(uint32_t addr); diff --git a/story-editor/src/node_engine/media_node.cpp b/story-editor/src/node_engine/media_node.cpp index 169be5d..0bf2bb4 100644 --- a/story-editor/src/node_engine/media_node.cpp +++ b/story-editor/src/node_engine/media_node.cpp @@ -1,7 +1,7 @@ #include "media_node.h" #include "story_project.h" #include "connection.h" - +#include "sys_lib.h" static std::string ChoiceLabel(const std::string &id) { @@ -74,25 +74,26 @@ std::string MediaNode::Build(IStoryProject &story, int nb_out_conns) << " Type: " << (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice") << "\n"; - std::string image = StoryProject::RemoveFileExtension(image); - std::string sound = StoryProject::RemoveFileExtension(sound); + + std::string img = SysLib::RemoveFileExtension(image); + std::string snd = SysLib::RemoveFileExtension(sound); // Le label de ce noeud est généré de la façon suivante : // "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien ss << BaseNode::GetEntryLabel(GetId()) << ":\n"; - if (image.size() > 0) + if (img.size() > 0) { - ss << "lcons r0, $" << image << "\n"; + ss << "lcons r0, $" << img << "\n"; } else { ss << "lcons r0, 0\n"; } - if (sound.size() > 0) + if (snd.size() > 0) { - ss << "lcons r1, $" << sound << "\n"; + ss << "lcons r1, $" << snd << "\n"; } else {