219 lines
6.4 KiB
C++
219 lines
6.4 KiB
C++
/*
|
|
* fileio.cpp
|
|
* BoE
|
|
*
|
|
* Created by Celtic Minstrel on 22/04/09.
|
|
*
|
|
*/
|
|
|
|
#include "fileio.hpp"
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <cstdlib>
|
|
#include <boost/filesystem/operations.hpp>
|
|
|
|
#include "fileio/resmgr/res_image.hpp"
|
|
#include "fileio/resmgr/res_cursor.hpp"
|
|
#include "fileio/resmgr/res_dialog.hpp"
|
|
#include "fileio/resmgr/res_font.hpp"
|
|
#include "fileio/resmgr/res_strings.hpp"
|
|
#include "fileio/resmgr/res_sound.hpp"
|
|
|
|
bool mac_is_intel(); // to suppress "no prototype" warning
|
|
bool mac_is_intel(){
|
|
static bool checked_for_intel = false;
|
|
static bool _mac_is_intel;
|
|
if(!checked_for_intel){
|
|
union {uint16_t x; uint8_t c;} endian;
|
|
endian.x = 1;
|
|
_mac_is_intel = endian.c;
|
|
checked_for_intel = true;
|
|
}
|
|
return _mac_is_intel;
|
|
}
|
|
fs::path progDir, tempDir, scenDir, replayDir, saveDir;
|
|
|
|
// This is here to avoid unnecessarily duplicating it in platform-specific files.
|
|
cursor_type Cursor::current = sword_curs;
|
|
|
|
std::filebuf logfile;
|
|
|
|
#if !defined(_WIN32) && !defined(_WIN64) && !defined(__APPLE__)
|
|
static fs::path get_posix_tempdir();
|
|
#endif
|
|
|
|
static void add_resmgr_paths(const fs::path& basePath) {
|
|
ResMgr::graphics.pushPath(basePath/"graphics");
|
|
ResMgr::cursors.pushPath(basePath/"cursors");
|
|
ResMgr::fonts.pushPath(basePath/"fonts");
|
|
ResMgr::strings.pushPath(basePath/"strings");
|
|
ResMgr::sounds.pushPath(basePath/"sounds");
|
|
ResMgr::dialogs.pushPath(basePath/"dialogs");
|
|
}
|
|
|
|
void init_directories(const char* exec_path) {
|
|
progDir = fs::canonical(exec_path);
|
|
#ifdef __APPLE__
|
|
// Need to back up out of the application package
|
|
// We're pointing at .app/Contents/MacOS/exec_name, so back out three steps
|
|
progDir = progDir.parent_path().parent_path().parent_path();
|
|
#endif
|
|
progDir = progDir.parent_path();
|
|
// Initialize the resource manager paths
|
|
add_resmgr_paths(progDir/"data");
|
|
|
|
// We need a location for temporary files, primarily for loading and saving operations
|
|
// The scenario editor may also use this location as "scratch space"
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
tempDir = getenv("APPDATA");
|
|
tempDir /= "Blades of Exile";
|
|
#elif defined(__APPLE__)
|
|
tempDir = getenv("HOME");
|
|
tempDir /= "Library/Application Support/Blades of Exile";
|
|
#else
|
|
tempDir = get_posix_tempdir();
|
|
#endif
|
|
scenDir = tempDir/"Scenarios";
|
|
fs::create_directories(scenDir);
|
|
|
|
replayDir = tempDir/"Replays";
|
|
fs::create_directories(replayDir);
|
|
|
|
saveDir = tempDir/"Saves";
|
|
fs::create_directories(saveDir);
|
|
|
|
add_resmgr_paths(tempDir/"data");
|
|
tempDir /= "Temporary Files";
|
|
|
|
// Depending on the build environment, we may need to redirect stdout and stderr.
|
|
#ifdef _MSC_VER
|
|
#ifdef DEBUG
|
|
void set_debug_buffers();
|
|
set_debug_buffers();
|
|
#else
|
|
std::string logpath = (tempDir.parent_path()/"bladeslog.txt").string();
|
|
logfile.open(logpath.c_str(), std::ios::out);
|
|
std::cout.rdbuf(&logfile);
|
|
std::cerr.rdbuf(&logfile);
|
|
#endif
|
|
std::cout << "Testing cout" << std::endl;
|
|
std::cerr << "Testing cerr" << std::endl;
|
|
sf::err().rdbuf(std::cerr.rdbuf());
|
|
#endif
|
|
// Now print all the paths
|
|
std::cout << "Working directory: " << fs::current_path() << std::endl;
|
|
std::cout << "Program directory: " << progDir << std::endl;
|
|
std::cout << "Scenario directory: " << scenDir << std::endl;
|
|
std::cout << "Temporary directory: " << tempDir << std::endl;
|
|
std::cout << "Replay directory: " << replayDir << std::endl;
|
|
}
|
|
|
|
#if !defined(_WIN32) && !defined(_WIN64) && !defined(__APPLE__)
|
|
fs::path get_posix_tempdir() {
|
|
|
|
fs::path tempdir;
|
|
|
|
const char* xdg_config_dir = std::getenv("XDG_CONFIG_HOME");
|
|
|
|
if(xdg_config_dir != nullptr) {
|
|
tempdir = xdg_config_dir;
|
|
} else {
|
|
// Default to $HOME/.config
|
|
const char* home = std::getenv("HOME");
|
|
|
|
if(home == nullptr)
|
|
throw std::runtime_error { "HOME and XDG_CONFIG_HOME env variables not set!" };
|
|
|
|
tempdir = home;
|
|
tempdir /= ".config";
|
|
}
|
|
|
|
tempdir /= "openboe/blades";
|
|
|
|
return tempdir;
|
|
}
|
|
#endif
|
|
|
|
std::string read_maybe_quoted_string(std::istream& from) {
|
|
std::string result;
|
|
from >> std::ws;
|
|
if(from.peek() == '"' || from.peek() == '\'') {
|
|
char delim = from.get();
|
|
bool reached_end = true;
|
|
do {
|
|
std::string nextPart;
|
|
getline(from, nextPart, delim);
|
|
if(!nextPart.empty() && nextPart.back() == '\\') {
|
|
nextPart.back() = delim;
|
|
reached_end = false;
|
|
} else {
|
|
reached_end = true;
|
|
}
|
|
// Collapse any double backslashes; remove any single backslashes
|
|
for(std::string::iterator iter = nextPart.begin(); iter != nextPart.end(); iter++) {
|
|
if(iter[0] == '\\' && iter + 1 != nextPart.end()) {
|
|
iter = nextPart.erase(iter);
|
|
// After this, iter points to the second of the two backslashes, so
|
|
// when incremented by the loop, it'll point to the character after the backslashes.
|
|
// However! It might also be pointing at an n, t, or f, so substitute that if so.
|
|
switch(*iter) {
|
|
case 'n': *iter = '\n'; break;
|
|
case 't': *iter = '\t'; break;
|
|
case 'f': *iter = '\f'; break;
|
|
}
|
|
}
|
|
}
|
|
// Note that this does not support escaping the single quotes in strings delimited by double quotes, and vice versa.
|
|
result += nextPart;
|
|
} while(!reached_end);
|
|
} else from >> result;
|
|
return result;
|
|
}
|
|
|
|
std::string maybe_quote_string(std::string which, bool force) {
|
|
if(which.empty()) return "''";
|
|
if(force || which.find_first_of(" \t\n\f") != std::string::npos || which[0] == '"' || which[0] == '\'') {
|
|
// The string contains spaces or starts with a quote, so quote it.
|
|
// We may have to escape quotes or backslashes.
|
|
int apos = 0, quot = 0, bslash = 0, newline = 0, formfeed = 0;
|
|
std::for_each(which.begin(), which.end(), [&apos,",&bslash,&newline,&formfeed](char c) {
|
|
if(c == '\'') apos++;
|
|
if(c == '"') quot++;
|
|
if(c == '\\') bslash++;
|
|
if(c == '\n') newline++;
|
|
if(c == '\f') formfeed++;
|
|
});
|
|
char quote_c;
|
|
// Surround it in whichever quote character appears fewer times.
|
|
if(quot < apos) quote_c = '"';
|
|
else quote_c = '\'';
|
|
// Let's create this string to initially have the required size.
|
|
std::string temp;
|
|
size_t quoted_len = which.length() + std::min(quot,apos) + bslash + newline + formfeed + 2;
|
|
temp.reserve(quoted_len);
|
|
temp += quote_c;
|
|
for(size_t i = 0; i < which.length(); i++) {
|
|
if(which[i] == quote_c) {
|
|
temp += '\\';
|
|
temp += quote_c;
|
|
} else if(which[i] == '\\') {
|
|
temp += R"(\\)";
|
|
} else if(which[i] == '\n') {
|
|
temp += R"(\n)";
|
|
} else if(which[i] == '\f') {
|
|
temp += R"(\f)";
|
|
}
|
|
else temp += which[i];
|
|
}
|
|
temp += quote_c;
|
|
which.swap(temp);
|
|
}
|
|
return which;
|
|
}
|
|
|
|
std::ostream& std_fmterr(std::ostream& out) {
|
|
return out << strerror(errno);
|
|
}
|