commit 0cdcf86e4cf027a2ed4b240f48a708a912214d1a Author: Ramen2X Date: Fri Mar 8 01:38:16 2024 -0500 initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a92fad4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Prerequisites +*.d + +# Compiled Object Files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic Libraries +*.so +*.dylib +*.dll + +# Fortran Module Files +*.mod +*.smod + +# Compiled Static Libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Build Directory +build/ + +# Miscellaneous +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ffcdff8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libweaver"] + path = libweaver + url = https://github.com/isledecomp/SIEdit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1f17321 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) + +project(actionheadergen LANGUAGES CXX) + +set(PROJECT_SOURCES + src/main.cpp + src/headergenerator.cpp + src/interleafhandler.cpp +) + +add_executable(actionheadergen ${PROJECT_SOURCES}) + +target_link_libraries(actionheadergen PRIVATE libweaver) + +target_link_directories(actionheadergen PRIVATE "${CMAKE_SOURCE_DIR}/libweaver/build/lib") + +target_include_directories(actionheadergen PRIVATE + "${CMAKE_SOURCE_DIR}/include" + "${CMAKE_SOURCE_DIR}/libweaver/lib" +) + +set_target_properties(actionheadergen PROPERTIES + CXX_STANDARD 98 + CXX_STANDARD_REQUIRED ON +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57c5c06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 isledecomp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/include/headergenerator.h b/include/headergenerator.h new file mode 100644 index 0000000..8b8ab66 --- /dev/null +++ b/include/headergenerator.h @@ -0,0 +1,20 @@ +#ifndef HEADERGENERATOR_H +#define HEADERGENERATOR_H + +#include +#include +#include + +class HeaderGenerator { +public: + bool GenerateHeader(char *p_interleafName, std::vector *p_actionVector, char *p_outputDir); + +private: + bool CreateHeader(char *p_interleafName, char *p_outputDir); + bool WriteHeader(char *p_interleafName, std::vector *p_actionVector); + std::ofstream m_fout; + char m_normalizedInlfName[512]; +}; + +#endif // HEADERGENERATOR_H + diff --git a/include/interleafhandler.h b/include/interleafhandler.h new file mode 100644 index 0000000..e6f4359 --- /dev/null +++ b/include/interleafhandler.h @@ -0,0 +1,20 @@ +#ifndef INTERLEAFHANDLER_H +#define INTERLEAFHANDLER_H + +#include + +class InterleafHandler { +public: + si::Interleaf::Error ReadInterleaf(char *p_filePath); + bool SortActionsIntoVector(); + + inline std::vector *GetActionVector() { return m_actionVector; } + +private: + si::Interleaf m_inlf; + size_t m_actionCount; + std::vector *m_actionVector; +}; + +#endif // INTERLEAFHANDLER_H + diff --git a/libweaver b/libweaver new file mode 160000 index 0000000..76dab7d --- /dev/null +++ b/libweaver @@ -0,0 +1 @@ +Subproject commit 76dab7dfd9af194637e99f58531807bce06a12b4 diff --git a/src/headergenerator.cpp b/src/headergenerator.cpp new file mode 100644 index 0000000..a7ad5c7 --- /dev/null +++ b/src/headergenerator.cpp @@ -0,0 +1,137 @@ +#include + +#include "headergenerator.h" + +// the generic extension to the header filename, used by weaver +const char *g_headerExt = "_actions.h"; + +// the generic extension used for the include guard +const char *g_headerGuard = "_ACTIONS_H"; + +// enum entry prefix used in the isle decomp +const char *g_enumEntryPrefix = "c_"; + +// notice to not edit autogenerated headers +const char *g_doNotEditNotice = "// This file was automatically generated by the actionheadergen tool.\n// Please do not manually edit this file.\n"; + +bool HeaderGenerator::GenerateHeader(char *p_interleafName, std::vector *p_actionVector, char *p_outputDir) +{ + // attempt to create header file + if (!CreateHeader(p_interleafName, p_outputDir)) { + printf("Failed to create header, check file permissions?\n"); + return false; + } + + // attempt to write header file + if (!WriteHeader(p_interleafName, p_actionVector)) { + printf("Failed to write header\n"); + return false; + } + + // success + return true; +} + +bool HeaderGenerator::CreateHeader(char *p_interleafName, char *p_outputDir) +{ + char headerName[1024]; + char outputPath[2048]; + + strcpy(m_normalizedInlfName, p_interleafName); + + // format Interleaf name to mostly lowercase, + // to make acceptable by decomp styling guidelines + for (int i = 1; i < strlen(p_interleafName); i++) { + m_normalizedInlfName[i] = tolower(m_normalizedInlfName[i]); + } + + // set the header name to the Interleaf name + strcpy(headerName, m_normalizedInlfName); + + // set first character to lowercase, which + // was skipped by the previous operation + headerName[0] = tolower(headerName[0]); + + // append the generic extension + strcat(headerName, g_headerExt); + + // generate the full path of the output file + strcpy(outputPath, p_outputDir); + strcat(outputPath, "/"); + strcat(outputPath, headerName); + + m_fout.open(outputPath); + + // make sure we can actually create this file + if (!m_fout.is_open()) { + return false; + } + + return true; +} + +bool HeaderGenerator::WriteHeader(char *p_interleafName, std::vector *p_actionVector) +{ + // create boilerplate + char guardBuffer[2048]; + char guardName[1024]; + + strcpy(guardName, p_interleafName); + strcat(guardName, g_headerGuard); + + // might be messy, but I don't know a better way to do it + strcpy(guardBuffer, "#ifndef "); + strcat(guardBuffer, guardName); + strcat(guardBuffer, "\n"); + strcat(guardBuffer, "#define "); + strcat(guardBuffer, guardName); + + // insert notice to not edit this header + m_fout << g_doNotEditNotice; + + // insert include guard + m_fout << guardBuffer << "\n\n"; + + // declare enum + m_fout << "enum " << m_normalizedInlfName << "Script {\n"; + + // cache action vector size + int vecSize = p_actionVector->size(); + + // iterate through the action vector and insert each action into enum + for (int i = 0; i < vecSize; i++) { + if (strlen(p_actionVector->at(i)) == 0) { + // this action has no name, so it's + // probably padding or not important + // we'll just skip it + + // check if the previous action was also skipped so we + // don't stack crazy amounts of whitespace into the enum + if (i != 0) { + if (strlen(p_actionVector->at(i - 1)) != 0) { + m_fout << "\n"; + } + } + + continue; + } + m_fout << " " << g_enumEntryPrefix << p_actionVector->at(i) << " = " << i; + if (i != vecSize - 1) { + // if there are still more entries, we need write a comma + m_fout << ","; + } + m_fout << "\n"; + } + + // all done with actions, so lets close the enum + m_fout << "};\n\n"; + + // finally, close the include guard + m_fout << "#endif // " << guardName << "\n"; + + // close the file + m_fout.close(); + + return true; +} + diff --git a/src/interleafhandler.cpp b/src/interleafhandler.cpp new file mode 100644 index 0000000..63dead6 --- /dev/null +++ b/src/interleafhandler.cpp @@ -0,0 +1,39 @@ +#include "interleafhandler.h" + +si::Interleaf::Error InterleafHandler::ReadInterleaf(char *p_filePath) +{ + // this is basically a wrapper function for libweaver's + // Read() so we can set private member variables + return m_inlf.Read(p_filePath); +} + +bool InterleafHandler::SortActionsIntoVector() +{ + // if there's no actions in this Interleaf, exit + if (!m_inlf.HasChildren()) { + return false; + } + + // get the amount of actions in this Interleaf + m_actionCount = m_inlf.GetChildCount(); + + // prepare our vector for use + m_actionVector = new std::vector; + + // itereate through every action in our action count + for (size_t i = 0; i < m_actionCount; i++) { + // get the action as Core initially + si::Core *actionAsCore = m_inlf.GetChildAt(i); + + // Core doesn't provide some of the data we need, so we get the + // action as Object, so then we can retrieve the name of the action + if (si::Object *actionAsObject = dynamic_cast(actionAsCore)) { + // push the name of the action into the vector + m_actionVector->push_back(actionAsObject->name().c_str()); + } + } + + // success + return true; +} + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..63dfc17 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,140 @@ +#include +#include +#include + +#include "headergenerator.h" +#include "interleafhandler.h" + +bool RecursivelyFindInterleaf(const char *p_path, std::vector &p_interleafFiles) +{ + // TODO: *by default* this is a UNIX-specific solution + // Windows is capable of using dirent through third party implementations, + // but it would be nice to have something that works out of the box on Win32 + // this is one of the pains of C++98, there is no good directory support in the standard + DIR *directory = opendir(p_path); + if (!directory) { + printf("Could not open directory %s, exiting\n", p_path); + return false; + } + + struct dirent *dentry; + // start scouring the directory + while ((dentry = readdir(directory)) != NULL) { + // if this path is a directory + if (dentry->d_type == DT_DIR) { + // filter out backwards paths + if (strcmp(dentry->d_name, ".") != 0 && strcmp(dentry->d_name, "..") != 0) { + // this is a subdirectory, so recurse + char nextPathBuffer[1024]; + snprintf(nextPathBuffer, sizeof(nextPathBuffer), "%s/%s", p_path, dentry->d_name); + + if (!RecursivelyFindInterleaf(nextPathBuffer, p_interleafFiles)) { + // couldn't access this subdirectory, abort + return false; + } + } + } + else { + if (strcasestr(dentry->d_name, ".si") != NULL) { + // found an Interleaf file, return it + char fullPathBuffer[1024]; + + // construct the full path to the file first + snprintf(fullPathBuffer, sizeof(fullPathBuffer), "%s/%s", p_path, dentry->d_name); + + p_interleafFiles.push_back(fullPathBuffer); + } + } + } + + // success + closedir(directory); + return true; +} + +int main(int argc, char *argv[]) +{ + // no file or output directory provided + if (argc < 3) { + printf("Usage: %s ( or ) \n", argv[0]); + return 1; + } + + const char *interleafExtension = ".si"; + + std::vector interleafFiles; + + // cache the arguments so we don't have to enter the array unnecessarily + char *filePath = argv[1]; + char *outputDir = argv[2]; + + // if filePath doesn't end in ".si", + // we got passed a directory, enter batch mode + if (strcasecmp(filePath + strlen(filePath) - strlen(interleafExtension), interleafExtension) != 0) { + // find each Interleaf recursively in the directory + // get all files and folders in current directory + if (!RecursivelyFindInterleaf(filePath, interleafFiles)) { + return 1; + } + } + else interleafFiles.push_back(filePath); // we were provided a single file, but we'll still use the vector + + // iterate through every Interleaf in the vector and perform our operations + for (int i = 0; i < interleafFiles.size(); i++) { + + InterleafHandler ihandler; + + char currentFilePath[1024]; + strcpy(currentFilePath, interleafFiles.at(i).c_str()); + + // load the Interleaf provided to us in the first argument + // InterleafHandler is using libweaver for this + // if we're not successful, exit + if (ihandler.ReadInterleaf(currentFilePath) != si::Interleaf::ERROR_SUCCESS) { + printf("Failure reading Interleaf, exiting\n"); + return 1; + } + + // sort the Interleaf's actions into a vector + // if libweaver is stable enough, a failure is only + // possible if no actions exist in the Interleaf + if (!ihandler.SortActionsIntoVector()) { + printf("No actions found in this Interleaf, exiting\n"); + return 1; + } + + // set filename to filePath without directory appended + // libweaver unfortunately doesn't provide a way to get + // the Interleaf name so we have to construct it ourselves + char *filename = strrchr(currentFilePath, '/'); + if (filename) { + // we don't need ".SI" for our purposes + // so just remove the last 3 characters + filename[strlen(filename) - 3] = '\0'; + // remove the first character as well, since it will be "/" + filename = filename + 1; + } + + HeaderGenerator hgenerator; + + // generate the actual header + // we use the Interleaf name for the filename + // and the vector previously generated to make the enum data + if (!hgenerator.GenerateHeader(filename, ihandler.GetActionVector(), outputDir)) { + // we failed for some reason, so let's just move on + printf("Failure generating header for %s, skipping\n", filename); + + delete ihandler.GetActionVector(); + continue; + } + + // all steps were successful + delete ihandler.GetActionVector(); + + printf("Succesfully generated header for %s\n", filename); + } + + printf("Finished!\n"); + return 0; +} +