[SOLVED] last char of last line in txtfile not read using ReadRawLineBack?

Started by arj0n, Thu 30/12/2021 16:11:58

Previous topic - Next topic

arj0n

I have a plain text file in the savegame folder that holds several lines of information.
- The second-last line holds the author information
- The last line holds the title information
- All lines above these 2 lines contain level input

The function as shown below reads through all the lines of a selected file using
ReadRawLineBack and gets the author and the title information in order to
show them, combined into one string, on a label.

It works fine but I have one small problem:
The last character of the title line (which is always the last line of the file)
seems not to be included in the string result.


so if the last line of the txtfile contains: Title: Level1
after ReadRawLineBack, the string result is: Title: Level

note:
As a test, if I add an empty line after the last line (after the title line) the
complete title, including the last character, is correctly read. But that would
not be a solution for my problem as these files are (and should be) exported
without adding an extra empty line.

So, how can I read the last line in a way that ALL CHARACTERS are included?

Code: ags

function load_levelinfoCustom()
{
  String Line;
  String LineTitle;
  String LineAuthor;
  String checkTitle;
  String checkAuthor;
  String finalInfo;
  
  String fileName = String.Format("$SAVEGAMEDIR$/%s", lst_CustomLevels.Items[lst_CustomLevels.SelectedIndex]);
  File *input = File.Open(fileName, eFileRead);
  
  if (input == null) Display ("the file seems to be empty...");
  else if (input != null) 
  {
    while (!input.EOF)
    {
      Line = input.ReadRawLineBack();

      checkAuthor = Line.Substring(0, 7);
      checkTitle = Line.Substring(0, 6);
      if (checkAuthor == "Author:") LineAuthor = Line;
      if (checkTitle == "Title:") LineTitle = Line;
    }
    if (LineTitle == null) LineTitle = "title unknown";
    if (LineAuthor == null) LineAuthor = "author unknown";
    
    finalInfo = LineTitle.Append("[");
    finalInfo = finalInfo.Append(LineAuthor);
    lbl_LevelInfo.Text = finalInfo;
    input.Close();
  }
}

Crimson Wizard

This is a known bug, someone else recently discussed this on AGS Discord.

The workaround is to always add an extra linebreak at the end of the file.

This seems to be fixed in 3.6.0.

arj0n


Crimson Wizard

Quote from: arj0n on Thu 30/12/2021 16:11:58
note:
As a test, if I add an empty line after the last line (after the title line) the
complete title, including the last character, is correctly read. But that would
not be a solution for my problem as these files are (and should be) exported
without adding an extra empty line.

Oh, I did not notice this at first.

I think it should also be possible to use File.ReadRawChar() and read byte by byte until the line break to get a line. Thus essentially scripting your own "Read Line".
This will be relatively slower (because of the many function calls in script) so may not be a good solution if files are very large.

arj0n

Quote from: Crimson Wizard on Thu 30/12/2021 17:00:33
The workaround is to always add an extra linebreak at the end of the file.

This seems to be fixed in 3.6.0.

My workaround is very close to your suggestion: In stead of always adding an extra linebreak I now first read the last char of the selected file, and if this is not a LF then WriteRawChar (10). (otherwise each time a new LF should be added which would be junkdata)

Crimson Wizard

Quote from: arj0n on Wed 05/01/2022 15:23:03
My workaround is very close to your suggestion: In stead of always adding an extra linebreak I now first read the last char of the selected file, and if this is not a LF then WriteRawChar (10).

Well, this works so long as the file is writeable by the game; but the game fixing its data files may not work in some circumstances.

arj0n

The data are plain text level files.
The workaround is only used for level files that the ags game can read but are not created by this ags game. One can even create a level in an ordinairy text editor for example, place it in the savegame folder and let the ags game read the file. These level files not created by the ags game might missing that LF at the end.

The data (level files) are always writeable, this is when loading such file and when exporting such file.

Do you have an example of a case where the 'game fixing its data files' may not work?

Crimson Wizard

Quote from: arj0n on Wed 05/01/2022 18:23:15
Do you have an example of a case where the 'game fixing its data files' may not work?

If the data files are located in read-only location, either technically cannot be written to or player does not have enough permissions on their system to write there.


Anyway, tbh, overwriting whole file only to let engine read it again properly, sounds like an overkill.

What about reading a file first, and then reading last character separately if it was not read? You can compare the length of the returned String with the length of the file and if it's shorter - then read the rest yourself.

arj0n

Quote from: Crimson Wizard on Wed 05/01/2022 20:17:27
If the data files are located in read-only location, either technically cannot be written to or player does not have enough permissions on their system to write there.
The data is for that reason written in the savegame folder because (I assumed) that location has no read-only issues.

Quote from: Crimson Wizard on Wed 05/01/2022 20:17:27
Anyway, tbh, overwriting whole file only to let engine read it again properly, sounds like an overkill.
Yes, it looks like an overkill indeed, but:
As the content is unknown before reading a file, the only way to find out if the last line in the file is ok is to check for the last character to be a LF.
After running this check I add a LF in case it is missing.

Quote from: Crimson Wizard on Wed 05/01/2022 20:17:27
What about reading a file first, and then reading last character separately if it was not read?
But how do I know if the last character was read if I don't know the content?
I only know the last char must be a LF.

Quote from: Crimson Wizard on Wed 05/01/2022 20:17:27
You can compare the length of the returned String with the length of the file and if it's shorter - then read the rest yourself.
I see, but how do I check/know the length of the file? By getting File.Position when at the EOF?

Crimson Wizard

Quote from: arj0n on Wed 05/01/2022 22:13:17
I see, but how do I check/know the length of the file? By getting File.Position when at the EOF?

I thought there's File.Length, but since there's not, the way to retrieve one is:
Code: ags

File *f = File.Open(...);
f.Seek(0, eSeekEnd);
int len = f.Position;


Or, since you are reading the file anyway, you may check for Position right after hitting EOF.

EDIT:
But I realized that instead, after reading last line, you may seek back and check if there was a linebreak in the end. If not, then append this last char to the last string.
Basically, it seems what you are already doing (checking the last character), except you do not have to write file back, but perform the check in the end of reading strings, and fixup the string, not the file.

arj0n

Thanx for all the info again CW!
I've added the following check before reading the file with ReadRawLineBack plus adding the last char if LF is missing.
It seems to be working fine now:

Code: ags

bool noLF = false;
String lChar;

String fileName = String.Format("$SAVEGAMEDIR$/%s", lst_CustomLevels.Items[lst_CustomLevels.SelectedIndex]);
File *output = File.Open(fileName, eFileRead);
if (output == null) Display ("error opening file.");
else if (output != null) 
{
  //first check if EOF has a LF:
  output.Seek(-1, eSeekEnd);
  if (output.ReadRawChar() != 10) //no LF
  {
    noLF = true;
    output.Seek(-1, eSeekEnd);
    lChar = String.Format("%c",output.ReadRawChar()); //grab last char
  }
  // now go back to begin of file, read complete file info using ReadRawLineBack and build up strings
  // if the 'title' string (title is the last line in the file) is not empty and (noLF == true) add lChar to last string
  output.Close();
}

SMF spam blocked by CleanTalk