0

So, I'm coding for a C++ program (well, actually a .dll plugin for RpgMaker 2003), and I have a code like this:

#include <DynRPG/DynRPG.h>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

std::map<std::string, std::string> configuration;

bool onStartup(char *pluginName) {
               // We load the configuration from the DynRPG.ini file here
               configuration = RPG::loadConfiguration(pluginName);
               return true; // Don't forget to return true so that the start of the game will continue!
}

bool onComment( const char* text,
                const RPG::ParsedCommentData*   parsedData,
                RPG::EventScriptLine*   nextScriptLine,
                RPG::EventScriptData*   scriptData,
                int     eventId,
                int     pageId,
                int     lineId,
                int*    nextLineId )
{
    std::string cmd = parsedData->command;
    std::string filecode;
    std::string scanfile;
    string FileName;

    if(!cmd.compare("file_string")) //new code
    {
        ofstream myfile;
        myfile.open ("dynstore.txt", ios::app);
        myfile << parsedData->parameters[0].text << "\n";
        myfile.close();
        return false;
    }
    if(!cmd.compare("store_file")) //new code
    {
        ofstream myfile;
        myfile.open ("dynstore.txt", ios::app);
        myfile << RPG::actors[parsedData->parameters[0].number]->name << "\n";
        myfile.close();
        return false;
    }
    if(!cmd.compare("load_file")) //new code
    {
        ifstream myfile;
        myfile.open ("dynstore.txt");
        if(myfile.good()) //this should skip if the file doesn't exist.
        {
            getline (myfile, filecode);
            RPG::actors[parsedData->parameters[0].number]->name = filecode;
        }
        myfile.close();
        return false;
    }

    //These first three allow only storage using dynstore.

    if(!cmd.compare("create_file")) //custom file name, plus storing text
    {
        FileName = parsedData->parameters[0].text;
        ofstream myfile;
        myfile.open (FileName.c_str(), ios::app);
        myfile << parsedData->parameters[1].text << "\n";
        myfile.close();
        return false;
    }
    //This stores a word and makes a new line

    if(!cmd.compare("create_save")) //custom file name, plus storing text
    {
        int GameFile = RPG::variables[3351];

        ofstream myfile;
        if(GameFile == 0)
        {
            FileName = parsedData->parameters[0].text;
            myfile.open (FileName.c_str(), ios::app);
        }
        if(GameFile == 1)
        {
            myfile.open ("Save01.txt", ios::app);
        }
        if(GameFile == 2)
        {
            myfile.open ("Save02.txt", ios::app);
        }
        if(GameFile == 3)
        {
            myfile.open ("Save03.txt", ios::app);
        }
        if(GameFile == 4)
        {
            myfile.open ("Save04.txt", ios::app);
        }
        if(GameFile == 5)
        {
            myfile.open ("Save05.txt", ios::app);
        }
        if(GameFile == 6)
        {
            myfile.open ("Save06.txt", ios::app);
        }
        myfile << parsedData->parameters[1].text << "\n";
        myfile.close();
        return false;
    }
    //This stores a word and makes a new line

    if(!cmd.compare("make_file")) //custom file name, plus storing text
    {
        FileName = parsedData->parameters[0].text;
        ofstream myfile;
        myfile.open (FileName.c_str(), ios::app);
        myfile << RPG::actors[parsedData->parameters[1].number]->name;
        myfile.close();
        return false;
    }
    //This stores a name

    if(!cmd.compare("read_file")) //sets data info to name for easy read
    {
        FileName = parsedData->parameters[0].text;
        ifstream myfile;
        myfile.open (FileName.c_str());
        if(myfile.good()) //this should skip if the file doesn't exist.
        {
            getline (myfile, filecode);
            RPG::actors[parsedData->parameters[1].number]->name = filecode;
        }
        myfile.close();
        return false;
    }
    if(!cmd.compare("clear_file")) //sets data info to name for easy read
    {
        FileName = parsedData->parameters[0].text;
        ofstream myfile;
        myfile.open (FileName.c_str());
        myfile.clear();
        myfile.close();
        return false;
    }

     if(!cmd.compare("scan_save"))
    {
        //Attempt ONE to scan the file for a string
        //this one works by turning on a switch if the string is found

        int GameFile = RPG::variables[3351];

        ifstream myfile;
        if(GameFile == 0)
        {
            FileName = parsedData->parameters[0].text;
            myfile.open (FileName.c_str());
        }
        if(GameFile == 1)
        {
            myfile.open ("Save01.txt");
        }
        if(GameFile == 2)
        {
            myfile.open ("Save02.txt");
        }
        if(GameFile == 3)
        {
            myfile.open ("Save03.txt");
        }
        if(GameFile == 4)
        {
            myfile.open ("Save04.txt");
        }
        if(GameFile == 5)
        {
            myfile.open ("Save05.txt");
        }
        if(GameFile == 6)
        {
            myfile.open ("Save06.txt");
        }
        scanfile = parsedData->parameters[1].text;
        int switchnum = parsedData->parameters[2].number;

        if(myfile.good()) //this should skip if the file doesn't exist.
        {
             for(std::string temp;!myfile.eof();std::getline(myfile,temp))
             {
                 if(temp.find(scanfile)!=std::string::npos)
                 {
                     RPG::switches[switchnum] = true;
                //Turns on the switch if the string has been found in the file
                //ideally, I'd want to save a new name using the scanfile, but I can't figure out how to copy it
                 }
             }
        }
        myfile.close();
        return false;
    } //This checks only the current save, defaulting to the input if none exists

    if(!cmd.compare("scan_file"))
    {
        //Attempt ONE to scan the file for a string
        //this one works by turning on a switch if the string is found
        FileName = parsedData->parameters[0].text;
        ifstream myfile;
        myfile.open (FileName.c_str());
        scanfile = parsedData->parameters[1].text;
        int switchnum = parsedData->parameters[2].number;

        if(myfile.good()) //this should skip if the file doesn't exist.
        {
             for(std::string temp;!myfile.eof();std::getline(myfile,temp))
             {
                 if(temp.find(scanfile)!=std::string::npos)
                 {
                     RPG::switches[switchnum] = true;
                //Turns on the switch if the string has been found in the file
                //ideally, I'd want to save a new name using the scanfile, but I can't figure out how to copy it
                 }
             }
        }
        myfile.close();
        return false;
    }

    if(!cmd.compare("file_good"))
    {
        //Checks for the existence of a file.
        FileName = parsedData->parameters[0].text;
        ifstream myfile;
        myfile.open (FileName.c_str());
        int switchnum = parsedData->parameters[1].number;

        if(myfile.good()) //this should skip if the file doesn't exist.
        {
             RPG::switches[switchnum] = true;
        }

        else
        {
            RPG::switches[switchnum] = false;
        }

        myfile.close();
        return false;
    }

    //End of cmd
    return true;
}

