// **********************************************************************************************************************
//
// Copyright (c)2011, YoYo Games Ltd. All Rights reserved.
//
// File: yyIniFile.js
// Created: 11/07/2011
// Author: Mike
// Project: GameMaker HTML5
// Description: Given a string, creates an INI file structure.
//
// Date Version BY Comment
// ----------------------------------------------------------------------------------------------------------------------
// 11/07/2011 V1.0 MJD Ported from the C++ version.
//
// **********************************************************************************************************************
// @if function("ini_*")
var g_LastFileSize = 0,
g_LastErrorStatus = 0;
// #############################################################################################
/// Function:
/// Create a new INI file "object"
///
///
/// Out:
/// The "empty" object
///
// #############################################################################################
/**@constructor*/
function yyIniFile( _pName )
{
this.m_Changed = false;
this.m_Keys = [];
this.m_pFileName = _pName;
this.m_pFileBuffer = "";
this.m_FileIndex = 0;
this.m_LineNumber = 0;
}
// #############################################################################################
/// Function:
/// Create an INI file handler from a string
///
///
/// In:
string holding the whole ini file
///
// #############################################################################################
function INI_OpenFromString(_content)
{
var pIniFile = new yyIniFile(null);
pIniFile.m_pFileBuffer = _content;
pIniFile.ReadIniFile();
var count = 0;
for (var i in pIniFile.m_Keys) {
if (!pIniFile.m_Keys.hasOwnProperty(i)) continue;
count++;
break;
}
//if (count == 0) return null; // could be an empty ini file.
return pIniFile;
}
// #############################################################################################
/// Function:
/// Create an ini file class
///
///
/// In:
Name of the ini file
///
// #############################################################################################
function INI_OpenIniFile(_FileName, _fLocal)
{
var pFile = LoadTextFile_Block( _FileName, _fLocal );
var pIniFile = new yyIniFile(_FileName);
pIniFile.m_pFileBuffer = pFile;
pIniFile.ReadIniFile();
var count=0;
for (var i in pIniFile.m_Keys) {
if (!pIniFile.m_Keys.hasOwnProperty(i)) continue;
count++;
break;
}
if( count==0 ) return null;
return pIniFile;
}
// #############################################################################################
/// Function:
/// Move to AFTER the newline
///
// #############################################################################################
yyIniFile.prototype.NextLine = function () {
while ((this.m_pFileBuffer.charCodeAt(this.m_FileIndex) != 0x0a) && (this.m_pFileBuffer.charCodeAt(this.m_FileIndex) != 0x0d) && (this.m_FileIndex = this.m_Size) return;
// Now check or the second part of the new line...
if ((this.m_pFileBuffer.charCodeAt(this.m_FileIndex) == 0x0a) && (this.m_pFileBuffer.charCodeAt(this.m_FileIndex) == 0x0d))
{
this.m_FileIndex++; // Skip 0x0a or 0x0d
}
};
// #############################################################################################
/// Function:
/// Is the next character a whitespace character? (includes comments etc..)
///
///
/// Out:
/// TRUE for yes, FALSE for no.
///
// #############################################################################################
yyIniFile.prototype.IsWhiteSpace = function () {
//with (this)
{
if (this.m_FileIndex >= this.m_Size) return false;
var c = this.m_pFileBuffer.charCodeAt(this.m_FileIndex);
if (c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d || c == ord('#') || c == ord(';')) // GM ini files don't support comments AT ALL!
{
return true;
} else
{
return false;
}
}
};
// #############################################################################################
/// Function:
/// Skip all whitespace (including newlines and comments)
///
// #############################################################################################
yyIniFile.prototype.SkipWhiteSpace = function () {
//with (this)
{
// Skip whitespace (and newlines/comments etc.)
while (this.IsWhiteSpace() && (this.m_FileIndex = this.m_Size) return;
}
};
// #############################################################################################
/// Function:
/// Find the next section
///
///
/// Out:
/// If not found, return NULL
///
// #############################################################################################
yyIniFile.prototype.GetSection = function () {
//with (this)
{
this.SkipWhiteSpace();
// Scan for a "[" which will hold the section
while ((this.m_pFileBuffer.charAt(this.m_FileIndex) != '[') && (this.m_FileIndex = this.m_Size) return null;
// Remember string start, but skip '['
this.m_FileIndex++;
var StartIndex = this.m_FileIndex;
// Scan for a "]" which will hold the section
while ((this.m_pFileBuffer.charAt(this.m_FileIndex) != ']') && (this.m_FileIndex = this.m_Size) return null;
// Make a new section.
var len = this.m_FileIndex - StartIndex;
var pSection = [];
pSection.__m_pIniFileName__ = this.m_pFileBuffer.substr(StartIndex, len);
this.m_FileIndex++; // Skip white space
return pSection;
}
};
// #############################################################################################
/// Function:
/// Find the next section
///
///
/// Out:
/// If not found, return NULL
///
// #############################################################################################
yyIniFile.prototype.GetKey = function (_Section) {
//with (this)
{
this.SkipWhiteSpace();
if (this.m_FileIndex >= this.m_Size || this.m_pFileBuffer.charAt(this.m_FileIndex) == '[') return false;
// Remember string start
var StartIndex = this.m_FileIndex;
// Scan past the KEY (get it's length)
var LastWhiteSpace = -1;
while ((this.m_pFileBuffer.charAt(this.m_FileIndex) != '=') && (this.m_FileIndex = this.m_Size) return false;
if (LastWhiteSpace = this.m_Size) return false;
this.m_FileIndex++; // Skip '='
//
// Now read the VALUE. First skip the white space before the vlaue, but make sure we dont go onto a newline
//
var line = this.m_LineNumber;
this.SkipWhiteSpace();
if (line != this.m_LineNumber) return false;
// Now read to the end of the line (or comment character)
var comment1 = ord('#');
var comment2 = ord(';');
var instring = false;
var cc = this.m_pFileBuffer.charCodeAt(this.m_FileIndex);
switch (cc) {
case 34/* '"'.code */:
case 39/* "'".code */:
// Are we IN a string? If so use the string character as the EOL
comment1 = cc;
comment2 = cc;
instring = true;
this.m_FileIndex++;
break;
case 91/* "[".code */:
case 123/* "{".code */:
// Since we don't support escape characters and JSON can contain
// arbitrary combinations of non-new-line chars, assume that a line
// with JSON isn't going to also have a comment at the end of it
// (as if comments after values aren't rare enough as-is)
comment1 = -1;
comment2 = -1;
break;
}
StartIndex = this.m_FileIndex;
// Now read to the end of the line (or comment character)
var LastWhiteSpaceChar = -1;
var c = this.m_pFileBuffer.charCodeAt(this.m_FileIndex);
while ((c != 0x0a) && (c != 0x0d) && (c != comment1) && (c != comment2) && (this.m_FileIndex = 0 && !instring)
{
len = LastWhiteSpaceChar - StartIndex;
} else
{
len = this.m_FileIndex - StartIndex;
}
var pValue = this.m_pFileBuffer.substr(StartIndex, len);
_Section[pKey] = pValue;
// IF we were in a string, get the end of line
if (instring)
{
cc = this.m_pFileBuffer.charCodeAt(this.m_FileIndex);
if ((cc == comment1) && (cc == comment2))
{
while ((this.m_pFileBuffer.charCodeAt(this.m_FileIndex) != 0x0a) && (this.m_pFileBuffer.charCodeAt(this.m_FileIndex) != 0x0d) && (this.m_FileIndex
/// Load an INI file into memory
///
///
/// In: Name of INI file
///
/// Out:
///
///
// #############################################################################################
yyIniFile.prototype.ReadIniFile = function () {
//with (this)
{
if (this.m_pFileBuffer == null) return false;
this.m_Size = this.m_pFileBuffer.length;
this.m_FileIndex = 0;
this.m_LineNumber = 0;
// First, get the first section so we can head into the loop ready.
var pSection = this.GetSection();
if (pSection == null)
{
this.m_pFileBuffer = null;
return false;
}
this.m_Keys[pSection.__m_pIniFileName__] = pSection;
// Now read in the INI file
while (this.m_FileIndex
/// Given the section + key, retrun the key container.
///
///
/// In: Section name
/// Key to retrieve
/// Out:
/// the key container, or NULL for not found
///
// #############################################################################################
yyIniFile.prototype.FindKey = function (_pSectionName, _pKeyName) {
//with (this)
{
var pSection = this.m_Keys[_pSectionName];
if (pSection != null && pSection != undefined)
{
var pValue = pSection[_pKeyName];
if (pValue != undefined) return pValue; // also returns NULL if it's been deleted.
}
return null;
}
};
// #############################################################################################
/// Function:
/// Given the section + key, retrun the INT it holds - or the default if not found
///
///
/// In: Section name
/// Key to retrieve
/// Out:
/// the INT it holds, or 0 for not found (or user supplied value)
///
// #############################################################################################
yyIniFile.prototype.ReadInt = function (_pSectionName, _pKeyName, _default) {
//with (this)
{
var pKey = this.FindKey(_pSectionName, _pKeyName);
if (pKey != null)
{
return this.parseInt(pKey, 10);
} else
{
return _default;
}
}
};
// #############################################################################################
/// Function:
/// Given the section + key, retrun the INT it holds - or the default if not found
///
///
/// In: Section name
/// Key to retrieve
/// Out:
/// the INT it holds, or 0.0f for not found (or user supplied value)
///
// #############################################################################################
yyIniFile.prototype.ReadFloat = function (_pSectionName, _pKeyName, _default) {
//with (this)
{
var pKey = this.FindKey(_pSectionName, _pKeyName);
if (pKey != null)
{
return parseFloat(pKey);
} else
{
return parseFloat(_default);
}
}
};
// #############################################################################################
/// Function:
/// Given the section + key, retrun the INT it holds - or the default if not found
///
///
/// In: Section name
/// Key to retrieve
/// Out:
/// the STRING it holds, or ""for not found (or user supplied value)
///
// #############################################################################################
yyIniFile.prototype.ReadString = function(_pSectionName, _pKeyName, _default)
{
//with (this)
{
var pKey = this.FindKey(_pSectionName, _pKeyName);
if (pKey != null)
{
return pKey;
} else
{
return _default;
}
}
};
// #############################################################################################
/// Function:
/// Set a KEY value.
///
///
/// In: Section name
/// KEY name
///
/// Out:
///
///
// #############################################################################################
yyIniFile.prototype.SetKey = function (_pSectionName, _pKeyName, _pValue)
{
this.m_Changed = true;
var pSection = this.m_Keys[_pSectionName];
if (pSection == null || pSection == undefined)
{
pSection = [];
pSection.__m_pIniFileName__ = _pSectionName;
this.m_Keys[pSection.__m_pIniFileName__] = pSection;
}
pSection[_pKeyName] = _pValue;
return true;
};
// #############################################################################################
/// Function:
/// delete a key.
///
///
/// In:
///
/// Out:
/// true for done, false for not found.
///
// #############################################################################################
yyIniFile.prototype.DeleteKey = function(_pSectionName, _pKeyName)
{
var pSection = this.m_Keys[_pSectionName];
if (pSection == null || pSection == undefined) return false;
var pKey = pSection[_pKeyName];
if (pKey == null || pKey == undefined) return false;
this.m_Changed = true;
pSection[_pKeyName] = null;
return true;
};
// #############################################################################################
/// Function:
/// Delete a whole section (and the keys)
///
///
/// In:
/// Out:
/// true for done, false for not found.
///
// #############################################################################################
yyIniFile.prototype.DeleteSection = function (_pSectionName) {
var pSection = this.m_Keys[_pSectionName];
if (pSection == null || pSection == undefined) return false;
this.m_Changed = true;
this.m_Keys[_pSectionName] = null;
return true;
};
// #############################################################################################
/// Function:
/// Save out the INI file.
///
///
/// Out:
///
///
// #############################################################################################
yyIniFile.prototype.WriteIniFile = function() {
var pFile = "";
var newline = chr(0x0d) + chr(0x0a);
for (var section in this.m_Keys) {
if (!this.m_Keys.hasOwnProperty(section)) continue;
pFile = pFile + "[" + section + "]" + newline;
var pSection = this.m_Keys[section];
for (var key in pSection) {
if (!pSection.hasOwnProperty(key)) continue;
if (key != "__m_pIniFileName__") {
var pValue = pSection[key];
if (pValue != null)
{
if (pValue.indexOf('"')
/// Check to see if the working directory has been added to the filename already,
/// And if not, then add it.
///
///
/// In: Filename
/// Out:
/// The filename WITH working_directory added
///
// #############################################################################################
function CheckWorkingDirectory(_FileName) {
if (_FileName.substring(0, 5) == "file:") return _FileName;
if (_FileName.substring(0, 5) == "data:") return _FileName;
if ((_FileName.substring(0, 7) == "http://") || (_FileName.substring(0, 8) == "https://")) return _FileName;
// Working directory already appended?
if (_FileName.substring(0, g_RootDir.length) == g_RootDir) return set_load_location(null,null,_FileName);
return set_load_location(null,null,g_RootDir + _FileName);
}
// #############################################################################################
/// Function:
/// Check the URL and see if it's a valid type.
/// And if not, then add it.
///
///
/// In: Filename
/// Out:
/// true for okay, false for not.
///
// #############################################################################################
function CheckValidURL(_FileName) {
if (_FileName.substring(0, 5) == "file:") return false;
if (_FileName.substring(0, 4) == "ftp:") return false;
if (_FileName.substring(0, 7) == "gopher:") return false;
if (_FileName.substring(0, 7) == "mailto:") return false;
if (_FileName.substring(0, 5) == "news:") return false;
if (_FileName.substring(0, 5) == "nntp:") return false;
if (_FileName.substring(0, 7) == "telnet:") return false;
if (_FileName.substring(0, 5) == "wais:") return false;
if (_FileName.substring(0, 5) == "news:") return false;
if (_FileName.substring(1, 1) == ":") return false; // probably c: style
return true;
}
// #############################################################################################
/// Function:
/// Raw "get size" of file. Can also do file_exists etc.
///
///
/// In:
/// Out:
///
///
// #############################################################################################
function RawFileExists(_url) {
try
{
var http = new XMLHttpRequest();
http.open('HEAD', _url, false);
http.send();
g_LastErrorStatus = http.status;
var bExists = (http.status != 404 && http.status !=0);
return bExists;
} catch (e)
{
return false;
}
}
// #############################################################################################
/// Function:
/// Read/Write a BINARY file from a server
///
///
/// In: file or URL to access
/// false for GET, true for POST
/// Out:
/// the file, or null if an error occured.
///
// #############################################################################################
function RawServerReadWrite(U, V)
{
try{
var X = !window.XMLHttpRequest ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
X.open(V ? 'PUT' : 'GET', U, false );
// RK :: This is using a trick to download the file as text and then avoid the encoding changing the data see
// https://web.archive.org/web/20071103070418/http://mgran.blogspot.com/2006/08/downloading-binary-streams-with.html
X.overrideMimeType('text\/plain; charset=x-user-defined');
X.send(V ? V : '');
g_LastErrorStatus = X.status;
return X.responseText;
}catch(e){
return null;
}
}
// #############################################################################################
/// Function:
/// Read/Write a TEXT file from a server
///
///
/// In: file or URL to access
/// false for GET, true for POST
/// Out:
/// the file, or null if an error occured.
///
// #############################################################################################
function TextServerReadWrite(U, V)
{
try{
var X = !window.XMLHttpRequest ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
X.open(V ? 'PUT' : 'GET', U, false );
X.send(V ? V : '');
g_LastErrorStatus = X.status;
return X.responseText;
}catch(e){
return null;
}
}
// #############################################################################################
/// Function:
/// Try and delete a file from a server
///
///
/// In:
/// Out:
/// true for okay, false for error.
///
// #############################################################################################
function RawServerDelete(U)
{
try{
var X = !window.XMLHttpRequest ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
X.open("DELETE", U, false );
X.send('');
g_LastErrorStatus = X.status;
return X.status;
}catch(e){
return false;
}
}
// #############################################################################################
/// Function:
/// Save a TEXT file to local storage
///
///
/// In:
///
/// Out:
///
///
// #############################################################################################
function SaveTextFile_Block(_filename, _pFile)
{
// Write to local storage
if (g_ChromeStore) {
/*var s = g_pBuiltIn.local_storage + _filename;
chrome.storage.local.set({ s : _pFile }, function() {
//console.log('Settings saved: ' + _pFile);
});*/
return false;
}
else if (g_SupportsLocalStorage) {
try {
window.localStorage[GetLocalStorageName(_filename)] = _pFile;
return true;
}
catch (ex) {
return false;
}
}
}
// #############################################################################################
/// Function:
/// Load a TEXT file from local, OR remote (blocking)
///
///
/// In:
///
/// Out:
///
///
// #############################################################################################
function LoadTextFile_Block( _FileName, _fLocal )
{
return LoadXXXFile_Block( _FileName, _fLocal, TextServerReadWrite );
}
// #############################################################################################
/// Function:
/// Load a BINARY file from local, OR remote (blocking)
///
///
/// In:
///
/// Out:
///
///
// #############################################################################################
function LoadBinaryFile_Block( _FileName, _fLocal )
{
return LoadXXXFile_Block( _FileName, _fLocal, RawServerReadWrite );
}
function LoadXXXFile_Block( _FileName, _fLocal, _ServerReadWriteFunc )
{
var pFile = null;
if (_FileName.substring(0, 5) == "file:") return null;
if (_fLocal)
{
if ((_FileName.substring(0, 7) == "http://") || (_FileName.substring(0, 8) == "https://")) return; // NOT a local file!
// Chrome store does NOT support localStorage, so use file API instead.
if (g_ChromeStore)
{
return null;
}
else if (g_SupportsLocalStorage) {
try {
pFile = window.localStorage[GetLocalStorageName(_FileName)];
}
catch (ex) {
return null;
}
if ( (pFile == undefined) || (pFile==null) ) return null;
}
}
else
{
if (!CheckValidURL(_FileName)) return null;
_FileName = CheckWorkingDirectory(_FileName);
pFile = _ServerReadWriteFunc(_FileName, false);
if( ( pFile ==null ) || (pFile==undefined ) ) return null;
if (g_LastErrorStatus == 404) return null;
}
return pFile;
}
// #############################################################################################
/// Function:
/// Load a TEXT file from local, OR remote (blocking)
///
///
/// In:
///
/// Out:
///
///
// #############################################################################################
function FileExists_Block(_FileName, _fLocal) {
var pFile = null;
if (_FileName.substring(0, 5) == "file:") return null;
if (_fLocal) {
if (g_ChromeStore) {
return false;
}
else if (g_SupportsLocalStorage) {
try {
var name = GetLocalStorageName(_FileName);
// We should consider empty files (empty strings) has "existing".
if (window.localStorage[name] !== undefined) {
return true;
}
return false;
}
catch (ex) {
return false;
}
}
}
else {
if (!CheckValidURL(_FileName)) {
return false;
}
_FileName = CheckWorkingDirectory(_FileName);
return RawFileExists(_FileName);
}
}