From 981f8b3acf7e76f27c02f4ced80d55b424cc7497 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 3 Feb 2013 12:49:25 +0100 Subject: initial commit to mercurial repository. Corresponds to MO version 0.12.6 --- src/shared/appconfig.cpp | 30 ++ src/shared/appconfig.h | 36 ++ src/shared/appconfig.inc | 12 + src/shared/directoryentry.cpp | 799 ++++++++++++++++++++++++++++++++++++++++++ src/shared/directoryentry.h | 313 +++++++++++++++++ src/shared/error_report.cpp | 99 ++++++ src/shared/error_report.h | 47 +++ src/shared/fallout3info.cpp | 244 +++++++++++++ src/shared/fallout3info.h | 94 +++++ src/shared/falloutnvinfo.cpp | 246 +++++++++++++ src/shared/falloutnvinfo.h | 95 +++++ src/shared/gameinfo.cpp | 204 +++++++++++ src/shared/gameinfo.h | 187 ++++++++++ src/shared/inject.cpp | 142 ++++++++ src/shared/inject.h | 27 ++ src/shared/oblivioninfo.cpp | 252 +++++++++++++ src/shared/oblivioninfo.h | 92 +++++ src/shared/shared.pro | 56 +++ src/shared/skyriminfo.cpp | 278 +++++++++++++++ src/shared/skyriminfo.h | 100 ++++++ src/shared/util.cpp | 119 +++++++ src/shared/util.h | 46 +++ src/shared/windows_error.cpp | 45 +++ src/shared/windows_error.h | 40 +++ 24 files changed, 3603 insertions(+) create mode 100644 src/shared/appconfig.cpp create mode 100644 src/shared/appconfig.h create mode 100644 src/shared/appconfig.inc create mode 100644 src/shared/directoryentry.cpp create mode 100644 src/shared/directoryentry.h create mode 100644 src/shared/error_report.cpp create mode 100644 src/shared/error_report.h create mode 100644 src/shared/fallout3info.cpp create mode 100644 src/shared/fallout3info.h create mode 100644 src/shared/falloutnvinfo.cpp create mode 100644 src/shared/falloutnvinfo.h create mode 100644 src/shared/gameinfo.cpp create mode 100644 src/shared/gameinfo.h create mode 100644 src/shared/inject.cpp create mode 100644 src/shared/inject.h create mode 100644 src/shared/oblivioninfo.cpp create mode 100644 src/shared/oblivioninfo.h create mode 100644 src/shared/shared.pro create mode 100644 src/shared/skyriminfo.cpp create mode 100644 src/shared/skyriminfo.h create mode 100644 src/shared/util.cpp create mode 100644 src/shared/util.h create mode 100644 src/shared/windows_error.cpp create mode 100644 src/shared/windows_error.h (limited to 'src/shared') diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp new file mode 100644 index 00000000..0d933d0a --- /dev/null +++ b/src/shared/appconfig.cpp @@ -0,0 +1,30 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "appconfig.h" + +namespace AppConfig { + +#define PARWSTRING wstring +#define APPPARAM(partype, parid, value) partype parid ## () { return value; } +#include "appconfig.inc" +#undef PARWSTRING +#undef APPPARAM + +} diff --git a/src/shared/appconfig.h b/src/shared/appconfig.h new file mode 100644 index 00000000..b10ecc3c --- /dev/null +++ b/src/shared/appconfig.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef APPCONFIG_H +#define APPCONFIG_H + +#include + +namespace AppConfig { + + +#define PARWSTRING wstring +#define APPPARAM(partype, parid, value) partype parid ## (); +#include "appconfig.inc" +#undef PARWSTRING +#undef APPPARAM + +} + +#endif // APPCONFIG_H diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc new file mode 100644 index 00000000..7710925a --- /dev/null +++ b/src/shared/appconfig.inc @@ -0,0 +1,12 @@ +APPPARAM(std::wstring, translationPrefix, L"organizer") +APPPARAM(std::wstring, pluginPath, L"plugins") +APPPARAM(std::wstring, stylesheetsPath, L"stylesheets") +APPPARAM(std::wstring, logFile, L"ModOrganizer.log") +APPPARAM(std::wstring, proxyDLLTarget, L"steam_api.dll") +APPPARAM(std::wstring, proxyDLLOrig, L"steam_api_orig.dll") // needs to be identical to the value used in proxydll-project +APPPARAM(std::wstring, proxyDLLSource, L"proxy.dll") +APPPARAM(std::wstring, hookDLLName, L"hook.dll") // needs to be identical to the value used in proxydll-project +APPPARAM(const wchar_t*, localSavePlaceholder, L"__MOProfileSave__\\") + +APPPARAM(std::wstring, firstStepsTutorial, L"tutorial_firststeps_main.js") +APPPARAM(int, numLogFiles, 5) // number of log files to keep diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp new file mode 100644 index 00000000..748ead99 --- /dev/null +++ b/src/shared/directoryentry.cpp @@ -0,0 +1,799 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "directoryentry.h" +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include "error_report.h" +#include "util.h" +#include "windows_error.h" +#include + + + +class OriginConnection { + +public: + + typedef int Index; + static const int INVALID_INDEX = INT_MIN; + +public: + + OriginConnection() + : m_NextID(0) + {} + + FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection) { + int newID = createID(); + m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection); + m_OriginsNameMap[originName] = newID; + m_OriginsPriorityMap[priority] = newID; + return m_Origins[newID]; + } + + bool exists(const std::wstring &name) { + std::map::iterator iter = m_OriginsNameMap.find(name); + return iter != m_OriginsNameMap.end(); + } + + FilesOrigin &getByID(Index ID) { + return m_Origins[ID]; + } + + FilesOrigin &getByName(const std::wstring &name) { + std::map::iterator iter = m_OriginsNameMap.find(name); + if (iter != m_OriginsNameMap.end()) { + return m_Origins[iter->second]; + } else { + std::ostringstream stream; + stream << "invalid origin name: " << ToString(name, false); + throw std::runtime_error(stream.str()); + } + } + + void changePriorityLookup(int oldPriority, int newPriority) + { + auto iter = m_OriginsPriorityMap.find(oldPriority); + if (iter != m_OriginsPriorityMap.end()) { + Index idx = iter->second; + m_OriginsPriorityMap.erase(iter); + m_OriginsPriorityMap[newPriority] = idx; + } + } + + void changeNameLookup(const std::wstring &oldName, const std::wstring &newName) + { + auto iter = m_OriginsNameMap.find(oldName); + if (iter != m_OriginsNameMap.end()) { + Index idx = iter->second; + m_OriginsNameMap.erase(iter); + m_OriginsNameMap[newName] = idx; + } else { + log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); + } + } + +private: + + Index createID() { + return m_NextID++; + } + +private: + + Index m_NextID; + + std::map m_Origins; + std::map m_OriginsNameMap; + std::map m_OriginsPriorityMap; + +}; + + +// +// FilesOrigin +// + + +void FilesOrigin::enable(bool enabled) +{ + if (!enabled) { + std::set copy = m_Files; + for (auto iter = copy.begin(); iter != copy.end(); ++iter) { + m_FileRegister->removeOrigin(*iter, m_ID); + } + m_Files.clear(); + } + m_Disabled = !enabled; +} + + +void FilesOrigin::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + m_Files.erase(iter); + } +} + + + +std::wstring tail(const std::wstring &source, const size_t count) +{ + if (count >= source.length()) { + return source; + } + + return source.substr(source.length() - count); +} + + +void FilesOrigin::setPriority(int priority) +{ + m_OriginConnection->changePriorityLookup(m_Priority, priority); + + m_Priority = priority; +} + + +void FilesOrigin::setName(const std::wstring &name) +{ + m_OriginConnection->changeNameLookup(m_Name, name); + // change path too + if (tail(m_Path, m_Name.length()) == m_Name) { + m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); + } + m_Name = name; +} + +std::vector FilesOrigin::getFiles() const +{ + std::vector result; + + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + result.push_back(m_FileRegister->getFile(*iter)); + } + + return result; +} + + + +// +// FileEntry +// + +void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive) +{ + if (m_Origin == -1) { + m_Origin = origin; + m_FileTime = fileTime; + m_Archive = archive; +// } else if (FilesOrigin::getByID(origin).getPriority() > FilesOrigin::getByID(m_Origin).getPriority()) { + } else if (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) { + if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) { + m_Alternatives.push_back(m_Origin); + } + m_Origin = origin; + m_FileTime = fileTime; + m_Archive = archive; + } else { + bool found = false; + if (m_Origin == origin) { + // already an origin + return; + } + for (std::vector::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + if (*iter == origin) { + // already an origin + return; + } + if (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority()) { + m_Alternatives.insert(iter, origin); + found = true; + break; + } + } + if (!found) { + m_Alternatives.push_back(origin); + } + } +} + +bool FileEntry::removeOrigin(int origin) +{ + if (m_Origin == origin) { + if (!m_Alternatives.empty()) { + // find alternative with the highest priority + std::vector::iterator currentIter = m_Alternatives.begin(); + for (std::vector::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) && + (*iter != origin)) { + currentIter = iter; + } + } + int currentID = *currentIter; + m_Alternatives.erase(currentIter); + + m_Origin = currentID; + + // now we need to update the file time... + std::wstring filePath = getFullPath(); + HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (!::GetFileTime(file, NULL, NULL, &m_FileTime)) { + // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh + // the view to find out + m_Archive = L"bsa?"; + } else { + m_Archive = L""; + } + + ::CloseHandle(file); + + } else { + m_Origin = -1; + return true; + } + } else { + std::vector::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin); + m_Alternatives.erase(newEnd, m_Alternatives.end()); + } + return false; +} + + +// sorted by priority descending +static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS) +{ + return entry->getOriginByID(LHS).getPriority() < entry->getOriginByID(RHS).getPriority(); +} + + +FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) + : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L"") +{ +} + + +void FileEntry::sortOrigins() +{ + m_Alternatives.push_back(m_Origin); + std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2)); + m_Origin = m_Alternatives[m_Alternatives.size() - 1]; + m_Alternatives.pop_back(); +} + + +bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const +{ + if (parent == NULL) { + return false; + } else { + // don't append the topmost parent because it is the virtual data-root + if (recurseParents(path, parent->getParent())) { + path.append(L"\\").append(parent->getName()); + } + return true; + } +} + +std::wstring FileEntry::getFullPath() const +{ + std::wstring result; + bool ignore = false; + result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin + recurseParents(result, m_Parent); // all intermediate directories + result.append(L"\\").append(m_Name); // the actual filename + return result; +} + +std::wstring FileEntry::getRelativePath() const +{ + std::wstring result; + recurseParents(result, m_Parent); // all intermediate directories + result.append(L"\\").append(m_Name); // the actual filename + return result; +} + + + + + +// +// DirectoryEntry +// +DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID) + : m_OriginConnection(new OriginConnection), + m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID) +{ + m_FileRegister.reset(new FileRegister(m_OriginConnection)); +} + +DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection) + : m_FileRegister(fileRegister), m_OriginConnection(originConnection), + m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID) +{} + + +DirectoryEntry::~DirectoryEntry() +{ + clear(); +} + + +const std::wstring &DirectoryEntry::getName() const +{ + return m_Name; +} + + +void DirectoryEntry::clear() +{ + m_Files.clear(); + for (std::vector::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + delete *iter; + } + m_SubDirectories.clear(); +} + + +FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority) +{ + if (m_OriginConnection->exists(originName)) { + FilesOrigin &origin = m_OriginConnection->getByName(originName); + origin.enable(true); + return origin; + } else { + return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection); + } +} + + +void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority) +{ + FilesOrigin &origin = createOrigin(originName, directory, priority); + wchar_t *buffer = new wchar_t[MAXPATH_UNICODE + 1]; + memset(buffer, L'\0', MAXPATH_UNICODE + 1); + try { + int offset = _snwprintf(buffer, MAXPATH_UNICODE, L"%ls", directory.c_str()); + addFiles(origin, buffer, offset); + } catch (...) { + delete [] buffer; + buffer = NULL; + } + delete [] buffer; + m_Populated = true; +} + + +void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority) +{ + FilesOrigin &origin = createOrigin(originName, directory, priority); + + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { + throw windows_error("failed to determine file time"); + } + + BSA::Archive archive; + BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str()); + if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { + std::ostringstream stream; + stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); + throw std::runtime_error(stream.str()); + } + size_t namePos = fileName.find_last_of(L"\\/"); + if (namePos == std::wstring::npos) { + namePos = 0; + } else { + ++namePos; + } + + addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos)); + m_Populated = true; +} + + +void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset) +{ + WIN32_FIND_DATAW findData; + + _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, L"\\*"); + HANDLE searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE); + if (searchHandle != INVALID_HANDLE_VALUE) { + BOOL result = true; + while (result) { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if ((wcscmp(findData.cFileName, L".") != 0) && + (wcscmp(findData.cFileName, L"..") != 0)) { + int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); + // recurse into subdirectories + getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset); + } + } else { + insert(findData.cFileName, origin, findData.ftLastWriteTime, L""); + } + result = ::FindNextFileW(searchHandle, &findData); + } + } + ::FindClose(searchHandle); +} + + +void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName) +{ + // add files + for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { + BSA::File::Ptr file = archiveFolder->getFile(fileIdx); + insert(ToWString(file->getName(), true), origin, fileTime, archiveName); + } + + // recurse into subdirectories + for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) { + BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx); + DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID()); + + folderEntry->addFiles(origin, folder, fileTime, archiveName); + } +} + + +void DirectoryEntry::removeFile(const std::wstring &filePath, int *origin) +{ + size_t pos = filePath.find_first_of(L"\\/"); + if (pos == std::string::npos) { + this->remove(filePath, origin); + } else { + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + if (entry != NULL) { + entry->removeFile(rest, origin); + } + } +} + +void DirectoryEntry::removeDirRecursive() +{ + while (!m_Files.empty()) { + m_FileRegister->removeFile(m_Files.begin()->second); + } + + for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + (*iter)->removeDirRecursive(); + } + m_SubDirectories.clear(); +} + +void DirectoryEntry::removeDir(const std::wstring &path) +{ + size_t pos = path.find_first_of(L"\\/"); + if (pos == std::string::npos) { + for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + if (_wcsicmp((*iter)->getName().c_str(), path.c_str()) == 0) { + (*iter)->removeDirRecursive(); + m_SubDirectories.erase(iter); + break; + } + } + } else { + std::wstring dirName = path.substr(0, pos); + std::wstring rest = path.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + if (entry != NULL) { + entry->removeDir(rest); + } + } +} + + +void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime) +{ + size_t pos = filePath.find_first_of(L"\\/"); + if (pos == std::string::npos) { + this->insert(filePath, origin, fileTime, L""); + } else { + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime); + } +} + + +void DirectoryEntry::removeFile(FileEntry::Index index) +{ + if (m_Files.size() != 0) { + auto iter = std::find_if(m_Files.begin(), m_Files.end(), + [&index](const std::pair &iter) -> bool { + return iter.second == index; } ); + if (iter != m_Files.end()) { + m_Files.erase(iter); + } else { + log("file \"%ls\" not in directory \"%ls\"", + m_FileRegister->getFile(index)->getName().c_str(), + this->getName().c_str()); + } + } else { + log("file \"%ls\" not in directory \"%ls\", directory empty", + m_FileRegister->getFile(index)->getName().c_str(), + this->getName().c_str()); + } +} + + +int DirectoryEntry::anyOrigin() const +{ + bool ignore; + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntry *entry = m_FileRegister->getFile(iter->second); + if (!entry->isFromArchive()) { + return entry->getOrigin(ignore); + } + } + + // if we got here, no file directly within this directory is a valid indicator for a mod, thus + // we continue looking in subdirectories + for (std::vector::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + int res = (*iter)->anyOrigin(); + if (res != -1){ + return res; + } + } + return m_Origin; +} + + +bool DirectoryEntry::originExists(const std::wstring &name) const +{ + return m_OriginConnection->exists(name); +} + + +FilesOrigin &DirectoryEntry::getOriginByID(int ID) const +{ + return m_OriginConnection->getByID(ID); +} + + +FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const +{ + return m_OriginConnection->getByName(name); +} + + +int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive) +{ + const DirectoryEntry *directory = NULL; + const FileEntry *file = searchFile(path, &directory); + if (file != NULL) { + return file->getOrigin(archive); + } else { + if (directory != NULL) { + return directory->anyOrigin(); + } else { + return -1; + } + } +} + +std::vector DirectoryEntry::getFiles() const +{ + std::vector result; + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + result.push_back(m_FileRegister->getFile(iter->second)); + } + return result; +} + + +const FileEntry *DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const +{ + if (directory != NULL) { + *directory = NULL; + } + + if ((path.length() == 0) || + (path == L"*")) { + // no file name -> the path ended on a (back-)slash + *directory = this; + + return NULL; + } + + size_t len = path.find_first_of(L"\\/"); + + if (len == std::string::npos) { + // no more path components + auto iter = m_Files.find(path); + if (iter != m_Files.end()) { + return m_FileRegister->getFile(iter->second); + } else if (directory != NULL) { + DirectoryEntry *temp = findSubDirectory(path); + if (temp != NULL) { + *directory = temp; + } + } + } else { + // file is in in a subdirectory, recurse into the matching subdirectory + std::wstring pathComponent = path.substr(0, len); + DirectoryEntry *temp = findSubDirectory(pathComponent); + if (temp != NULL) { + return temp->searchFile(path.substr(len + 1), directory); + } + } + return NULL; +} + +/* +void DirectoryEntry::sortOrigins() +{ + for (std::set::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + const_cast(*iter).sortOrigins(); + } + for (std::vector::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + (*iter)->sortOrigins(); + } +} +*/ + +DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const +{ + for (std::vector::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) { + return *iter; + } + } + return NULL; +} + + + +const FileEntry *DirectoryEntry::findFile(const std::wstring &name) +{ + auto iter = m_Files.find(name); + if (iter != m_Files.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return NULL; + } +} + +DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID) +{ + for (std::vector::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) { + return *iter; + } + } + if (create) { + std::vector::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(), + new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection)); + return *iter; + } else { + return NULL; + } +} + + +DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID) +{ + if (path.length() == 0) { + // path ended with a backslash? + return this; + } + + size_t pos = path.find_first_of(L"\\/"); + if (pos == std::wstring::npos) { + return getSubDirectory(path, create); + } else { + DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID); + if (nextChild == NULL) { + return NULL; + } else { + return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID); + } + } +} + + + +FileRegister::FileRegister(boost::shared_ptr originConnection) + : m_OriginConnection(originConnection) +{ +} + +FileEntry::Index FileRegister::generateIndex() +{ + static FileEntry::Index sIndex = 0; + return sIndex++; +} + +bool FileRegister::indexValid(FileEntry::Index index) const +{ + return m_Files.find(index) != m_Files.end(); +} + +FileEntry &FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) +{ + FileEntry::Index index = generateIndex(); + m_Files[index] = FileEntry(index, name, parent); + return m_Files[index]; +} + + +FileEntry *FileRegister::getFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + return &iter->second; + } + return NULL; +} + + +void FileRegister::unregisterFile(FileEntry &file) +{ + bool ignore; + // unregister from origin + int originID = file.getOrigin(ignore); + m_OriginConnection->getByID(originID).removeFile(file.getIndex()); + const std::vector &alternatives = file.getAlternatives(); + for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + m_OriginConnection->getByID(*iter).removeFile(file.getIndex()); + } + + // unregister from directory + if (file.getParent() != NULL) { + file.getParent()->removeFile(file.getIndex()); + } +} + + +void FileRegister::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + unregisterFile(iter->second); + m_Files.erase(index); + } +} + +void FileRegister::removeOrigin(FileEntry::Index index, int originID) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + if (iter->second.removeOrigin(originID)) { + unregisterFile(iter->second); + } + } +} + +void FileRegister::sortOrigins() +{ + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + iter->second.sortOrigins(); + } +} diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h new file mode 100644 index 00000000..72e96357 --- /dev/null +++ b/src/shared/directoryentry.h @@ -0,0 +1,313 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef DIRECTORYENTRY_H +#define DIRECTORYENTRY_H + +#include +#include +#include +#include +#include +#define WIN32_MEAN_AND_LEAN +#include +#include +#include +#include "util.h" + + +class DirectoryEntry; +class OriginConnection; +class FileRegister; + + +class FileEntry { + +public: + + typedef unsigned int Index; + +public: + + FileEntry() : m_Index(UINT_MAX), m_Name(), m_Parent(NULL) {} + + FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent); + + Index getIndex() const { return m_Index; } + + void addOrigin(int origin, FILETIME fileTime, const std::wstring &archive); + // remove the specified origin from the list of origins that contain this file. if no origin is left, + // the file is effectively deleted and true is returned. otherwise, false is returned + bool removeOrigin(int origin); + void sortOrigins(); + + // gets the list of alternative origins (origins with lower priority than the primary one). + // if sortOrigins has been called, it is sorted by priority (ascending) + const std::vector &getAlternatives() const { return m_Alternatives; } + + const std::wstring &getName() const { return m_Name; } + int getOrigin(bool &archive) const { archive = (m_Archive.length() != 0); return m_Origin; } + const std::wstring &getArchive() const { return m_Archive; } + bool isFromArchive() const { return m_Archive.length() != 0; } + std::wstring getFullPath() const; + std::wstring getRelativePath() const; + DirectoryEntry *getParent() { return m_Parent; } + + void setFileTime(FILETIME fileTime) const { m_FileTime = fileTime; } + FILETIME getFileTime() const { return m_FileTime; } + +private: + + bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const; + + void determineTime(); + +private: + + Index m_Index; + std::wstring m_Name; + int m_Origin; + std::wstring m_Archive; + std::vector m_Alternatives; + DirectoryEntry *m_Parent; + mutable FILETIME m_FileTime; + + friend bool operator<(const FileEntry &lhs, const FileEntry &rhs) { + return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) < 0; + } + friend bool operator==(const FileEntry &lhs, const FileEntry &rhs) { + return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) == 0; + } +}; + + +// represents a mod or the data directory, providing files to the tree +class FilesOrigin { + friend class OriginConnection; +public: + + FilesOrigin() + : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) { } + + // sets priority for this origin, but it will overwrite the exisiting mapping for this priority, + // the previous origin will no longer be referenced + void setPriority(int priority); + + int getPriority() const { return m_Priority; } + + void setName(const std::wstring &name); + const std::wstring &getName() const { return m_Name; } + + int getID() const { return m_ID; } + const std::wstring &getPath() const { return m_Path; } + + std::vector getFiles() const; + + void enable(bool enabled); + bool isDisabled() const { return m_Disabled; } + + void addFile(FileEntry::Index index) { m_Files.insert(index); } + void removeFile(FileEntry::Index index); + +private: + + FilesOrigin(int ID, const std::wstring &name, const std::wstring &path, int priority, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection) + : m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), m_Priority(priority), + m_FileRegister(fileRegister), m_OriginConnection(originConnection) {} + +private: + + int m_ID; + + bool m_Disabled; + + std::set m_Files; + std::wstring m_Name; + std::wstring m_Path; + int m_Priority; + boost::shared_ptr m_FileRegister; + boost::shared_ptr m_OriginConnection; + +}; + + +class FileRegister +{ + +public: + + FileRegister(boost::shared_ptr originConnection); + + bool indexValid(FileEntry::Index index) const; + + FileEntry &createFile(const std::wstring &name, DirectoryEntry *parent); + FileEntry *getFile(FileEntry::Index index); + + void removeFile(FileEntry::Index index); + void removeOrigin(FileEntry::Index index, int originID); + + void sortOrigins(); + +private: + + FileEntry::Index generateIndex(); + + void unregisterFile(FileEntry &file); + +private: + + std::map m_Files; + + boost::shared_ptr m_OriginConnection; + +}; + + +class DirectoryEntry +{ +public: + + DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID); + + DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection); + + ~DirectoryEntry(); + + void clear(); + bool isPopulated() const { return m_Populated; } + + boost::shared_ptr getRegister() { return m_FileRegister; } + const DirectoryEntry *getParent() const { return m_Parent; } + + // add files to this directory (and subdirectories) from the specified origin. That origin may exist or not + void addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority); + void addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority); + + const std::wstring &getName() const; + + boost::shared_ptr getFileRegister() { return m_FileRegister; } + + bool originExists(const std::wstring &name) const; + FilesOrigin &getOriginByID(int ID) const; + FilesOrigin &getOriginByName(const std::wstring &name) const; + + int getOrigin(const std::wstring &path, bool &archive); + + std::vector getFiles() const; + + void getSubDirectories(std::vector::const_iterator &begin, std::vector::const_iterator &end) const { + begin = m_SubDirectories.begin(); end = m_SubDirectories.end(); + } + + DirectoryEntry *findSubDirectory(const std::wstring &name) const; + + /** retrieve a file in this directory by name. + * @param name name of the file + * @return fileentry object for the file or NULL if no file matches + */ + const FileEntry *findFile(const std::wstring &name); + + /** search through this directory and all subdirectories for a file by the specified name. + if directory is not NULL, the referenced variable will be set to true if the path refers to a directory. + the returned pointer is NULL in that case */ + const FileEntry *searchFile(const std::wstring &path, const DirectoryEntry **directory) const; + + void insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime); + + void removeFile(FileEntry::Index index); + + // remove the specified file from the tree. This can be a path leading to a file in a subdirectory + void removeFile(const std::wstring &filePath, int *origin = NULL); + + /** + * @brief remove the specified directory + * @param path directory to remove + */ + void removeDir(const std::wstring &path); + + void remove(const std::wstring &fileName, int *origin) { + auto iter = m_Files.find(fileName); + if (iter != m_Files.end()) { + if (origin != NULL) { + FileEntry *entry = m_FileRegister->getFile(iter->second); + if (entry != NULL) { + bool ignore; + *origin = entry->getOrigin(ignore); + } + } + m_FileRegister->removeFile(iter->second); + } + } + +private: + + void insert(const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive) { + auto iter = m_Files.find(fileName); + FileEntry *file = NULL; + if (iter != m_Files.end()) { + file = m_FileRegister->getFile(iter->second); + } else { + file = &m_FileRegister->createFile(fileName, this); + m_Files[fileName] = file->getIndex(); + } + file->addOrigin(origin.getID(), fileTime, archive); + origin.addFile(file->getIndex()); + } + + FilesOrigin &createOrigin(const std::wstring &originName, const std::wstring &directory, int priority); + void addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset); + void addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName); + + DirectoryEntry *getSubDirectory(const std::wstring &name, bool create, int originID = -1); + + DirectoryEntry *getSubDirectoryRecursive(const std::wstring &path, bool create, int originID = -1); + + int anyOrigin() const; + + void removeDirRecursive(); + +private: + + struct WStrLess { + bool operator()(const std::wstring &LHS, const std::wstring &RHS) const { + return _wcsicmp(LHS.c_str(), RHS.c_str()) < 0; + } + }; + + +private: + + boost::shared_ptr m_FileRegister; + boost::shared_ptr m_OriginConnection; + + std::wstring m_Name; + std::map m_Files; + std::vector m_SubDirectories; + + DirectoryEntry *m_Parent; + int m_Origin; + + bool m_Populated; + +}; + + +#endif // DIRECTORYENTRY_H diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp new file mode 100644 index 00000000..49fdba85 --- /dev/null +++ b/src/shared/error_report.cpp @@ -0,0 +1,99 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "error_report.h" +#include +#include + + +void reportError(LPCSTR format, ...) +{ + char buffer[1025]; + memset(buffer, 0, sizeof(char) * 1025); + + va_list argList; + va_start(argList, format); + + vsnprintf(buffer, 1024, format, argList); + va_end(argList); + + MessageBoxA(NULL, buffer, "Error", MB_OK | MB_ICONERROR); + LocalFree(buffer); +} + +void reportError(LPCWSTR format, ...) +{ + WCHAR buffer[1025]; + memset(buffer, 0, sizeof(WCHAR) * 1025); + + va_list argList; + va_start(argList, format); + + + _vsnwprintf(buffer, 1024, format, argList); + va_end(argList); + + MessageBoxW(NULL, buffer, L"Error", MB_OK | MB_ICONERROR); + LocalFree(buffer); +} + + +std::string getCurrentErrorStringA() +{ + LPSTR buffer = NULL; + + DWORD errorCode = ::GetLastError(); + + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, NULL) == 0) { + ::SetLastError(errorCode); + return std::string(); + } else { + LPSTR lastChar = buffer + strlen(buffer) - 2; + *lastChar = '\0'; + + std::string result(buffer); + + LocalFree(buffer); + ::SetLastError(errorCode); + return result; + } +} + +std::wstring getCurrentErrorStringW() +{ + LPWSTR buffer = NULL; + + DWORD errorCode = ::GetLastError(); + + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, NULL) == 0) { + ::SetLastError(errorCode); + return std::wstring(); + } else { + LPWSTR lastChar = buffer + wcslen(buffer) - 2; + *lastChar = '\0'; + + std::wstring result(buffer); + + LocalFree(buffer); + ::SetLastError(errorCode); + return result; + } +} diff --git a/src/shared/error_report.h b/src/shared/error_report.h new file mode 100644 index 00000000..6ee296df --- /dev/null +++ b/src/shared/error_report.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#include +#define WIN32_LEAN_AND_MEAN +#include +#include + +namespace std { +#ifdef UNICODE +typedef wstring tstring; +#else +typedef string tstring; +#endif +} + +void reportError(LPCSTR format, ...); +void reportError(LPCWSTR format, ...); + +extern void log(const char* format, ...); + +std::string getCurrentErrorStringA(); +std::wstring getCurrentErrorStringW(); + +#ifdef UNICODE +#define getCurrentErrorString getCurrentErrorStringW +#else +#define getCurrentErrorString getCurrentErrorStringA +#endif diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp new file mode 100644 index 00000000..f9699145 --- /dev/null +++ b/src/shared/fallout3info.cpp @@ -0,0 +1,244 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "fallout3info.h" +#include "util.h" +#include +#include +#include +#include "windows_error.h" +#include "error_report.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + +Fallout3Info::Fallout3Info(const std::wstring &omoDirectory, const std::wstring &gameDirectory) + : GameInfo(omoDirectory, gameDirectory) +{ + identifyMyGamesDirectory(L"fallout3"); +} + +bool Fallout3Info::identifyGame(const std::wstring &searchPath) +{ + return FileExists(searchPath, L"Fallout3.exe") && + FileExists(searchPath, L"FalloutLauncher.exe"); +} + +unsigned long Fallout3Info::getBSAVersion() +{ + return 0x68; +} + +std::wstring Fallout3Info::getRegPathStatic() +{ + HKEY key; + LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Fallout3", + 0, KEY_QUERY_VALUE, &key); + + if (errorcode != ERROR_SUCCESS) { + return L""; + } + + WCHAR temp[MAX_PATH]; + DWORD bufferSize = MAX_PATH; + + errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); + + return std::wstring(temp); +} + +std::wstring Fallout3Info::getInvalidationBSA() +{ + return L"Fallout - Invalidation.bsa"; +} + +bool Fallout3Info::isInvalidationBSA(const std::wstring &bsaName) +{ + static LPCWSTR invalidation[] = { L"Fallout - AI!.bsa", L"Fallout - Invalidation.bsa", NULL }; + + for (int i = 0; invalidation[i] != NULL; ++i) { + if (wcscmp(bsaName.c_str(), invalidation[i]) == 0) { + return true; + } + } + return false; +} + +std::wstring Fallout3Info::getDocumentsDir() +{ + std::wostringstream temp; + temp << getMyGamesDirectory() << L"\\Fallout3"; + + return temp.str(); +} + +std::wstring Fallout3Info::getSaveGameDir() +{ + std::wostringstream temp; + temp << getDocumentsDir() << L"\\Saves"; + return temp.str(); +} + +std::vector Fallout3Info::getPrimaryPlugins() +{ + return boost::assign::list_of(L"fallout3.esm"); +} + +std::vector Fallout3Info::getIniFileNames() +{ + return boost::assign::list_of(L"fallout.ini")(L"falloutprefs.ini"); +} + +std::wstring Fallout3Info::getSaveGameExtension() +{ + return L"*.fos"; +} + +std::wstring Fallout3Info::getReferenceDataFile() +{ + return L"Fallout - Meshes.bsa"; +} + + +std::wstring Fallout3Info::getOMODExt() +{ + return L"fomod"; +} + + +std::wstring Fallout3Info::getSteamAPPId() +{ + return L"22300"; +} + + +std::wstring Fallout3Info::getSEName() +{ + return L"fose"; +} + + +std::wstring Fallout3Info::getNexusPage() +{ + return L"http://fallout3.nexusmods.com"; +} + + +std::wstring Fallout3Info::getNexusInfoUrlStatic() +{ + return L"http://fallout3.nexusmods.com"; +} + + +int Fallout3Info::getNexusModIDStatic() +{ + return 16348; +} + + +void Fallout3Info::createProfile(const std::wstring &directory, bool useDefaults) +{ + std::wostringstream target; + + // copy plugins.txt + target << directory << "\\plugins.txt"; + + if (!FileExists(target.str())) { + std::wostringstream source; + source << getLocalAppFolder() << "\\Fallout3\\plugins.txt"; + if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + HANDLE file = ::CreateFileW(target.str().c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + ::CloseHandle(file); + } + } + + // copy ini-file + target.str(L""); target.clear(); + target << directory << L"\\fallout.ini"; + + if (!FileExists(target.str())) { + std::wostringstream source; + if (useDefaults) { + source << getGameDirectory() << L"\\fallout_default.ini"; + } else { + source << getMyGamesDirectory() << L"\\Fallout3"; + if (::FileExists(source.str(), L"fallout.ini")) { + source << L"\\fallout.ini"; + } else { + source.str(L""); + source << getGameDirectory() << L"\\fallout_default.ini"; + } + } + + if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + { // copy falloutprefs.ini-file + std::wstring target = directory.substr().append(L"\\falloutprefs.ini"); + if (!FileExists(target)) { + std::wostringstream source; + source << getMyGamesDirectory() << L"\\Fallout3\\falloutprefs.ini"; + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + } +} + + +void Fallout3Info::repairProfile(const std::wstring &directory) +{ + createProfile(directory, false); +} + + +bool Fallout3Info::rerouteToProfile(const wchar_t *fileName, const wchar_t*) +{ + static LPCWSTR profileFiles[] = { L"fallout.ini", L"falloutprefs.ini", L"plugins.txt", NULL }; + + for (int i = 0; profileFiles[i] != NULL; ++i) { + if (_wcsicmp(fileName, profileFiles[i]) == 0) { + return true; + } + } + return false; +} + + +std::vector Fallout3Info::getExecutables() +{ + std::vector result; + result.push_back(ExecutableInfo(L"FOSE", L"fose_loader.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Fallout 3", L"fallout3.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Fallout Mod Manager", L"fomm/fomm.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Construction Kit", L"geck.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Fallout Launcher", L"FalloutLauncher.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"BOSS", L"BOSS/BOSS.exe", L"", L"", NEVER_CLOSE)); + + return result; +} diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h new file mode 100644 index 00000000..bc9ab82d --- /dev/null +++ b/src/shared/fallout3info.h @@ -0,0 +1,94 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef FALLOUT3INFO_H +#define FALLOUT3INFO_H + + +#include "gameinfo.h" + + +class Fallout3Info : public GameInfo +{ + + friend class GameInfo; + +public: + + virtual ~Fallout3Info() {} + + virtual unsigned long getBSAVersion(); + + static std::wstring getRegPathStatic(); + virtual std::wstring getRegPath() { return Fallout3Info::getRegPathStatic(); } + virtual std::wstring getBinaryName() { return L"Fallout3.exe"; } + + virtual GameInfo::Type getType() { return TYPE_FALLOUT3; } + + virtual std::wstring getGameName() const { return L"Fallout 3"; } + virtual std::wstring getGameShortName() const { return L"Fallout3"; } + + virtual std::wstring getInvalidationBSA(); + + virtual bool isInvalidationBSA(const std::wstring &bsaName); + + // full path to this games "My Games"-directory + virtual std::wstring getDocumentsDir(); + + virtual std::wstring getSaveGameDir(); + + virtual std::vector getPrimaryPlugins(); + + // file name of this games ini (no path) + virtual std::vector getIniFileNames(); + + virtual std::wstring getSaveGameExtension(); + virtual std::wstring getReferenceDataFile(); + virtual std::wstring getOMODExt(); + + virtual std::wstring getSteamAPPId(); + + virtual std::wstring getSEName(); + + virtual std::wstring getNexusPage(); + static std::wstring getNexusInfoUrlStatic(); + virtual std::wstring getNexusInfoUrl() { return Fallout3Info::getNexusInfoUrlStatic(); } + static int getNexusModIDStatic(); + virtual int getNexusModID() { return Fallout3Info::getNexusModIDStatic(); } + + virtual void createProfile(const std::wstring &directory, bool useDefaults); + virtual void repairProfile(const std::wstring &directory); + + virtual bool rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath); + + // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to + // the game directory + virtual std::vector getExecutables(); + + virtual std::wstring archiveListKey() { return L"SArchiveList"; } + +private: + + Fallout3Info(const std::wstring &omoDirectory, const std::wstring &gameDirectory); + + static bool identifyGame(const std::wstring &searchPath); + +}; + +#endif // FALLOUT3INFO_H diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp new file mode 100644 index 00000000..380940b7 --- /dev/null +++ b/src/shared/falloutnvinfo.cpp @@ -0,0 +1,246 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "falloutnvinfo.h" +#include "util.h" +#include +#include +#include +#include "windows_error.h" +#include "error_report.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + + +FalloutNVInfo::FalloutNVInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory) + : GameInfo(omoDirectory, gameDirectory) +{ + identifyMyGamesDirectory(L"falloutnv"); +} + +bool FalloutNVInfo::identifyGame(const std::wstring &searchPath) +{ + return FileExists(searchPath, L"FalloutNV.exe") && + FileExists(searchPath, L"FalloutNVLauncher.exe"); +} + +unsigned long FalloutNVInfo::getBSAVersion() +{ + return 0x68; +} + +std::wstring FalloutNVInfo::getRegPathStatic() +{ + HKEY key; + LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\FalloutNV", + 0, KEY_QUERY_VALUE, &key); + + if (errorcode != ERROR_SUCCESS) { + return L""; + } + + WCHAR temp[MAX_PATH]; + DWORD bufferSize = MAX_PATH; + + errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); + + return std::wstring(temp); +} + +std::wstring FalloutNVInfo::getInvalidationBSA() +{ + return L"Fallout - Invalidation.bsa"; +} + +bool FalloutNVInfo::isInvalidationBSA(const std::wstring &bsaName) +{ + static LPCWSTR invalidation[] = { L"Fallout - AI!.bsa", L"Fallout - Invalidation.bsa", NULL }; + + for (int i = 0; invalidation[i] != NULL; ++i) { + if (wcscmp(bsaName.c_str(), invalidation[i]) == 0) { + return true; + } + } + return false; +} + +std::wstring FalloutNVInfo::getDocumentsDir() +{ + std::wostringstream temp; + temp << getMyGamesDirectory() << L"\\FalloutNV"; + + return temp.str(); +} + +std::wstring FalloutNVInfo::getSaveGameDir() +{ + std::wostringstream temp; + temp << getDocumentsDir() << L"\\Saves"; + return temp.str(); +} + +std::vector FalloutNVInfo::getPrimaryPlugins() +{ + return boost::assign::list_of(L"falloutnv.esm"); +} + +std::vector FalloutNVInfo::getIniFileNames() +{ + return boost::assign::list_of(L"fallout.ini")(L"falloutprefs.ini"); +} + + +std::wstring FalloutNVInfo::getSaveGameExtension() +{ + return L"*.fos"; +} + +std::wstring FalloutNVInfo::getReferenceDataFile() +{ + return L"Fallout - Meshes.bsa"; +} + + +std::wstring FalloutNVInfo::getOMODExt() +{ + return L"fomod"; +} + + +std::wstring FalloutNVInfo::getSteamAPPId() +{ + return L"22380"; +} + + +void FalloutNVInfo::createProfile(const std::wstring &directory, bool useDefaults) +{ + std::wostringstream target; + + // copy plugins.txt + target << directory << "\\plugins.txt"; + + if (!FileExists(target.str())) { + std::wostringstream source; + source << getLocalAppFolder() << "\\FalloutNV\\plugins.txt"; + if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + HANDLE file = ::CreateFileW(target.str().c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + ::CloseHandle(file); + } + } + + // copy ini-file + target.str(L""); target.clear(); + target << directory << L"\\fallout.ini"; + + if (!FileExists(target.str())) { + std::wostringstream source; + if (useDefaults) { + source << getGameDirectory() << L"\\fallout_default.ini"; + } else { + source << getMyGamesDirectory() << L"\\FalloutNV"; + if (::FileExists(source.str(), L"fallout.ini")) { + source << L"\\fallout.ini"; + } else { + source.str(L""); + source << getGameDirectory() << L"\\fallout_default.ini"; + } + } + + if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + { // copy falloutprefs.ini-file + std::wstring target = directory.substr().append(L"\\falloutprefs.ini"); + if (!FileExists(target)) { + std::wostringstream source; + source << getMyGamesDirectory() << L"\\FalloutNV\\falloutprefs.ini"; + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + } +} + + +std::wstring FalloutNVInfo::getSEName() +{ + return L"nvse"; +} + + +std::wstring FalloutNVInfo::getNexusPage() +{ + return L"http://newvegas.nexusmods.com"; +} + + +std::wstring FalloutNVInfo::getNexusInfoUrlStatic() +{ + return L"http://newvegas.nexusmods.com"; +} + + +int FalloutNVInfo::getNexusModIDStatic() +{ + return 42572; +} + + +void FalloutNVInfo::repairProfile(const std::wstring &directory) +{ + createProfile(directory, false); +} + + +bool FalloutNVInfo::rerouteToProfile(const wchar_t *fileName, const wchar_t*) +{ + static LPCWSTR profileFiles[] = { L"fallout.ini", L"falloutprefs.ini", L"plugins.txt", NULL }; + + for (int i = 0; profileFiles[i] != NULL; ++i) { + if (_wcsicmp(fileName, profileFiles[i]) == 0) { + return true; + } + } + return false; +} + + +std::vector FalloutNVInfo::getExecutables() +{ + std::vector result; + result.push_back(ExecutableInfo(L"NVSE", L"nvse_loader.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"New Vegas", L"falloutnv.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Fallout Mod Manager", L"fomm/fomm.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Construction Kit", L"geck.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Fallout Launcher", L"FalloutNVLauncher.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"BOSS", L"BOSS/BOSS.exe", L"", L"", NEVER_CLOSE)); + + return result; +} diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h new file mode 100644 index 00000000..aa02ac30 --- /dev/null +++ b/src/shared/falloutnvinfo.h @@ -0,0 +1,95 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef FALLOUTNVINFO_H +#define FALLOUTNVINFO_H + + +#include "gameinfo.h" + + +class FalloutNVInfo : public GameInfo +{ + + friend class GameInfo; + +public: + + virtual ~FalloutNVInfo() {} + + virtual unsigned long getBSAVersion(); + + static std::wstring getRegPathStatic(); + virtual std::wstring getRegPath() { return FalloutNVInfo::getRegPathStatic(); } + virtual std::wstring getBinaryName() { return L"FalloutNV.exe"; } + + virtual GameInfo::Type getType() { return TYPE_FALLOUTNV; } + + virtual std::wstring getGameName() const { return L"New Vegas"; } + virtual std::wstring getGameShortName() const { return L"FalloutNV"; } + +// virtual bool requiresSteam() const { return true; } + + virtual std::wstring getInvalidationBSA(); + + virtual bool isInvalidationBSA(const std::wstring &bsaName); + + // full path to this games "My Games"-directory + virtual std::wstring getDocumentsDir(); + + virtual std::wstring getSaveGameDir(); + + virtual std::vector getPrimaryPlugins(); + + // file name of this games ini (no path) + virtual std::vector getIniFileNames(); + + virtual std::wstring getSaveGameExtension(); + virtual std::wstring getReferenceDataFile(); + virtual std::wstring getOMODExt(); + + virtual std::wstring getSteamAPPId(); + + virtual std::wstring getSEName(); + + virtual std::wstring getNexusPage(); + static std::wstring getNexusInfoUrlStatic(); + virtual std::wstring getNexusInfoUrl() { return FalloutNVInfo::getNexusInfoUrlStatic(); } + static int getNexusModIDStatic(); + virtual int getNexusModID() { return FalloutNVInfo::getNexusModIDStatic(); } + + virtual void createProfile(const std::wstring &directory, bool useDefaults); + virtual void repairProfile(const std::wstring &directory); + + virtual bool rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath); + + // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to + // the game directory + virtual std::vector getExecutables(); + + virtual std::wstring archiveListKey() { return L"SArchiveList"; } + +private: + + FalloutNVInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory); + + static bool identifyGame(const std::wstring &searchPath); +}; + +#endif // FALLOUTNVINFO_H diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp new file mode 100644 index 00000000..6054df8b --- /dev/null +++ b/src/shared/gameinfo.cpp @@ -0,0 +1,204 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "gameinfo.h" + +#include "windows_error.h" + +#include "oblivioninfo.h" +#include "fallout3info.h" +#include "falloutnvinfo.h" +#include "skyriminfo.h" +#include "util.h" + +#include +#include +#include + + +GameInfo* GameInfo::s_Instance = NULL; + + +GameInfo::GameInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory) + : m_GameDirectory(gameDirectory), m_OrganizerDirectory(omoDirectory) +{ +} + + +void GameInfo::identifyMyGamesDirectory(const std::wstring &file) +{ + wchar_t myDocuments[MAX_PATH]; + memset(myDocuments, '\0', MAX_PATH); + + ::SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, myDocuments); + + m_MyGamesDirectory.assign(myDocuments).append(L"\\My Games"); + if (!FileExists(m_MyGamesDirectory.substr().append(L"/").append(file))) { + if (::SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_DEFAULT, myDocuments) != S_OK) { + m_MyGamesDirectory.assign(myDocuments).append(L"\\My Games"); + } + } + if (!FileExists(m_MyGamesDirectory.substr().append(L"/").append(file))) { + m_MyGamesDirectory.assign(getSpecialPath(L"Personal")).append(L"\\My Games"); + } +} + + +bool GameInfo::identifyGame(const std::wstring &omoDirectory, const std::wstring &searchPath) +{ + if (OblivionInfo::identifyGame(searchPath)) { + s_Instance = new OblivionInfo(omoDirectory, searchPath); + } else if (Fallout3Info::identifyGame(searchPath)) { + s_Instance = new Fallout3Info(omoDirectory, searchPath); + } else if (FalloutNVInfo::identifyGame(searchPath)) { + s_Instance = new FalloutNVInfo(omoDirectory, searchPath); + } else if (SkyrimInfo::identifyGame(searchPath)) { + s_Instance = new SkyrimInfo(omoDirectory, searchPath); + } + + return s_Instance != NULL; +} + + +bool GameInfo::init(const std::wstring &omoDirectory, const std::wstring &gamePath) +{ + if (s_Instance == NULL) { + if (gamePath.length() == 0) { + // search upward in the directory until a recognized game-binary is found + std::wstring searchPath(omoDirectory); + while (!identifyGame(omoDirectory, searchPath)) { + size_t lastSep = searchPath.find_last_of(L"/\\"); + if (lastSep == std::string::npos) { + return false; + } + searchPath.erase(lastSep); + } + } else if (!identifyGame(omoDirectory, gamePath)) { + return false; + } + } + return true; +} + + +GameInfo &GameInfo::instance() +{ + assert(s_Instance != NULL); + return *s_Instance; +} + +std::wstring GameInfo::getGameDirectory() const +{ + return m_GameDirectory; +} + +std::wstring GameInfo::getModsDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << L"\\mods"; + return temp.str(); +} + +std::wstring GameInfo::getProfilesDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << L"\\profiles"; + return temp.str(); +} + +std::wstring GameInfo::getIniFilename() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << L"\\ModOrganizer.ini"; + return temp.str(); +} + + +std::wstring GameInfo::getDownloadDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << L"\\downloads"; + return temp.str(); +} + + +std::wstring GameInfo::getCacheDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << L"\\webcache"; + return temp.str(); +} + + +std::wstring GameInfo::getOverwriteDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << "\\overwrite"; + return temp.str(); +} + + +std::wstring GameInfo::getLogDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << "\\logs"; + return temp.str(); +} + + +std::wstring GameInfo::getTutorialDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << "\\tutorials"; + return temp.str(); +} + + +bool GameInfo::requiresSteam() const +{ + return FileExists(getGameDirectory().append(L"\\steam_api.dll")); +} + + +std::wstring GameInfo::getLocalAppFolder() const +{ + return getSpecialPath(L"Local AppData"); +} + +std::wstring GameInfo::getSpecialPath(LPCWSTR name) const +{ + HKEY key; + LONG errorcode = ::RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", + 0, KEY_QUERY_VALUE, &key); + + if (errorcode != ERROR_SUCCESS) { + throw windows_error("failed to look up special folder", errorcode); + } + + WCHAR temp[MAX_PATH]; + DWORD bufferSize = MAX_PATH; + + errorcode = ::RegQueryValueExW(key, name, NULL, NULL, (LPBYTE)temp, &bufferSize); + if (errorcode != ERROR_SUCCESS) { + throw windows_error("failed to look up special folder", errorcode); + } + + return temp; +} + diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h new file mode 100644 index 00000000..8828a2fc --- /dev/null +++ b/src/shared/gameinfo.h @@ -0,0 +1,187 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef GAMEINFO_H +#define GAMEINFO_H + +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +enum CloseMOStyle { + DEFAULT_CLOSE, + DEFAULT_STAY, + NEVER_CLOSE +}; + +struct ExecutableInfo { + + ExecutableInfo(const std::wstring &aTitle, const std::wstring &aBinary, + const std::wstring &aArguments, const std::wstring &aWorkingDirectory, CloseMOStyle aCloseMO) + : title(aTitle), binary(aBinary), arguments(aArguments), workingDirectory(aWorkingDirectory), + closeMO(aCloseMO), steamAppID(L"") {} + ExecutableInfo(const std::wstring &aTitle, const std::wstring &aBinary, + const std::wstring &aArguments, const std::wstring &aWorkingDirectory, + CloseMOStyle aCloseMO, const std::wstring &aSteamAppID) + : title(aTitle), binary(aBinary), arguments(aArguments), workingDirectory(aWorkingDirectory), + closeMO(aCloseMO), steamAppID(aSteamAppID) {} + std::wstring title; + std::wstring binary; + std::wstring arguments; + std::wstring workingDirectory; + CloseMOStyle closeMO; + std::wstring steamAppID; +}; + + +/** + Class to manage information that depends on the used game type. The intention is to keep + as much of the rest of omo is game-agnostic. It is mostly concerned with figuring out the + correct paths, filenames and data types. +*/ +class GameInfo +{ + +public: + + enum Type { + TYPE_OBLIVION, + TYPE_FALLOUT3, + TYPE_FALLOUTNV, + TYPE_SKYRIM + }; + + enum LoadOrderMechanism { + TYPE_FILETIME, + TYPE_PLUGINSTXT + }; + +public: + + virtual ~GameInfo() {} + + std::wstring getOrganizerDirectory() { return m_OrganizerDirectory; } + + virtual std::wstring getRegPath() = 0; + virtual std::wstring getBinaryName() = 0; + + virtual unsigned long getBSAVersion() = 0; + + virtual GameInfo::Type getType() = 0; + + virtual std::wstring getGameName() const = 0; + virtual std::wstring getGameShortName() const = 0; + + // full path to this games "My Games"-directory + virtual std::wstring getDocumentsDir() = 0; + + virtual std::wstring getSaveGameDir() = 0; + + /// determine the load order mechanism used by this game. this may throw an + /// exception if the mechanism can't be determined + virtual LoadOrderMechanism getLoadOrderMechanism() const { return TYPE_FILETIME; } + + virtual std::wstring getGameDirectory() const; + // get absolute path to the directory where omo stores its mods + virtual std::wstring getModsDir() const; + // get absolute path to the directory where omo stores its profiles + virtual std::wstring getProfilesDir() const; + + virtual std::wstring getIniFilename() const; + virtual std::wstring getDownloadDir() const; + virtual std::wstring getCacheDir() const; + virtual std::wstring getOverwriteDir() const; + virtual std::wstring getLogDir() const; + virtual std::wstring getTutorialDir() const; + + virtual bool requiresBSAInvalidation() const { return true; } + virtual bool requiresSteam() const; + + virtual std::wstring getInvalidationBSA() = 0; + + virtual bool isInvalidationBSA(const std::wstring &bsaName) = 0; + + // the key in the game's ini-file that defines the list of bsas to load + virtual std::wstring archiveListKey() = 0; + + virtual std::vector getPrimaryPlugins() = 0; + + // file name of this games ini file(s) + virtual std::vector getIniFileNames() = 0; + + virtual std::wstring getSaveGameExtension() = 0; + virtual std::wstring getReferenceDataFile() = 0; + + virtual std::wstring getOMODExt() = 0; + + virtual std::wstring getSteamAPPId() = 0; + + virtual std::wstring getSEName() = 0; + + virtual std::wstring getNexusPage() = 0; + virtual std::wstring getNexusInfoUrl() = 0; + virtual int getNexusModID() = 0; + + // clone relevant files to the specified directory + virtual void createProfile(const std::wstring &directory, bool useDefaults) = 0; + virtual void repairProfile(const std::wstring &directory) = 0; + + virtual bool rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath) = 0; + + // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to + // the game directory. the boolean says whether omo should be closed when the executable is started + virtual std::vector getExecutables() = 0; + +public: + + // initialise with the path to the omo directory (needs to be where hook.dll is stored). This + // needs to be called before the instance can be retrieved + static bool init(const std::wstring &omoDirectory, const std::wstring &gamePath = L""); + + static GameInfo& instance(); + +protected: + + GameInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory); + + std::wstring getLocalAppFolder() const; + const std::wstring &getMyGamesDirectory() const { return m_MyGamesDirectory; } + void identifyMyGamesDirectory(const std::wstring &file); + +private: + + static bool identifyGame(const std::wstring &omoDirectory, const std::wstring &searchPath); + std::wstring getSpecialPath(LPCWSTR name) const; + +private: + + static GameInfo* s_Instance; + + std::wstring m_MyGamesDirectory; + + std::wstring m_GameDirectory; + std::wstring m_OrganizerDirectory; + +}; + + +#endif // GAMEINFO_H diff --git a/src/shared/inject.cpp b/src/shared/inject.cpp new file mode 100644 index 00000000..8cedd247 --- /dev/null +++ b/src/shared/inject.cpp @@ -0,0 +1,142 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "inject.h" +/*#if defined UNICODE && !defined _UNICODE +#define _UNICODE 1 +#endif*/ +#include +#include +#include +#include +#include +#include +#include +#include "windows_error.h" +#include "error_report.h" + + +struct TParameters { + char dllname[MAX_PATH]; + wchar_t profileName[101]; + char initstr[5]; + int logLevel; +}; + + +typedef HMODULE (WINAPI *TLoadLibraryType)(LPCTSTR); +typedef FARPROC (WINAPI *TGetProcAddressType)(HMODULE, LPCSTR); + + + +void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel) +{ + // prepare parameters that are to be passed to the injected dll + TParameters parameters; + memset(¶meters, '\0', sizeof(TParameters)); + strncpy(parameters.dllname, dllname.c_str(), MAX_PATH - 1); + wcsncpy(parameters.profileName, profileName.c_str(), 100); + _snprintf(parameters.initstr, 5, "Init"); //this is the name of thie initialisation function we want to call in the target process + + HMODULE k32mod = ::LoadLibrary(__TEXT("kernel32.dll")); + TLoadLibraryType loadLibraryFunc = NULL; + TGetProcAddressType getProcAddressFunc = NULL; + // ansi binaries + if (k32mod != NULL) { + loadLibraryFunc = reinterpret_cast(::GetProcAddress(k32mod, "LoadLibraryA")); + getProcAddressFunc = reinterpret_cast(::GetProcAddress(k32mod, "GetProcAddress")); + if ((loadLibraryFunc == NULL) || (getProcAddressFunc == NULL)) { + throw windows_error("failed to determine address for required functions"); + } + } else { + throw windows_error("kernel32.dll not loaded??"); + } + + // allocate memory in the target process and write the parameter-block there + LPVOID remoteMem = ::VirtualAllocEx(processHandle, NULL, sizeof(TParameters), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (remoteMem == NULL) { + throw windows_error("failed to allocate memory in target process"); + } + SIZE_T written; + if (!::WriteProcessMemory(processHandle, remoteMem, ¶meters, sizeof(TParameters), &written) || + (written != sizeof(TParameters))) { + throw windows_error("failed to write parameters to target process"); + } + + // now for the interesting part: write a stub into the target process that is run before any code of the original binary. This code will load + // our injected dll into the process and run its Init-function which takes the profile name as its parameter + // obviously this code is ultra-hacky + + // construct the stub in beautiful assembler with placeholders + BYTE stubLocal[] = { 0x60, // PUSHAD + 0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOV EAX, imm32 (LoadLibrary) + 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (dllname) + 0xFF, 0xD0, // CALL EAX (=LoadLibrary, leaves module handle of our dll in eax) + 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 ("Init") + 0x50, // PUSH EAX + 0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOVE EAX, imm32 (GetProcAddress) + 0xFF, 0xD0, // CALL EAX (GetProcAddress) + 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (profile name) + 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (log level) + 0xFF, 0xD0, // CALL EAX (=InitFunction) + 0x58, // POP EAX (init function is defined cdecl) + 0x58, // POP EAX (init function is defined cdecl) + 0x61, // POPAD + 0xE9, 0xBA, 0xAD, 0xF0, 0x0D // JMP near, relative (=original entry point) + }; + + // reserve memory for the stub + PBYTE stubRemote = reinterpret_cast(::VirtualAllocEx(processHandle, NULL, sizeof(stubLocal), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); + if (stubRemote == NULL) { + throw windows_error("failed to allocate memory for stub"); + } + TParameters *remoteParams = reinterpret_cast(remoteMem); + // determine the remote addresses of each of the parameters +// ULONG remoteDLLName = reinterpret_cast(remoteMem); +// ULONG remoteInitString = remoteDLLName + MAX_PATH * sizeof(char); + + // dizzy yet? we still have to calculate the entry point as an address relative to our stub + ULONG entryPoint = 0; + + CONTEXT threadContext; + threadContext.ContextFlags = CONTEXT_CONTROL; + if (::GetThreadContext(threadHandle, &threadContext) == 0) { + throw windows_error("failed to access thread context. Please note that Mod Organizer does not support 64bit binaries!"); + } else { + entryPoint = threadContext.Eip - (reinterpret_cast(stubRemote) + sizeof(stubLocal)); + } + // now replace each baadf00d by the correct value. The pointers need to be the pointers in the REMOTE memory of course, that's why we copied them there + *(PULONG)&stubLocal[2] = reinterpret_cast(*loadLibraryFunc); + *(PULONG)&stubLocal[7] = reinterpret_cast(remoteParams->dllname); + *(PULONG)&stubLocal[14] = reinterpret_cast(remoteParams->initstr); + *(PULONG)&stubLocal[20] = reinterpret_cast(*getProcAddressFunc); + *(PULONG)&stubLocal[27] = reinterpret_cast(remoteParams->profileName); + *(PULONG)&stubLocal[32] = logLevel; + *(PULONG)&stubLocal[42] = entryPoint; + // almost there. copy stub to target process + if (!::WriteProcessMemory(processHandle, stubRemote, reinterpret_cast(stubLocal), sizeof(stubLocal), &written) || + (written != sizeof(stubLocal))) { + throw windows_error("failed to write stub to target process"); + } + // finally, make the stub the new next thing for the thread to execute + threadContext.Eip = (ULONG)stubRemote; + if (::SetThreadContext(threadHandle, &threadContext) == 0) { + throw windows_error("failed to overwrite thread context"); + } +} diff --git a/src/shared/inject.h b/src/shared/inject.h new file mode 100644 index 00000000..69c56cc6 --- /dev/null +++ b/src/shared/inject.h @@ -0,0 +1,27 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel); diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp new file mode 100644 index 00000000..c34b2202 --- /dev/null +++ b/src/shared/oblivioninfo.cpp @@ -0,0 +1,252 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "oblivioninfo.h" +#include +#include +#include "util.h" +#include +#include "windows_error.h" +#include "error_report.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + + +OblivionInfo::OblivionInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory) + : GameInfo(omoDirectory, gameDirectory) +{ + identifyMyGamesDirectory(L"oblivion"); +} + +bool OblivionInfo::identifyGame(const std::wstring &searchPath) +{ + return FileExists(searchPath, L"Oblivion.exe") && + FileExists(searchPath, L"OblivionLauncher.exe"); +} + +unsigned long OblivionInfo::getBSAVersion() +{ + return 0x67; +} + +std::wstring OblivionInfo::getRegPathStatic() +{ + HKEY key; + LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Oblivion", + 0, KEY_QUERY_VALUE, &key); + + if (errorcode != ERROR_SUCCESS) { + return L""; + } + + WCHAR temp[MAX_PATH]; + DWORD bufferSize = MAX_PATH; + + errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); + + return std::wstring(temp); +} + +std::wstring OblivionInfo::getInvalidationBSA() +{ + return L"Oblivion - Invalidation.bsa"; +} + +bool OblivionInfo::isInvalidationBSA(const std::wstring &bsaName) +{ + static LPCWSTR invalidation[] = { L"Oblivion - Invalidation.bsa", L"ArchiveInvalidationInvalidated!.bsa", + L"BSARedirection.bsa", NULL }; + + for (int i = 0; invalidation[i] != NULL; ++i) { + if (wcscmp(bsaName.c_str(), invalidation[i]) == 0) { + return true; + } + } + return false; +} + +std::wstring OblivionInfo::getDocumentsDir() +{ + std::wostringstream temp; + temp << getMyGamesDirectory() << L"\\Oblivion"; + + return temp.str(); +} + +std::wstring OblivionInfo::getSaveGameDir() +{ + std::wostringstream temp; + temp << getDocumentsDir() << L"\\Saves"; + return temp.str(); +} + + +std::vector OblivionInfo::getPrimaryPlugins() +{ + return boost::assign::list_of(L"oblivion.esm"); +} + + +std::vector OblivionInfo::getIniFileNames() +{ + return boost::assign::list_of(L"oblivion.ini")(L"oblivionprefs.ini"); +} + + +void OblivionInfo::createProfile(const std::wstring &directory, bool useDefaults) +{ + std::wostringstream target; + + // copy plugins.txt + target << directory << "\\plugins.txt"; + + if (!FileExists(target.str())) { + std::wostringstream source; + source << getLocalAppFolder() << "\\Oblivion\\plugins.txt"; + if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + HANDLE file = ::CreateFileW(target.str().c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + ::CloseHandle(file); + } + } + + // copy ini-file + target.str(L""); target.clear(); + target << directory << L"\\oblivion.ini"; + + if (!FileExists(target.str())) { + std::wostringstream source; + source << getMyGamesDirectory() << L"\\Oblivion\\oblivion.ini"; + if (useDefaults) { + source << getGameDirectory() << L"\\oblivion_default.ini"; + } else { + source << getMyGamesDirectory() << L"Oblivion"; + if (::FileExists(source.str(), L"oblivion.ini")) { + source << L"\\oblivion.ini"; + } else { + source.str(L""); + source << getGameDirectory() << L"\\oblivion_default.ini"; + } + } + if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + + { // copy oblivionprefs.ini-file + std::wstring target = directory.substr().append(L"\\oblivionprefs.ini"); + if (!FileExists(target)) { + std::wostringstream source; + source << getMyGamesDirectory() << L"\\Oblivion\\oblivionprefs.ini"; + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + if ((::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL) == INVALID_HANDLE_VALUE) && + (::GetLastError() != ERROR_FILE_EXISTS)) { + std::ostringstream stream; + stream << "failed to create ini file: " << ToString(target.c_str(), false); + throw windows_error(stream.str()); + } + } + } + } +} + + +std::wstring OblivionInfo::getSEName() +{ + return L"obse"; +} + + +std::wstring OblivionInfo::getNexusPage() +{ + return L"http://oblivion.nexusmods.com"; +} + + +std::wstring OblivionInfo::getNexusInfoUrlStatic() +{ + return L"http://oblivion.nexusmods.com"; +} + + +int OblivionInfo::getNexusModIDStatic() +{ + return 38277; +} + + +void OblivionInfo::repairProfile(const std::wstring &directory) +{ + createProfile(directory, false); +} + + +bool OblivionInfo::rerouteToProfile(const wchar_t *fileName, const wchar_t*) +{ + static LPCWSTR profileFiles[] = { L"oblivion.ini", L"oblivionprefs.ini", L"plugins.txt", NULL }; + + for (int i = 0; profileFiles[i] != NULL; ++i) { + if (_wcsicmp(fileName, profileFiles[i]) == 0) { + return true; + } + } + return false; +} + + +std::wstring OblivionInfo::getSaveGameExtension() +{ + return L"*.ess"; +} + +std::wstring OblivionInfo::getReferenceDataFile() +{ + return L"Oblivion - Meshes.bsa"; +} + + +std::wstring OblivionInfo::getOMODExt() +{ + return L"omod"; +} + + +std::wstring OblivionInfo::getSteamAPPId() +{ + return L"22330"; +} + + +std::vector OblivionInfo::getExecutables() +{ + std::vector result; + result.push_back(ExecutableInfo(L"OBSE", L"obse_loader.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Oblivion", L"oblivion.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Oblivion Mod Manager", L"OblivionModManager.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Construction Set", L"TESConstructionSet.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Oblivion Launcher", L"OblivionLauncher.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"BOSS", L"BOSS/BOSS.exe", L"", L"", NEVER_CLOSE)); + result.push_back(ExecutableInfo(L"BOSS (old)", L"Data/BOSS.exe", L"", L"", NEVER_CLOSE)); + + return result; +} diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h new file mode 100644 index 00000000..0d6bba65 --- /dev/null +++ b/src/shared/oblivioninfo.h @@ -0,0 +1,92 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef OBLIVIONINFO_H +#define OBLIVIONINFO_H + +#include "gameinfo.h" + +class OblivionInfo : public GameInfo +{ + + friend class GameInfo; + +public: + + virtual ~OblivionInfo() {} + + virtual unsigned long getBSAVersion(); + + static std::wstring getRegPathStatic(); + virtual std::wstring getRegPath() { return OblivionInfo::getRegPathStatic(); } + virtual std::wstring getBinaryName() { return L"Oblivion.exe"; } + + virtual GameInfo::Type getType() { return TYPE_OBLIVION; } + + virtual std::wstring getGameName() const { return L"Oblivion"; } + virtual std::wstring getGameShortName() const { return L"Oblivion"; } + + virtual std::wstring getInvalidationBSA(); + + virtual bool isInvalidationBSA(const std::wstring &bsaName); + + // full path to this games "My Games"-directory + virtual std::wstring getDocumentsDir(); + + virtual std::wstring getSaveGameDir(); + + virtual std::vector getPrimaryPlugins(); + + // file name of this games ini (no path) + virtual std::vector getIniFileNames(); + + virtual std::wstring getSaveGameExtension(); + virtual std::wstring getReferenceDataFile(); + virtual std::wstring getOMODExt(); + + virtual std::wstring getSteamAPPId(); + + virtual std::wstring getSEName(); + + virtual std::wstring getNexusPage(); + static std::wstring getNexusInfoUrlStatic(); + virtual std::wstring getNexusInfoUrl() { return OblivionInfo::getNexusInfoUrlStatic(); } + static int getNexusModIDStatic(); + virtual int getNexusModID() { return OblivionInfo::getNexusModIDStatic(); } + + virtual void createProfile(const std::wstring &directory, bool useDefaults); + virtual void repairProfile(const std::wstring &directory); + + virtual bool rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath); + + // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to + // the game directory + virtual std::vector getExecutables(); + + virtual std::wstring archiveListKey() { return L"SArchiveList"; } + +private: + + OblivionInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory); + + static bool identifyGame(const std::wstring &searchPath); + +}; + +#endif // OBLIVIONINFO_H diff --git a/src/shared/shared.pro b/src/shared/shared.pro new file mode 100644 index 00000000..3e1283e6 --- /dev/null +++ b/src/shared/shared.pro @@ -0,0 +1,56 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2011-05-03T18:35:56 +# +#------------------------------------------------- + +QT -= gui core +#QT += core + +TARGET = mo_shared +TEMPLATE = lib +CONFIG += staticlib + +INCLUDEPATH += ../../../bsatk "$(BOOSTPATH)" + +CONFIG(debug, debug|release) { + LIBS += -L../../../bsatk-build/debug + +} else { + LIBS += -L../../../bsatk-build/release +} + +LIBS += -lbsatk + +DEFINES += UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS + +DEFINES += BOOST_DISABLE_ASSERTS NDEBUG + +# QMAKE_CXXFLAGS += /analyze + +SOURCES += \ + inject.cpp \ + windows_error.cpp \ + error_report.cpp \ + directoryentry.cpp \ + gameinfo.cpp \ + oblivioninfo.cpp \ + fallout3info.cpp \ + falloutnvinfo.cpp \ + util.cpp \ + skyriminfo.cpp \ + appconfig.cpp + +HEADERS += \ + inject.h \ + windows_error.h \ + error_report.h \ + directoryentry.h \ + gameinfo.h \ + oblivioninfo.h \ + fallout3info.h \ + falloutnvinfo.h \ + util.h \ + skyriminfo.h \ + appconfig.h \ + appconfig.inc diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp new file mode 100644 index 00000000..f3043c28 --- /dev/null +++ b/src/shared/skyriminfo.cpp @@ -0,0 +1,278 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "skyriminfo.h" + +#include "util.h" +#include +#include +#include +#include "windows_error.h" +#include "error_report.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + + +SkyrimInfo::SkyrimInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory) + : GameInfo(omoDirectory, gameDirectory) +{ + identifyMyGamesDirectory(L"skyrim"); +} + +bool SkyrimInfo::identifyGame(const std::wstring &searchPath) +{ + return FileExists(searchPath, L"TESV.exe") && + FileExists(searchPath, L"SkyrimLauncher.exe"); +} + +unsigned long SkyrimInfo::getBSAVersion() +{ + return 0x68; +} + + +std::wstring SkyrimInfo::getRegPathStatic() +{ + HKEY key; + LONG errorcode = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Bethesda Softworks\\Skyrim", + 0, KEY_QUERY_VALUE, &key); + + if (errorcode != ERROR_SUCCESS) { + return L""; + } + + WCHAR temp[MAX_PATH]; + DWORD bufferSize = MAX_PATH; + + errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); + + return std::wstring(temp); +} + + +std::wstring SkyrimInfo::getInvalidationBSA() +{ + return L"Skyrim - Invalidation.bsa"; +} + +bool SkyrimInfo::isInvalidationBSA(const std::wstring &bsaName) +{ + static LPCWSTR invalidation[] = { L"Skyrim - Invalidation.bsa", NULL }; + + for (int i = 0; invalidation[i] != NULL; ++i) { + if (wcscmp(bsaName.c_str(), invalidation[i]) == 0) { + return true; + } + } + return false; +} + + +GameInfo::LoadOrderMechanism SkyrimInfo::getLoadOrderMechanism() const +{ + std::wstring fileName = getGameDirectory().append(L"\\TESV.exe"); + + try { + VS_FIXEDFILEINFO versionInfo = GetFileVersion(fileName); + if ((versionInfo.dwFileVersionMS > 0x10004) || // version >= 1.5.x? + ((versionInfo.dwFileVersionMS == 0x10004) && (versionInfo.dwFileVersionLS >= 0x1A0000))) { // version >= ?.4.26 + return TYPE_PLUGINSTXT; + } else { + return TYPE_FILETIME; + } + } catch (const std::exception &e) { + reportError("TESV.exe is invalid: %s", e.what()); + return TYPE_FILETIME; + } +} + +std::wstring SkyrimInfo::getDocumentsDir() +{ + std::wostringstream temp; + temp << getMyGamesDirectory() << L"\\Skyrim"; + + return temp.str(); +} + +std::wstring SkyrimInfo::getSaveGameDir() +{ + std::wostringstream temp; + temp << getDocumentsDir() << L"\\Saves"; + return temp.str(); +} + +std::vector SkyrimInfo::getPrimaryPlugins() +{ + return boost::assign::list_of(L"skyrim.esm")(L"update.esm"); +} + +std::vector SkyrimInfo::getIniFileNames() +{ + return boost::assign::list_of(L"skyrim.ini")(L"skyrimprefs.ini"); +} + +std::wstring SkyrimInfo::getSaveGameExtension() +{ + return L"*.ess"; +} + +std::wstring SkyrimInfo::getReferenceDataFile() +{ + return L"Skyrim - Meshes.bsa"; +} + + +std::wstring SkyrimInfo::getOMODExt() +{ + return L"fomod"; +} + + +std::wstring SkyrimInfo::getSteamAPPId() +{ + return L"72850"; +} + + +std::wstring SkyrimInfo::getSEName() +{ + return L"skse"; +} + + +std::wstring SkyrimInfo::getNexusPage() +{ + return L"http://skyrim.nexusmods.com"; +} + + +std::wstring SkyrimInfo::getNexusInfoUrlStatic() +{ + return L"http://skyrim.nexusmods.com"; +} + + +int SkyrimInfo::getNexusModIDStatic() +{ + return 1334; +} + + +void SkyrimInfo::createProfile(const std::wstring &directory, bool useDefaults) +{ + { // copy plugins.txt + std::wstring target = directory.substr().append(L"\\plugins.txt"); + if (!FileExists(target)) { + std::wostringstream source; + source << getLocalAppFolder() << "\\Skyrim\\plugins.txt"; + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + HANDLE file = ::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + ::CloseHandle(file); + } + } + target = directory.substr().append(L"\\loadorder.txt"); + if (!FileExists(target)) { + std::wostringstream source; + source << getLocalAppFolder() << "\\Skyrim\\loadorder.txt"; + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + HANDLE file = ::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + ::CloseHandle(file); + } + } + } + + { // copy skyrim.ini-file + std::wstring target = directory.substr().append(L"\\skyrim.ini"); + if (!FileExists(target)) { + std::wostringstream source; + if (useDefaults) { + source << getGameDirectory() << L"\\skyrim_default.ini"; + } else { + source << getMyGamesDirectory() << L"\\Skyrim"; + if (::FileExists(source.str(), L"skyrim.ini")) { + source << L"\\skyrim.ini"; + } else { + source.str(L""); + source << getGameDirectory() << L"\\skyrim_default.ini"; + } + } + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + } + + { // copy skyrimprefs.ini-file + std::wstring target = directory.substr().append(L"\\skyrimprefs.ini"); + if (!FileExists(target)) { + std::wostringstream source; + source << getMyGamesDirectory() << L"\\Skyrim\\skyrimprefs.ini"; + if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + if (::GetLastError() != ERROR_FILE_EXISTS) { + std::ostringstream stream; + stream << "failed to copy ini file: " << ToString(source.str(), false); + throw windows_error(stream.str()); + } + } + } + } +} + + +void SkyrimInfo::repairProfile(const std::wstring &directory) +{ + createProfile(directory, false); +} + + +bool SkyrimInfo::rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath) +{ + static LPCWSTR profileFiles[] = { L"skyrim.ini", L"skyrimprefs.ini", L"loadorder.txt", NULL }; + + for (int i = 0; profileFiles[i] != NULL; ++i) { + if (_wcsicmp(fileName, profileFiles[i]) == 0) { + return true; + } + } + + if ((_wcsicmp(fileName, L"plugins.txt") == 0) && + (wcsstr(fullPath, L"AppData") != NULL)){ + return true; + } + + return false; +} + + +std::vector SkyrimInfo::getExecutables() +{ + std::vector result; + result.push_back(ExecutableInfo(L"SKSE", L"skse_loader.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"SBW", L"SBW.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"Skyrim", L"TESV.exe", L"", L"", DEFAULT_CLOSE)); + result.push_back(ExecutableInfo(L"BOSS", L"BOSS/BOSS.exe", L"", L"", DEFAULT_STAY)); + result.push_back(ExecutableInfo(L"Creation Kit", L"CreationKit.exe", L"", L"", DEFAULT_STAY, L"202480")); + + return result; +} diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h new file mode 100644 index 00000000..25de4029 --- /dev/null +++ b/src/shared/skyriminfo.h @@ -0,0 +1,100 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SKYRIMINFO_H +#define SKYRIMINFO_H + + +#include "gameinfo.h" + + +class SkyrimInfo : public GameInfo +{ + + friend class GameInfo; + +public: + + virtual ~SkyrimInfo() {} + + virtual unsigned long getBSAVersion(); + + static std::wstring getRegPathStatic(); + virtual std::wstring getRegPath() { return SkyrimInfo::getRegPathStatic(); } + virtual std::wstring getBinaryName() { return L"TESV.exe"; } + + virtual GameInfo::Type getType() { return TYPE_SKYRIM; } + + virtual std::wstring getGameName() const { return L"Skyrim"; } + virtual std::wstring getGameShortName() const { return L"Skyrim"; } + + virtual LoadOrderMechanism getLoadOrderMechanism() const; + + virtual bool requiresBSAInvalidation() const { return true; } +// virtual bool requiresSteam() const { return true; } + + virtual std::wstring getInvalidationBSA(); + + virtual bool isInvalidationBSA(const std::wstring &bsaName); + + // full path to this games "My Games"-directory + virtual std::wstring getDocumentsDir(); + + virtual std::wstring getSaveGameDir(); + + virtual std::vector getPrimaryPlugins(); + + // file name of this games ini (no path) + virtual std::vector getIniFileNames(); + + virtual std::wstring getSaveGameExtension(); + virtual std::wstring getReferenceDataFile(); + virtual std::wstring getOMODExt(); + + virtual std::wstring getSteamAPPId(); + + virtual std::wstring getSEName(); + + virtual std::wstring getNexusPage(); + + static std::wstring getNexusInfoUrlStatic(); + virtual std::wstring getNexusInfoUrl() { return SkyrimInfo::getNexusInfoUrlStatic(); } + static int getNexusModIDStatic(); + virtual int getNexusModID() { return SkyrimInfo::getNexusModIDStatic(); } + + virtual void createProfile(const std::wstring &directory, bool useDefaults); + virtual void repairProfile(const std::wstring &directory); + + virtual bool rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath); + + // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to + // the game directory + virtual std::vector getExecutables(); + + virtual std::wstring archiveListKey() { return L"SResourceArchiveList"; } + +private: + + SkyrimInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory); + + static bool identifyGame(const std::wstring &searchPath); + +}; + +#endif // SKYRIMINFO_H diff --git a/src/shared/util.cpp b/src/shared/util.cpp new file mode 100644 index 00000000..ec52dd38 --- /dev/null +++ b/src/shared/util.cpp @@ -0,0 +1,119 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "util.h" +#include "windows_error.h" +#include +#include + + +bool FileExists(const std::wstring &filename) +{ + WIN32_FIND_DATAW findData; + ZeroMemory(&findData, sizeof(WIN32_FIND_DATAW)); + HANDLE search = ::FindFirstFileExW(filename.c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, 0); + if (search == INVALID_HANDLE_VALUE) { + return false; + } else { + FindClose(search); + return true; + } +} + +bool FileExists(const std::wstring &searchPath, const std::wstring &filename) +{ + std::wstringstream stream; + stream << searchPath << "\\" << filename; + return FileExists(stream.str()); +} + +std::string ToString(const std::wstring &source, bool utf8) +{ + char buffer[MAX_PATH]; + if (utf8) { + ::WideCharToMultiByte(CP_UTF8, 0, source.c_str(), -1, buffer, MAX_PATH, NULL, NULL); + } else { + ::WideCharToMultiByte(GetACP(), 0, source.c_str(), -1, buffer, MAX_PATH, NULL, NULL); +// wcstombs(buffer, source.c_str(), MAX_PATH); + } + return std::string(buffer); +} + +std::wstring ToWString(const std::string &source, bool utf8) +{ + wchar_t buffer[MAX_PATH]; + if (utf8) { + ::MultiByteToWideChar(CP_UTF8, 0, source.c_str(), -1, buffer, MAX_PATH); + } else { + // codepage encoding + ::MultiByteToWideChar(GetACP(), 0, source.c_str(), -1, buffer, MAX_PATH); +// mbstowcs(buffer, source.c_str(), MAX_PATH); + } + return std::wstring(buffer); +} + +std::string &ToLower(std::string &text) +{ + std::transform(text.begin(), text.end(), text.begin(), tolower); + return text; +} + + +std::wstring &ToLower(std::wstring &text) +{ + std::transform(text.begin(), text.end(), text.begin(), towlower); + return text; +} + +std::wstring ToLower(const std::wstring &text) +{ + std::wstring temp = text; + + std::transform(temp.begin(), temp.end(), temp.begin(), towlower); + return temp; +} + +VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName) +{ + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), NULL); + if (size == 0) { + throw windows_error("failed to determine file version info size"); + } + + void *buffer = new char[size]; + try { + if (!::GetFileVersionInfoW(fileName.c_str(), 0UL, size, buffer)) { + throw windows_error("failed to determine file version info"); + } + + void *versionInfoPtr = NULL; + UINT versionInfoLength = 0; + if (!::VerQueryValue(buffer, L"\\", &versionInfoPtr, &versionInfoLength)) { + throw windows_error("failed to determine file version"); + } + + VS_FIXEDFILEINFO result = *(VS_FIXEDFILEINFO*)versionInfoPtr; + delete [] buffer; + return result; + } catch (...) { + delete [] buffer; + throw; + } +} + diff --git a/src/shared/util.h b/src/shared/util.h new file mode 100644 index 00000000..7f44b55d --- /dev/null +++ b/src/shared/util.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef UTIL_H +#define UTIL_H + + +#include +#define WIN32_LEAN_AND_MEAN +#include + + +static const int MAXPATH_UNICODE = 32767; + + +bool FileExists(const std::wstring &filename); + +bool FileExists(const std::wstring &searchPath, const std::wstring &filename); + +std::string ToString(const std::wstring &source, bool utf8); +std::wstring ToWString(const std::string &source, bool utf8); + +std::string &ToLower(std::string &text); +std::wstring &ToLower(std::wstring &text); + +std::wstring ToLower(const std::wstring &text); + +VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName); + +#endif // UTIL_H diff --git a/src/shared/windows_error.cpp b/src/shared/windows_error.cpp new file mode 100644 index 00000000..ad91db44 --- /dev/null +++ b/src/shared/windows_error.cpp @@ -0,0 +1,45 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "windows_error.h" +#include + +std::string windows_error::constructMessage(const std::string& input, int inErrorCode) +{ + std::ostringstream finalMessage; + finalMessage << input; + + LPSTR buffer = NULL; + + DWORD errorCode = inErrorCode != -1 ? inErrorCode : ::GetLastError(); + + // TODO: the message is not english? + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, NULL) == 0) { + finalMessage << " (errorcode " << errorCode << ")"; + } else { + LPSTR lastChar = buffer + strlen(buffer) - 2; + *lastChar = '\0'; + finalMessage << " (" << buffer << " [" << errorCode << "])"; + LocalFree(buffer); // allocated by FormatMessage + } + + ::SetLastError(errorCode); // restore error code because FormatMessage might have modified it + return finalMessage.str(); +} diff --git a/src/shared/windows_error.h b/src/shared/windows_error.h new file mode 100644 index 00000000..879ee1d2 --- /dev/null +++ b/src/shared/windows_error.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef WINDOWS_ERROR_H +#define WINDOWS_ERROR_H + + +#include +#define WIN32_LEAN_AND_MEAN +#include + +class windows_error : public std::runtime_error { +public: + windows_error(const std::string& message, int errorcode = -1) + : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) + {} + int getErrorCode() const { return m_ErrorCode; } +private: + std::string constructMessage(const std::string& input, int errorcode); +private: + int m_ErrorCode; +}; + +#endif // WINDOWS_ERROR_H -- cgit v1.3.1