Generally, this is how the .dll plugin works: It runs the DynRPG headers through the linker settings, and the RPG_RT.exe of RpgMaker as its host arguments. onStartup and onComment are DynRPG-specific codes to run compatibly with RpgMaker. You don't need to know most of this, except to know that these if(!cmd.compare(" ")) codes basically are commands for RpgMaker. So, I am telling various files to open text files, but I've been having problems:

  1. I have a code to clear file, and it does clear the file. But I think it creates the file in order to clear it, rather than skipping over if it doesn't exist. In fact, I think this is a persistent issue, I dunno how to get the code to skip over nonexistent file names, so it makes new files if the code checks files.
  2. If possible, that clear file should actually delete the files rather than just making it blank.
  3. It appends information into the file I make (and there doesn't seem to be any memory leak), but it doesn't check to make sure that I haven't already entered the same word or phrase. When I'm writing into the file, I want to check But I think I only know how to check output when loading output, not before input. I want to avoid duplicates. Currently, I have a file called "awards.txt" (unlockable awards) and I have the file with like 6 instances of the same word. I want it to write the same code only once. This is my main problem.

Don't give obtuse advice, assuming I will understand what you mean. Show me actual code. I barely pieced together this much code from reading and adapting other tutorials, and I do not have a degree in computer science (I was a history major, so the theory of C++ code doesn't always make sense to me). Oh and uhhhh, not all the header files are necessary, I tend to include more than I need just in case.

