Parse INI files with OFINIFile

This commit is contained in:
Inori 2019-12-15 05:37:06 -05:00 committed by Inori
parent fbedf4361d
commit ae3cfe9344
10 changed files with 88 additions and 359 deletions

View file

@ -107,9 +107,9 @@ mkxp-z provides limited support for some WinAPI functions that would normally br
* `GetSystemMetrics`: Only supports getting screen width/height.
* `SetCapture`: No-op. Always returns `571`.
* `ReleaseCapture`: No-op.
* `GetPrivateProfileString`: Emulated with MKXP's ini code.
* `GetPrivateProfileString`: Emulated with OFINIFile.
* `GetUserDefaultLangId`: Checks for JP, EN, FR, IT, DE, ES, KO, PT and ZH. Returns English (`0x09`) if the locale can't be determined. Doesn't handle sublanguages.
* `GetUserName`: Returns the `$USER` environment variable, or `Ditto` if it doesn't exist.
* `GetUserName`: *(macOS)* Gets user login name. *(Linux)* Returns the `$USER` environment variable, or `Ditto` if it doesn't exist.
## Nonstandard RGSS extensions

View file

@ -71,7 +71,7 @@ endif
# Objectify our C
global_args += run_command(objfw,'--cppflags').stdout().split()
add_project_arguments(run_command(objfw,'--objcflags').stdout().split(), language:'objc')
add_project_arguments(run_command(objfw,'--objcflags').stdout().split(), language:['objc','objcpp'])
add_project_link_arguments(run_command(objfw,'--libs','--ldflags').stdout().split(), language:['objc','objcpp'])
# Make sure to use ARC

View file

