Actually Good scripts exporter (for rojo or whatever else idk)

“Rbxlx to rojo” can’t export even 5% of scripts in my game, so I finally made a replacement for it

A single “rbxlx to visual studio code” script to rule them all

It is very simple but does it’s job 100%
Also, it’s written in c++ cause i aint touching lua io lib fr

You have to create all the folders beforehand though, there’s no good way to create folders before c++17 so i dont bother

The code, its like 120 lines, really not much

#include <sstream>
#include <string>
#include <fstream>
#include <iostream>
#include <vector>

const size_t NOT_FOUND = std::string::npos;
using namespace std;

string RemoveStringPrefix(string input, string prefix){
    int prefixStartingIndex = input.find(prefix);
    if(prefixStartingIndex == NOT_FOUND) return input;
    
    return input.substr(prefixStartingIndex + prefix.length());
}

void EraseFile(string fileNameAndPathAndShit){
    ofstream fileThatWillGetErased;
    fileThatWillGetErased.open(fileNameAndPathAndShit, ios::out | ios::trunc);
    fileThatWillGetErased.close();
}

bool OutputShitFromFileToNewFileWithNameAndType(istream &file, string newFileName, string fileType){
    ofstream outputFile;
    if(fileType == "" or fileType == " ") fileType = ".txt";
    
    string outputFilePath = "../files/" + newFileName + fileType;
    EraseFile(outputFilePath);
    outputFile.open(outputFilePath, ios::app);

    string line;
    bool sourceStarted = false;

    const string sourcePrefix = "<ProtectedString name=\"Source\"><![CDATA[";

    while(getline(file, line)){
        if(not sourceStarted){
            if(line.find(sourcePrefix) == NOT_FOUND) continue;

            cout<< "source started" << endl;
            cout<< "name = "<< newFileName<<" , type = " <<fileType<<endl<<endl<<endl;

            line = RemoveStringPrefix(line, sourcePrefix);
            sourceStarted = true;
        }
        
        int indexOfSourceEnding = line.find("]]>");
        bool SourceEndingFound = indexOfSourceEnding != NOT_FOUND;

        if (SourceEndingFound) {
            cout<< "source ending found" << endl;
            line = line.substr(0, indexOfSourceEnding);
            outputFile << line << endl;
            return 1;
        } 

        outputFile << line << endl;
    }
    return 0;
}

string TryExtractingFilenameFromLine(string line){
    const string nameTag = "<string name=\"Name\">";
    int nameStartingIndex = line.find(nameTag);
    if(nameStartingIndex == NOT_FOUND) return "";
    
    nameStartingIndex += nameTag.length();
    line = line.substr(nameStartingIndex);

    int nameEndingIndex = line.find('<');
    return line.substr(0, nameEndingIndex);
}

void StartOutputtingShitFromFileOfType(istream &file, string fileType){
    string outputFileName = "";
    string line;
    while(getline(file, line)){
        if(outputFileName == ""){
            outputFileName = TryExtractingFilenameFromLine(line);
            if(outputFileName == "") continue;
        }
        if(OutputShitFromFileToNewFileWithNameAndType(file, outputFileName, fileType)) return;
    }
}

int FindIndexOfStringSubstringInStringArray(string checkedString, vector<string> stringsArray){
    for(size_t i=0; i<stringsArray.size(); i++){
        if(checkedString.find(stringsArray[i]) != NOT_FOUND) return i;
    } return NOT_FOUND;
}

int main(){
    ifstream inputFile("../input.rbxlx");

    if (not inputFile.is_open()) {
        cerr << "Unable to open file!" << endl;
    }

    string currentLine;
    vector<string> ParsedFileTypes = {
        "ModuleScript",
        "LocalScript",
        "Script",
    };

    vector<string> OutputFileTypes = {
        ".lua",
        ".client.lua",
        ".server.lua"
    };

    while(getline(inputFile, currentLine)){
        int currentParsedTypeIndexInTypesArray = FindIndexOfStringSubstringInStringArray(currentLine, ParsedFileTypes);
        if(currentParsedTypeIndexInTypesArray == NOT_FOUND) continue;
        
        StartOutputtingShitFromFileOfType(inputFile, OutputFileTypes[currentParsedTypeIndexInTypesArray]);
    }
}

Example of using the script

Settings

if you want to change output types, or choose other instance types to parse (you can parse models, cframes, and a lot of stuff btw), you can by tweaking the main function based on jo needs

this is where output settings are (i dont like fancyness just use your damn console)

alright boys that’s it enjoy i hope someone will use this instead of writing their own parser like i did

5 Likes

i first wrote it in paint


then finished it on paper cause had to go to uni

11 Likes

It’s best practice to build up your project with rojo from the start instead of an already existing place, hence why I think they don’t bother updating the exporter program

Love the LotR reference BTW!

1 Like

FYI, a feature named Syncback is being worked on that basically does this. There are also projects like Lune that provide libraries that allow you to read and manipulate Roblox model files using Luau.

string FolderNameByFileType(string fileType){
    if(fileType == ".lua") return "Modules";
    else if (fileType == ".client.lua") return "Client";
    else if (fileType == ".server.lua") return "Server";
    return "";
}

bool OutputShitFromFileToNewFileWithNameAndType(istream &file, string newFileName, string fileType){
    ofstream outputFile;
    if(fileType == "" or fileType == " ") fileType = ".txt";

    string outputFolder = FolderNameByFileType(fileType);
    string outputFilePath = "../files/" + outputFolder + "/" + newFileName + fileType;

    EraseFile(outputFilePath);
    outputFile.open(outputFilePath, ios::app);

a little change for anyone who wants to have scripts separated into folders by types

idk how to create folders in c++, there isn’t a good way before c++17, so im not gonna add automatic hierarchy detection and stuff :slight_smile:

2 Likes

Argon was cooking with the two-way sync, superior to Rojo’s imo