Samantha
  • 1
  • 3
  • I know you've asked for concrete code, but this require much more research than a piece of code to copy. It sounds like you're facing a race-condition between the threads. To fix that, you can either implement a `Multiple-Reader-Single-Writer Job Queue` or implement `Mutual Exclusion Logic` using locks (mutex). – Irad Ohayon Apr 08 '21 at 23:53
  • Coded for years with nothing but the half of a political philosophy degree I earned before I realized finishing the degree would most likely leave me starving in the street, so Comp-sci ain't everything. That said, the degree's early emphasis on logic seems to have set me up well. – user4581301 Apr 09 '21 at 00:06
  • A few unrelated comments: I see `std::map` without `#include `. There's probably a dependency somewhere pulling in `map` for you, but you can't always count on that to hold. Sucks when a completely unrelated change breaks something. Down in the `"load_file"` case of `onComment`, you have `if(myfile.good()) { getline (myfile, filecode);` this tests for success before reading, leaving the read open to failure undetected. It's better if you test after the read. – user4581301 Apr 09 '21 at 00:11
  • A bit later you have `for(std::string temp;!myfile.eof();std::getline(myfile,temp))` which is similar. The EOF flag is only set after EOF is found which means you could have a bogus `getline` that failed because it found the end of the file slip through. Use `for(std::string temp;std::getline(myfile,temp); )` instead. If `gettline` fails for any reason, including getting no data because it hit the end of the file before finding anything, the loop won't enter. – user4581301 Apr 09 '21 at 00:16
  • See [Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?](https://stackoverflow.com/questions/5605125) for details – user4581301 Apr 09 '21 at 00:16
  • Hey, what compiler and version are you using? If it supports C++17, you get access to the [ library](https://en.cppreference.com/w/cpp/filesystem) and the first point is a fairly simple call to [`exists`](https://en.cppreference.com/w/cpp/filesystem/exists), followed by [`resize_file`](https://en.cppreference.com/w/cpp/filesystem/resize_file) to set the size to 0 if the file exists. If not, blanking a file without creating a new one is a good question all by itself. – user4581301 Apr 09 '21 at 00:51
  • Rethinking that last bit of advice. Just call `resize_file` and deal with the thrown exception. You have to anyway in case someone else deleted the file between the existence test and the resize or someone could have the file open and locked. Smurf it. I might as well just answer at least the first bullet point. – user4581301 Apr 09 '21 at 01:22
  • I use CodeBlocks. It's currently running on Windows (I have a dual-boot computer) with GNU GCC Compiler. Not sure what version. My C++ training kinda was high school, and it was kinda half-baked as the teacher spent a large portion on remedial with other students. I and about four others were above average, but we knew we weren't that good. Coulda used better fundamentals. Where do I put the if(myfile.good()) then? Because my understanding was it stopped processing file stuff after close. Or is it considered "read" after the close, but still able to do operations? – Samantha Apr 10 '21 at 04:00
  • Irad Ohayon, kinda what I'm talking about. I dunno what that means. As for the rest from user4581301, I'll probably have to read that link because I'm not even sure if this solves the correct problem. I'll respond to your big response below though... – Samantha Apr 10 '21 at 04:05

1 Answers1

0

Point 1

In

    if(!cmd.compare("clear_file")) //sets data info to name for easy read
    {
        FileName = parsedData->parameters[0].text;
        ofstream myfile;
        myfile.open (FileName.c_str());
        myfile.clear();
        myfile.close();
        return false;
    }

the default file open mode used in myfile.open (FileName.c_str()); will create an empty file, overwriting an exiting file. Cool. Empty file is what you wanted, but yeah, it created an empty file where none existed. To avoid that, you'd need to specify an open mode that won't create a new file. Using this groovy table here, specifying the in open mode will raise an error if the file doesn't exit rather than opening it. Unfortunately if you keep reading you'll find there is no combination of openmodes that will truncate (clear) a file without also creating a new file.

Well doesn't that suck?

In C++17 we can use the <filesystem> Library's resize_file function.

std::error_code code;
std::filesystem::resize_file(FileName, 0, code);
// test code here if you care. Someone else could have the file open and prevent clearing.

Before C++17, its easiest to use system-specific API calls. You mention DLL above, so I'm going to assume a Windows target

HANDLE h = CreateFileA(FileName.c_str(), 
                       GENERIC_WRITE, 
                       0, 
                       NULL, 
                       TRUNCATE_EXISTING, 
                       FILE_ATTRIBUTE_NORMAL, 
                       NULL);
if (h != INVALID_HANDLE_VALUE)
{
    CloseHandle(h);
}

Documentation on CreateFileA. Note I use CreateFileA and not CreateFileW or CreateFile because FileName doesn't contain wide characters.

There might be a more direct call. I'm not really that familiar with the Win132 API.

A POSIX compliant target like Linux (Maybe Mac. I know even less about Macs) uses truncate

int code = truncate(FileName.c_str(), 0);
// test code here and look at errno if you care. Someone else could have the file open.

If you're lucky the compiler developers ported truncate to Windows for you and simplify your job because you only have to support one target.

Side note

Because the openmode was what's actually clearing the file, we should take a look at what myfile.clear(); does: clears error flags on a stream. Normally when a stream operation fails error flags are set so you can check to see what went wrong. When any error flags are set, the stream refuses to process any further transactions and instantly returns. It's on you-the-programmer to check the state of the stream after every transaction to ensure that it worked because if it didn't and you don't check, you'll never know. The stream state and error flags are the ONLY warning you get.

So, you check the flags, handle the flagged error, and then call clear to signal you've resolved the issue and are ready to get back to reading and writing. Note: Sometimes handling the error requires you to clear the flag early in order to remove, read out, bad data so that the program doesn't immediately find the same garbage and fail again once you return to the regular flow of the code.

Point 2

Deleting a file... Now that's easier. In C++17

std::error_code code;
std::filesystem::remove("test", code);
// Wrap in an if and test code if you care about failure. I would. 
// If it's file not found, cool!

And in the old world,

if (remove(FileName.c_str()) !=)
{
    //check errno to find out why
}

Documentation for std::filesystem::remove

Documentation for remove

Point 3

I just saw the time and I need to stop. You should remove point three from the question since it is a completely different issue and deserves its own question.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • I think Point 2 nicely solves Point 1, if there is some sort of "if file exists, erase contents, if not, delete it." Or just delete the file might be best. Point 3 really is the biggest issue, the one I titled the question about. Without that, I get a kinda file that keeps writing repeats. I mentioned that I'd get back to the eof one. Lemme tell you the design plan of the plugin, and maybe you can tell me if I am even doing this right. https://www.rewking.com/dynrpg/getting_started.html (For the basic codes) – Samantha Apr 10 '21 at 04:13
  • I finally got around to editing the code, and it works! Thank you! As to Point 3, I believe it can be fixed internally to RpgMaker by using scan_file before the desired switch. As the scan_file now seems to work (thanks again), it will switch the event switch ON if you already have the string of text in the file, thereby skipping over where it adds it again. As for the remove thing, I simply removed the file, and didn't bother with any if statements. Feel free to mark this as answered now. – Samantha Apr 20 '21 at 18:45