@ -19,114 +19,28 @@
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#import <ObjFW/ObjFW.h>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#import "config.h"
#include <SDL_filesystem.h>
#import <boost/program_options/options_description.hpp>
#import <boost/program_options/parsers.hpp>
#import <boost/program_options/variables_map.hpp>
#include <fstream>
#include <stdint.h>
#import <SDL_filesystem.h>
#include "debugwriter.h"
#include "util.h"
#include "sdl-util.h"
#include "iniconfig.h"
#import <fstream>
#import <stdint.h>
#import "debugwriter.h"
#import "util.h"
#import "sdl-util.h"
#ifdef HAVE_DISCORDSDK
#include <discord_game_sdk.h>
#include "discordstate.h"
#import <discord_game_sdk.h>
#import "discordstate.h"
#endif
#ifdef INI_ENCODING
extern "C" {
#include <libguess.h>
}
#include <iconv.h>
#include <errno.h>
#endif
/* http://stackoverflow.com/a/1031773 */
static bool validUtf8(const char *string)
{
const uint8_t *bytes = (uint8_t*) string;
while(*bytes)
{
if( (/* ASCII
* use bytes[0] <= 0x7F to allow ASCII control characters */
bytes[0] == 0x09 ||
bytes[0] == 0x0A ||
bytes[0] == 0x0D ||
(0x20 <= bytes[0] && bytes[0] <= 0x7E)
)
) {
bytes += 1;
continue;
}
if( (/* non-overlong 2-byte */
(0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF)
)
) {
bytes += 2;
continue;
}
if( (/* excluding overlongs */
bytes[0] == 0xE0 &&
(0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
) ||
(/* straight 3-byte */
((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
bytes[0] == 0xEE ||
bytes[0] == 0xEF) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
) ||
(/* excluding surrogates */
bytes[0] == 0xED &&
(0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF)
)
) {
bytes += 3;
continue;
}
if( (/* planes 1-3 */
bytes[0] == 0xF0 &&
(0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
) ||
(/* planes 4-15 */
(0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
(0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
) ||
(/* plane 16 */
bytes[0] == 0xF4 &&
(0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
(0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
(0x80 <= bytes[3] && bytes[3] <= 0xBF)
)
) {
bytes += 4;
continue;
}
return false;
}
return true;
}
static std::string prefPath(const char *org, const char *app)
{
char *path = SDL_GetPrefPath(org, app);
@ -194,6 +108,7 @@ void Config::read(int argc, char *argv[])
// Not gonna take your shit boost
#define GUARD_ALL( exp ) try { exp } catch(...) {}
#define GUARD_ALL_OBJ( exp ) @try { exp } @catch(...) {}
editor.debug = false;
editor.battleTest = false;
@ -318,6 +233,29 @@ void Config::readGameINI()
return;
}
OFString* iniFilename = [OFString stringWithFormat:@"%s.ini", execName.c_str()];
if ([[OFFileManager defaultManager] fileExistsAtPath:iniFilename])
{
@try{
OFINIFile* iniFile = [OFINIFile fileWithPath:iniFilename];
OFINICategory* iniCat = [iniFile categoryForName:@"Game"];
GUARD_ALL_OBJ( game.title = [[iniCat stringForKey:@"Title" defaultValue:@""] UTF8String]; )
GUARD_ALL_OBJ( game.scripts = [[iniCat stringForKey:@"Scripts" defaultValue:@""] UTF8String]; )
strReplace(game.scripts, '\\', '/');
}
@catch(OFException* exc){
Debug() << "Failed to parse INI:" << [[exc description] UTF8String];
}
if (game.title.empty())
Debug() << [iniFilename UTF8String] << ": Could not find Game.Title property";
if (game.scripts.empty())
Debug() << [iniFilename UTF8String] << ": Could not find Game.Scripts property";
}
/*
std::string iniFilename = execName + ".ini";
SDLRWStream iniFile(iniFilename.c_str(), "r");
@ -350,68 +288,8 @@ void Config::readGameINI()
{
Debug() << "FAILED to open" << iniFilename;
}
*/
#ifdef INI_ENCODING
/* Can add more later */
const char *languages[] =
{
titleLanguage.c_str(),
GUESS_REGION_JP, /* Japanese */
GUESS_REGION_KR, /* Korean */
GUESS_REGION_CN, /* Chinese */
0
};
bool convSuccess = true;
/* Verify that the game title is UTF-8, and if not,
* try to determine the encoding and convert to UTF-8 */
if (!validUtf8(game.title.c_str()))
{
const char *encoding = 0;
convSuccess = false;
for (size_t i = 0; languages[i]; ++i)
{
encoding = libguess_determine_encoding(game.title.c_str(),
game.title.size(),
languages[i]);
if (encoding)
break;
}
if (encoding)
{
iconv_t cd = iconv_open("UTF-8", encoding);
size_t inLen = game.title.size();
size_t outLen = inLen * 4;
std::string buf(outLen, '\0');
char *inPtr = const_cast<char*>(game.title.c_str());
char *outPtr = const_cast<char*>(buf.c_str());
errno = 0;
size_t result = iconv(cd, &inPtr, &inLen, &outPtr, &outLen);
iconv_close(cd);
if (result != (size_t) -1 && errno == 0)
{
buf.resize(buf.size()-outLen);
game.title = buf;
convSuccess = true;
}
}
}
if (!convSuccess)
game.title.clear();
#else
if (!validUtf8(game.title.c_str()))
game.title.clear();
#endif
if (game.title.empty())
{
game.title = "mkxp-z";

View file

@ -16,7 +16,7 @@ typedef unsigned int DWORD, UINT, *LPDWORD;
typedef char BYTE, *LPSTR, *LPCSTR, *LPCTSTR, *LPTSTR, *PBYTE;
typedef short SHORT;
typedef int LONG;
typedef bool BOOL;
typedef signed char BOOL;
typedef void VOID, *LPVOID, *HANDLE, *HMODULE, *HWND;
typedef size_t SIZE_T;

View file

@ -1,19 +1,22 @@
#include <SDL.h>
#import <SDL.h>
#ifdef __APPLE__
#import <Foundation/Foundation.h>
#endif
#import <ObjFW/ObjFW.h>
#ifdef __WIN32__
#include <windows.h>
#include <SDL_syswm.h>
#import <windows.h>
#import <SDL_syswm.h>
#else
#include <cstring>
#include "iniconfig.h"
#import <cstring>
#endif
#include "sharedstate.h"
#include "eventthread.h"
#include "filesystem.h"
#include "input.h"
#include "lang-fun.h"
#include "fake-api.h"
#import "sharedstate.h"
#import "eventthread.h"
#import "filesystem.h"
#import "input.h"
#import "lang-fun.h"
#import "fake-api.h"
// Essentials, without edits, needs Win32API. Two problems with that:
@ -405,23 +408,25 @@ MKXP_GetPrivateProfileString(LPCTSTR lpAppName,
DWORD nSize,
LPCTSTR lpFileName)
{
char *lpFileName_normal = shState->fileSystem().normalize(lpFileName, true, false);
SDLRWStream iniFile(lpFileName_normal, "r");
delete lpFileName_normal;
if (iniFile)
OFString* filePath = [OFString stringWithUTF8String:lpFileName];
OFString* ret = 0;
if ([[OFFileManager defaultManager] fileExistsAtPath:filePath])
{
INIConfiguration ic;
if (ic.load(iniFile.stream()))
{
std::string result = ic.getStringProperty(lpAppName, lpKeyName);
if (!result.empty())
{
strncpy(lpReturnedString, result.c_str(), nSize);
return result.length();
}
@try{
OFINIFile* iniFile = [OFINIFile fileWithPath:filePath];
OFINICategory* iniCat = [iniFile categoryForName:[OFString stringWithUTF8String:lpAppName]];
ret = [iniCat stringForKey:[OFString stringWithUTF8String:lpKeyName]];
}
@catch(...){}
}
if (ret)
{
strncpy(lpReturnedString, [ret UTF8String], nSize);
}
else
{
strncpy(lpReturnedString, lpDefault, nSize);
}
strncpy(lpReturnedString, lpDefault, nSize);
return strlen(lpDefault);
}
@ -464,9 +469,13 @@ PREFABI BOOL
MKXP_GetUserName(LPSTR lpBuffer, LPDWORD pcbBuffer)
{
if (*pcbBuffer < 1) return false;
#ifdef __APPLE__
strncpy(lpBuffer, [NSUserName() UTF8String], *pcbBuffer);
#else
char *username = getenv("USER");
strncpy(lpBuffer, (username) ? username : "ditto", *pcbBuffer);
lpBuffer[0] = toupper(lpBuffer[0]);
#endif
return true;
}

View file

@ -721,10 +721,13 @@ char* FileSystem::normalize(const char *pathname, bool preferred, bool absolute)
if (absolute)
{
OFURL* base = [OFURL fileURLWithPath:[[OFFileManager defaultManager] currentDirectoryPath]];
OFURL* purl = [OFURL fileURLWithPath:str];
OFString* path = [[OFURL URLWithString:[purl string] relativeToURL:base] path];
str = [OFMutableString stringWithString:path];
@try{
OFURL* base = [OFURL fileURLWithPath:[[OFFileManager defaultManager] currentDirectoryPath]];
OFURL* purl = [OFURL fileURLWithPath:str];
OFString* path = [[OFURL URLWithString:[purl string] relativeToURL:base] path];
str = [OFMutableString stringWithString:path];
}
@catch(...){}
}
#ifdef __WIN32__

View file

@ -1,114 +0,0 @@
#include "iniconfig.h"
#include <algorithm>
std::string toLowerCase(const std::string& str)
{
std::string lower = str;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
return lower;
}
std::string trim(const std::string& str, const std::string& chars = "\t\n\v\f\r ")
{
std::string trimmed = str;
trimmed.erase(trimmed.find_last_not_of(chars) + 1);
trimmed.erase(0, trimmed.find_first_not_of(chars));
return trimmed;
}
INIConfiguration::Section::Section (const std::string& sname) : m_Name (sname), m_PropertyMap()
{
}
bool INIConfiguration::Section::getStringProperty (const std::string& name, std::string& outPropStr) const
{
try
{
outPropStr = m_PropertyMap.at(toLowerCase(name)).m_Value;
return true;
}
catch (std::out_of_range& oorexcept)
{
return false;
}
}
bool INIConfiguration::load (std::istream& is)
{
if (!is.good())
{
return false;
}
std::string currSectionName;
std::string line;
std::getline (is, line);
while (!is.eof() && !is.bad())
{
if (line[0] == '[')
{
currSectionName = line.substr (1, line.find_last_of (']') - 1);
}
else if (line[0] != '#' && line.length() > 2)
{
int crloc = line.length() - 1;
if (crloc >= 0 && line[crloc] == '\r') //check for Windows-style newline
line.resize (crloc); //and correct
size_t equalsPos = line.find_first_of ("=");
if (equalsPos != std::string::npos)
{
std::string key = line.substr (0, equalsPos);
std::string val = line.substr (equalsPos + 1);
addProperty (currSectionName, key , val);
}
}
std::getline (is, line);
}
if (is.bad())
{
return false;
}
return true;
}
std::string INIConfiguration::getStringProperty(const std::string& sname, const std::string& name, const std::string& def) const
{
auto sectionIt = m_SectionMap.find(toLowerCase(sname));
if (sectionIt != m_SectionMap.end())
{
std::string prop;
if(sectionIt->second.getStringProperty(name, prop))
{
return prop;
}
}
return def;
}
void INIConfiguration::addProperty (const std::string& sname, const std::string& name, const std::string& val)
{
if (m_SectionMap.find (toLowerCase(sname)) == m_SectionMap.end())
{
m_SectionMap.emplace (toLowerCase(sname), Section (sname));
}
Section::Property p;
p.m_Name = trim(name);
p.m_Value = trim(val);
m_SectionMap.at (toLowerCase(sname)).m_PropertyMap[toLowerCase(p.m_Name)] = p;
}

View file

@ -1,46 +0,0 @@
#ifndef INICONFIG_H
#define INICONFIG_H
#include <iostream>
#include <map>
class INIConfiguration
{
class Section
{
friend class INIConfiguration;
struct Property
{
std::string m_Name;
std::string m_Value;
};
typedef std::map<std::string, Property> property_map;
public:
Section (const Section& s) = default;
Section (Section&& s) = default;
bool getStringProperty (const std::string& name, std::string& outPropStr) const;
private:
explicit Section (const std::string& name);
std::string m_Name;
property_map m_PropertyMap;
};
typedef std::map<std::string, Section> section_map;
public:
bool load (std::istream& inStream);
std::string getStringProperty(const std::string& sname, const std::string& name, const std::string& def = "") const;
protected:
void addProperty (const std::string& sname, const std::string& name, const std::string& val);
private:
section_map m_SectionMap;
};
#endif // INICONFIG_H

View file

@ -41,11 +41,11 @@ NSString* get_mac_locale(void)
NSString* locale_string;
if (country != nil)
locale_string = [NSString stringWithFormat:@"%@_%@", lang, country];
locale_string = [NSString stringWithFormat:[NSString stringWithUTF8String:"%@_%@"], lang, country];
else
locale_string = [NSString stringWithString:lang];
[mac_locale appendFormat:@"%@%@", locale_string, @".UTF-8"];
[mac_locale appendFormat:[NSString stringWithUTF8String:"%@%@"], locale_string, [NSString stringWithUTF8String:".UTF-8"]];
}
return mac_locale;
}

View file

@ -47,7 +47,6 @@ main_source = files(
'filesystem.mm',
'font.cpp',
'input.cpp',
'iniconfig.cpp',
'plane.cpp',
'scene.cpp',
'sprite.cpp',
@ -63,7 +62,7 @@ main_source = files(
'graphics.cpp',
'gl-debug.cpp',
'etc.cpp',
'config.cpp',
'config.mm',
'settingsmenu.cpp',
'keybindings.cpp',
'tileatlas.cpp',
@ -87,7 +86,7 @@ main_source = files(
)
if get_option('use_fakeapi') == true
main_source += files('fake-api.cpp')
main_source += files('fake-api.mm')
endif
if discord == true