forked from teamnwah/openmw-tes3coop
Compare commits
153 Commits
0.7.0
...
new-script
Author | SHA1 | Date |
---|---|---|
David Cernat | c2f330d4f1 | 7 years ago |
David Cernat | fc5e883160 | 7 years ago |
David Cernat | 993cc3dfd6 | 7 years ago |
David Cernat | d1ad0c91f8 | 7 years ago |
David Cernat | 7e322f1f8b | 7 years ago |
David Cernat | bd9e8bd10f | 7 years ago |
David Cernat | 711bdf187a | 7 years ago |
David Cernat | 720ef5f6c5 | 7 years ago |
David Cernat | eff3504b05 | 7 years ago |
Koncord | b55bb445dd | 7 years ago |
Koncord | a546d99000 | 7 years ago |
Koncord | 1841562553 | 7 years ago |
Koncord | 744b8cf168 | 7 years ago |
Koncord | e44fcdc0b3 | 7 years ago |
David Cernat | 901fe72471 | 7 years ago |
David Cernat | a796f81444 | 7 years ago |
Koncord | 46d55816b8 | 7 years ago |
Koncord | 4ebfcc4a21 | 7 years ago |
Koncord | 729f6e745e | 7 years ago |
Koncord | fef3764eb1 | 7 years ago |
Koncord | 382f56178c | 7 years ago |
Koncord | e657934cef | 7 years ago |
Koncord | c276ff4bd9 | 7 years ago |
David Cernat | 0a35b897be | 7 years ago |
David Cernat | b0965f094a | 7 years ago |
David Cernat | ef79a98544 | 7 years ago |
David Cernat | 64b57983f0 | 7 years ago |
David Cernat | 606ddff813 | 7 years ago |
David Cernat | cac4684986 | 7 years ago |
David Cernat | 07d75abdf8 | 7 years ago |
David Cernat | 1ee460bba8 | 7 years ago |
David Cernat | d33254f287 | 7 years ago |
David Cernat | 010a80ceca | 7 years ago |
David Cernat | 947b3f76be | 7 years ago |
David Cernat | 6f822f54aa | 7 years ago |
David Cernat | a3e2ab4d4e | 7 years ago |
David Cernat | 4cc0216e0a | 7 years ago |
David Cernat | 80be664139 | 7 years ago |
David Cernat | 57a0415ba3 | 7 years ago |
David Cernat | 494b10b97e | 7 years ago |
David Cernat | ba161ddddd | 7 years ago |
David Cernat | 7788821a69 | 7 years ago |
David Cernat | 068f733d1e | 7 years ago |
David Cernat | 1272b03f25 | 7 years ago |
David Cernat | b4e8560698 | 7 years ago |
David Cernat | 926106cf8c | 7 years ago |
David Cernat | ac7a588632 | 7 years ago |
David Cernat | a21f5d18d6 | 7 years ago |
David Cernat | a8261bb385 | 7 years ago |
David Cernat | 700e4d032e | 7 years ago |
David Cernat | 4dbada69bf | 7 years ago |
Koncord | ca7f3f7450 | 7 years ago |
Koncord | 64b531aa3c | 7 years ago |
Koncord | f377164db9 | 7 years ago |
Koncord | 7ab01b66e4 | 7 years ago |
Koncord | d15c674584 | 7 years ago |
Koncord | 0da44f69ad | 7 years ago |
Koncord | 062d6a1824 | 7 years ago |
David Cernat | 29cb51cdce | 7 years ago |
Koncord | 1d111fdca8 | 7 years ago |
Koncord | bd7082f57e | 7 years ago |
David Cernat | 71c921faa7 | 7 years ago |
David Cernat | 5653d07c7b | 7 years ago |
Koncord | 948090676a | 7 years ago |
David Cernat | 61db22f5ae | 7 years ago |
David Cernat | b7e5e77166 | 7 years ago |
David Cernat | 378d30834b | 7 years ago |
Koncord | 14d47213ef | 7 years ago |
Koncord | 3495fd43f4 | 7 years ago |
Koncord | e7a5919477 | 7 years ago |
Koncord | 1aa630e4a9 | 7 years ago |
Koncord | e8915f8ec5 | 7 years ago |
Koncord | 14fdec2478 | 7 years ago |
David Cernat | b801cf2c9e | 7 years ago |
David Cernat | 878294e4fe | 7 years ago |
Koncord | d44848ecbb | 7 years ago |
Koncord | 05abb8ace3 | 7 years ago |
Koncord | 04a844a9c0 | 7 years ago |
Koncord | dad0b38f25 | 7 years ago |
Koncord | a3d5fbbdcd | 7 years ago |
Koncord | 916ada108f | 7 years ago |
David Cernat | bece095579 | 7 years ago |
David Cernat | d6dc75e94b | 7 years ago |
David Cernat | 76a4abd7c0 | 7 years ago |
David Cernat | 0e73571111 | 7 years ago |
David Cernat | 8a93631e08 | 7 years ago |
Koncord | ba8613a179 | 7 years ago |
David Cernat | fb67180809 | 7 years ago |
Koncord | 4530370e52 | 7 years ago |
Koncord | ce6a4e4032 | 7 years ago |
Koncord | fc3f2483ee | 7 years ago |
Koncord | dffd3bfa7d | 7 years ago |
Koncord | 7a0b45d456 | 7 years ago |
Koncord | 66283943c5 | 7 years ago |
Koncord | d702845026 | 7 years ago |
Koncord | 0a0c9893b1 | 7 years ago |
David Cernat | 6e7c033a5d | 7 years ago |
David Cernat | bbac26294f | 7 years ago |
David Cernat | b20df4b7cd | 7 years ago |
David Cernat | 17a8e32782 | 7 years ago |
Koncord | 1fd16ba69c | 7 years ago |
Koncord | 62588ce088 | 7 years ago |
Koncord | 6cb9c3c713 | 7 years ago |
Koncord | 3839a2dcfd | 7 years ago |
Koncord | ed75563a94 | 7 years ago |
Koncord | 01a5196a92 | 7 years ago |
Koncord | 15723adb9a | 7 years ago |
Koncord | 57353cdfff | 7 years ago |
David Cernat | fe9a3088bd | 7 years ago |
Koncord | 5c79e7106f | 7 years ago |
David Cernat | 4845599bda | 7 years ago |
David Cernat | 7a38a0b223 | 7 years ago |
Koncord | cbabc91b06 | 7 years ago |
Koncord | 846f83e3e4 | 7 years ago |
David Cernat | 841a4f90c1 | 7 years ago |
David Cernat | 3284769fef | 7 years ago |
David Cernat | b5ce3cebbc | 7 years ago |
Koncord | 73aa83aa03 | 7 years ago |
Koncord | 5fcdff843c | 7 years ago |
David Cernat | 76f1a61538 | 7 years ago |
David Cernat | d591180e99 | 7 years ago |
David Cernat | 381a5fabc4 | 7 years ago |
David Cernat | 9838cc680a | 7 years ago |
David Cernat | 98195b5e3c | 7 years ago |
Koncord | 3124e627cf | 7 years ago |
Koncord | c2286482ae | 7 years ago |
Koncord | f814ae795d | 7 years ago |
Koncord | 50ef9677fe | 7 years ago |
Koncord | 838e05521e | 7 years ago |
David Cernat | cccbe753d7 | 7 years ago |
Koncord | 6decd148e5 | 7 years ago |
Koncord | 045dc566ea | 7 years ago |
Koncord | bb183457a6 | 7 years ago |
Koncord | dc18916f46 | 7 years ago |
Koncord | f99dafbf51 | 7 years ago |
Koncord | aee6fb1265 | 7 years ago |
Koncord | d1388cdf84 | 7 years ago |
Koncord | b869fe0b76 | 7 years ago |
Koncord | 0e2817da88 | 7 years ago |
Koncord | fc8232f943 | 7 years ago |
Koncord | 26324c2578 | 7 years ago |
Koncord | 4e93905350 | 7 years ago |
Koncord | 510e657c93 | 7 years ago |
Koncord | 060ebe3d4a | 7 years ago |
Koncord | 1c0adc47ee | 7 years ago |
Koncord | 66fdba957b | 7 years ago |
Koncord | 1d16958910 | 7 years ago |
Koncord | b18c6dec9d | 7 years ago |
Koncord | 991a1fe8d8 | 7 years ago |
Koncord | 60fc0bedb8 | 7 years ago |
Koncord | 7717f9bece | 7 years ago |
Koncord | fe2dd1bad4 | 7 years ago |
Koncord | 2d0840cb3a | 7 years ago |
@ -1,69 +0,0 @@
|
||||
stages:
|
||||
- build
|
||||
|
||||
Debian:
|
||||
tags:
|
||||
- docker
|
||||
- linux
|
||||
image: gcc
|
||||
cache:
|
||||
key: apt-cache
|
||||
paths:
|
||||
- apt-cache/
|
||||
before_script:
|
||||
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
|
||||
- apt-get update -yq
|
||||
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
|
||||
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
|
||||
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
|
||||
stage: build
|
||||
script:
|
||||
- cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi
|
||||
- mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../
|
||||
- make -j$cores_to_use
|
||||
- DESTDIR=artifacts make install
|
||||
artifacts:
|
||||
paths:
|
||||
- build/artifacts/
|
||||
MacOS:
|
||||
tags:
|
||||
- macos
|
||||
- xcode
|
||||
except:
|
||||
- branches # because our CI VMs are not public, MRs can't use them and timeout
|
||||
stage: build
|
||||
allow_failure: true
|
||||
script:
|
||||
- rm -fr build/* # remove anything in the build directory
|
||||
- CI/before_install.osx.sh
|
||||
- CI/before_script.osx.sh
|
||||
- cd build; make -j2 package
|
||||
artifacts:
|
||||
paths:
|
||||
- build/OpenMW-*.dmg
|
||||
|
||||
Windows:
|
||||
tags:
|
||||
- win10
|
||||
- msvc2017
|
||||
except:
|
||||
- branches # because our CI VMs are not public, MRs can't use them and timeout
|
||||
stage: build
|
||||
allow_failure: true
|
||||
script:
|
||||
# - env # turn on for debugging
|
||||
- sh %CI_PROJECT_DIR%/CI/before_script.msvc.sh -c Release -p x64 -v 2017 -V
|
||||
- SET msBuildLocation="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
|
||||
- call %msBuildLocation% MSVC2017_64\OpenMW.sln /t:Build /p:Configuration=Release /m:%NUMBER_OF_PROCESSORS%
|
||||
- 7z a OpenMW_MSVC2017_64_%CI_BUILD_REF_NAME%_%CI_BUILD_ID%.zip %CI_PROJECT_DIR%\MSVC2017_64\Release\
|
||||
cache:
|
||||
paths:
|
||||
- deps
|
||||
artifacts:
|
||||
paths:
|
||||
- "*.zip"
|
@ -1,3 +1,6 @@
|
||||
[submodule "extern/breakpad"]
|
||||
path = extern/breakpad
|
||||
url = https://chromium.googlesource.com/breakpad/breakpad
|
||||
[submodule "extern/sol"]
|
||||
path = extern/sol
|
||||
url = https://github.com/ThePhD/sol2
|
||||
|
@ -1,176 +0,0 @@
|
||||
#include "advancedpage.hpp"
|
||||
|
||||
#include <components/config/gamesettings.hpp>
|
||||
#include <components/config/launchersettings.hpp>
|
||||
#include <QFileDialog>
|
||||
#include <QCompleter>
|
||||
#include <components/contentselector/view/contentselector.hpp>
|
||||
#include <components/contentselector/model/esmfile.hpp>
|
||||
|
||||
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
||||
Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, mCfgMgr(cfg)
|
||||
, mGameSettings(gameSettings)
|
||||
, mEngineSettings(engineSettings)
|
||||
{
|
||||
setObjectName ("AdvancedPage");
|
||||
setupUi(this);
|
||||
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
|
||||
// Set up an auto-completer for the "Start default character at" field
|
||||
auto *completer = new QCompleter(cellNames);
|
||||
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
|
||||
startDefaultCharacterAtField->setCompleter(completer);
|
||||
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) {
|
||||
startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked);
|
||||
startDefaultCharacterAtField->setEnabled(state == Qt::Checked);
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
|
||||
{
|
||||
QString scriptFile = QFileDialog::getOpenFileName(
|
||||
this,
|
||||
QObject::tr("Select script file"),
|
||||
QDir::currentPath(),
|
||||
QString(tr("Text file (*.txt)")));
|
||||
|
||||
if (scriptFile.isEmpty())
|
||||
return;
|
||||
|
||||
QFileInfo info(scriptFile);
|
||||
|
||||
if (!info.exists() || !info.isReadable())
|
||||
return;
|
||||
|
||||
const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
|
||||
runScriptAfterStartupField->setText(path);
|
||||
}
|
||||
|
||||
bool Launcher::AdvancedPage::loadSettings()
|
||||
{
|
||||
// Testing
|
||||
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
|
||||
if (skipMenu) {
|
||||
skipMenuCheckBox->setCheckState(Qt::Checked);
|
||||
}
|
||||
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
||||
startDefaultCharacterAtField->setEnabled(skipMenu);
|
||||
|
||||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
||||
|
||||
// Game Settings
|
||||
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
||||
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
||||
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
||||
loadSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
|
||||
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
||||
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
||||
|
||||
// Input Settings
|
||||
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
||||
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
||||
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
||||
|
||||
// Saves Settings
|
||||
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
|
||||
|
||||
// User Interface Settings
|
||||
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
||||
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
||||
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
||||
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
|
||||
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
|
||||
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid.
|
||||
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
|
||||
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
|
||||
|
||||
// Other Settings
|
||||
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
|
||||
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
||||
screenshotFormatComboBox->addItem(screenshotFormatString);
|
||||
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::saveSettings()
|
||||
{
|
||||
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
|
||||
// user settings file (which by definition should only contain settings the user has touched)
|
||||
|
||||
// Testing
|
||||
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
||||
if (skipMenu != mGameSettings.value("skip-menu").toInt())
|
||||
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
|
||||
|
||||
QString startCell = startDefaultCharacterAtField->text();
|
||||
if (startCell != mGameSettings.value("start")) {
|
||||
mGameSettings.setValue("start", startCell);
|
||||
}
|
||||
QString scriptRun = runScriptAfterStartupField->text();
|
||||
if (scriptRun != mGameSettings.value("script-run"))
|
||||
mGameSettings.setValue("script-run", scriptRun);
|
||||
|
||||
// Game Settings
|
||||
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
||||
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
||||
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
||||
saveSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
|
||||
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
||||
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
||||
|
||||
// Input Settings
|
||||
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
||||
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
||||
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
||||
|
||||
// Saves Settings
|
||||
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||
int maximumQuicksaves = maximumQuicksavesComboBox->value();
|
||||
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) {
|
||||
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
|
||||
}
|
||||
|
||||
// User Interface Settings
|
||||
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
||||
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
||||
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
||||
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
|
||||
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
|
||||
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
|
||||
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
|
||||
|
||||
// Other Settings
|
||||
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
||||
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
|
||||
mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) {
|
||||
if (mEngineSettings.getBool(setting, group))
|
||||
checkbox->setCheckState(Qt::Checked);
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) {
|
||||
bool cValue = checkbox->checkState();
|
||||
if (cValue != mEngineSettings.getBool(setting, group))
|
||||
mEngineSettings.setBool(setting, group, cValue);
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
|
||||
{
|
||||
loadCellsForAutocomplete(cellNames);
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
#ifndef ADVANCEDPAGE_H
|
||||
#define ADVANCEDPAGE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui_advancedpage.h"
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
namespace Files { struct ConfigurationManager; }
|
||||
namespace Config { class GameSettings; }
|
||||
|
||||
namespace Launcher
|
||||
{
|
||||
class AdvancedPage : public QWidget, private Ui::AdvancedPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent = 0);
|
||||
|
||||
bool loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
public slots:
|
||||
void slotLoadedCellsChanged(QStringList cellNames);
|
||||
|
||||
private slots:
|
||||
void on_skipMenuCheckBox_stateChanged(int state);
|
||||
void on_runScriptAfterStartupBrowseButton_clicked();
|
||||
|
||||
private:
|
||||
Files::ConfigurationManager &mCfgMgr;
|
||||
Config::GameSettings &mGameSettings;
|
||||
Settings::Manager &mEngineSettings;
|
||||
|
||||
/**
|
||||
* Load the cells associated with the given content files for use in autocomplete
|
||||
* @param filePaths the file paths of the content files to be examined
|
||||
*/
|
||||
void loadCellsForAutocomplete(QStringList filePaths);
|
||||
void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
||||
void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
||||
};
|
||||
}
|
||||
#endif
|
@ -1,48 +0,0 @@
|
||||
#include "cellnameloader.hpp"
|
||||
|
||||
#include <components/esm/loadcell.hpp>
|
||||
#include <components/contentselector/view/contentselector.hpp>
|
||||
|
||||
QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
|
||||
{
|
||||
QSet<QString> cellNames;
|
||||
ESM::ESMReader esmReader;
|
||||
|
||||
// Loop through all content files
|
||||
for (auto &contentPath : contentPaths) {
|
||||
esmReader.open(contentPath.toStdString());
|
||||
|
||||
// Loop through all records
|
||||
while(esmReader.hasMoreRecs())
|
||||
{
|
||||
ESM::NAME recordName = esmReader.getRecName();
|
||||
esmReader.getRecHeader();
|
||||
|
||||
if (isCellRecord(recordName)) {
|
||||
QString cellName = getCellName(esmReader);
|
||||
if (!cellName.isEmpty()) {
|
||||
cellNames.insert(cellName);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop loading content for this record and continue to the next
|
||||
esmReader.skipRecord();
|
||||
}
|
||||
}
|
||||
|
||||
return cellNames;
|
||||
}
|
||||
|
||||
bool CellNameLoader::isCellRecord(ESM::NAME &recordName)
|
||||
{
|
||||
return recordName.intval == ESM::REC_CELL;
|
||||
}
|
||||
|
||||
QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
|
||||
{
|
||||
ESM::Cell cell;
|
||||
bool isDeleted = false;
|
||||
cell.loadNameAndData(esmReader, isDeleted);
|
||||
|
||||
return QString::fromStdString(cell.mName);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
#ifndef OPENMW_CELLNAMELOADER_H
|
||||
#define OPENMW_CELLNAMELOADER_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
|
||||
namespace ESM {class ESMReader; struct Cell;}
|
||||
namespace ContentSelectorView {class ContentSelector;}
|
||||
|
||||
class CellNameLoader {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns the names of all cells contained within the given content files
|
||||
* @param contentPaths the file paths of each content file to be examined
|
||||
* @return the names of all cells
|
||||
*/
|
||||
QSet<QString> getCellNames(QStringList &contentPaths);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns whether or not the given record is of type "Cell"
|
||||
* @param name The name associated with the record
|
||||
* @return whether or not the given record is of type "Cell"
|
||||
*/
|
||||
bool isCellRecord(ESM::NAME &name);
|
||||
|
||||
/**
|
||||
* Returns the name of the cell
|
||||
* @param esmReader the reader currently pointed to a loaded cell
|
||||
* @return the name of the cell
|
||||
*/
|
||||
QString getCellName(ESM::ESMReader &esmReader);
|
||||
};
|
||||
|
||||
|
||||
#endif //OPENMW_CELLNAMELOADER_H
|
@ -0,0 +1,69 @@
|
||||
//
|
||||
// Created by koncord on 04.09.17.
|
||||
//
|
||||
|
||||
#include "AdminRest.hpp"
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "RestUtils.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace chrono;
|
||||
using namespace boost::property_tree;
|
||||
|
||||
|
||||
AdminRest::AdminRest(const std::string &cert, const std::string &key, const std::string &verifyFile,
|
||||
unsigned short port, std::shared_ptr<MasterServer> master) : httpServer(cert, key, verifyFile), master(master)
|
||||
{
|
||||
httpServer.config.port = port;
|
||||
}
|
||||
|
||||
void AdminRest::start()
|
||||
{
|
||||
static const string AdminArea = "^/api/admin?";
|
||||
|
||||
httpServer.resource[AdminArea]["POST"] = [this](auto response, auto request) {
|
||||
cout << request->method << endl;
|
||||
cout << request->path << endl;
|
||||
cout << request->http_version << endl;
|
||||
|
||||
for (auto &header : request->header)
|
||||
cout << header.first << ": " << header.second << endl;
|
||||
|
||||
string resp;
|
||||
master->luaStuff([&request, &response, &resp](sol::state &state) {
|
||||
sol::protected_function func = state["OnAdminRequest"];
|
||||
|
||||
sol::protected_function_result result = func.call(request->remote_endpoint_address, request->content.string());
|
||||
if (result.valid())
|
||||
*response << result.get<string>();
|
||||
else
|
||||
{
|
||||
cerr << "Error: " << result.get<string>() << endl;
|
||||
*response << response500;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/*httpServer.on_error = [](auto request, const boost::system::error_code& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.message() << " " << err.category().name() << std::endl;
|
||||
};*/
|
||||
|
||||
httpServer.default_resource["GET"] = [](auto response, auto /*request*/) {
|
||||
cout << "Default request" << endl;
|
||||
*response << response400;
|
||||
};
|
||||
|
||||
thr = thread([this](){httpServer.start();});
|
||||
}
|
||||
|
||||
void AdminRest::stop()
|
||||
{
|
||||
httpServer.stop();
|
||||
if(thr.joinable())
|
||||
thr.join();
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by koncord on 04.09.17.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SimpleWeb/https_server.hpp"
|
||||
#include "MasterServer.hpp"
|
||||
|
||||
typedef SimpleWeb::Server<SimpleWeb::HTTPS> HttpsServer;
|
||||
|
||||
class AdminRest
|
||||
{
|
||||
public:
|
||||
AdminRest(const std::string &cert, const std::string &key, const std::string &verifyFile, unsigned short port, std::shared_ptr<MasterServer> master);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
HttpsServer httpServer;
|
||||
std::shared_ptr<MasterServer> master;
|
||||
std::thread thr;
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by koncord on 04.09.17.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "SimpleWeb/base_server.hpp"
|
||||
|
||||
static std::string response201 = "HTTP/1.1 201 Created\r\nContent-Length: 7\r\n\r\nCreated";
|
||||
static std::string response202 = "HTTP/1.1 202 Accepted\r\nContent-Length: 8\r\n\r\nAccepted";
|
||||
static std::string response400 = "HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\n\r\nbad request";
|
||||
|
||||
static std::string response403 = "HTTP/1.1 403 Forbidden\r\nContent-Length: 9\r\n\r\nForbidden";
|
||||
static std::string response500 = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 21\r\n\r\nInternal Server Error";
|
||||
|
||||
template <class Protocol>
|
||||
inline void ResponseStr(std::shared_ptr<typename SimpleWeb::ServerBase<Protocol>::Response> response,
|
||||
std::string content, std::string type = "", std::string code = "200 OK")
|
||||
{
|
||||
*response << "HTTP/1.1 " << code << "\r\n";
|
||||
if (!type.empty())
|
||||
*response << "Content-Type: " << type <<"\r\n";
|
||||
*response << "Content-Length: " << content.length() << "\r\n\r\n" << content;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2016 Ole Christian Eidheim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,55 +1,42 @@
|
||||
/*
|
||||
* https://github.com/eidheim/Simple-Web-Server/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2014-2016 Ole Christian Eidheim
|
||||
*/
|
||||
|
||||
#ifndef SERVER_HTTP_HPP
|
||||
#define SERVER_HTTP_HPP
|
||||
#pragma once
|
||||
|
||||
#include "base_server.hpp"
|
||||
|
||||
namespace SimpleWeb
|
||||
{
|
||||
namespace SimpleWeb {
|
||||
|
||||
template<class socket_type>
|
||||
template <class socket_type>
|
||||
class Server : public ServerBase<socket_type> {};
|
||||
|
||||
typedef boost::asio::ip::tcp::socket HTTP;
|
||||
using HTTP = asio::ip::tcp::socket;
|
||||
|
||||
template<>
|
||||
class Server<HTTP> : public ServerBase<HTTP>
|
||||
{
|
||||
template <>
|
||||
class Server<HTTP> : public ServerBase<HTTP> {
|
||||
public:
|
||||
Server() : ServerBase<HTTP>::ServerBase(80)
|
||||
{}
|
||||
Server() noexcept : ServerBase<HTTP>::ServerBase(80) {}
|
||||
|
||||
protected:
|
||||
virtual void accept()
|
||||
{
|
||||
//Create new socket for this connection
|
||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
auto socket = std::make_shared<HTTP>(*io_service);
|
||||
|
||||
acceptor->async_accept(*socket, [this, socket](const boost::system::error_code &ec)
|
||||
{
|
||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
||||
if (ec != boost::asio::error::operation_aborted)
|
||||
accept();
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
boost::asio::ip::tcp::no_delay option(true);
|
||||
socket->set_option(option);
|
||||
|
||||
this->read_request_and_content(socket);
|
||||
void accept() override {
|
||||
auto session = std::make_shared<Session>(create_connection(*io_service));
|
||||
|
||||
acceptor->async_accept(*session->connection->socket, [this, session](const error_code &ec) {
|
||||
auto lock = session->connection->handler_runner->continue_lock();
|
||||
if(!lock)
|
||||
return;
|
||||
|
||||
// Immediately start accepting a new connection (unless io_service has been stopped)
|
||||
if(ec != asio::error::operation_aborted)
|
||||
this->accept();
|
||||
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
error_code ec;
|
||||
session->connection->socket->set_option(option, ec);
|
||||
|
||||
this->read_request_and_content(session);
|
||||
}
|
||||
else if (on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
else if(this->on_error)
|
||||
this->on_error(session->request, ec);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SERVER_HTTP_HPP
|
||||
} // namespace SimpleWeb
|
||||
|
@ -1,91 +1,82 @@
|
||||
#ifndef HTTPS_SERVER_HPP
|
||||
#define HTTPS_SERVER_HPP
|
||||
#pragma once
|
||||
|
||||
#include "base_server.hpp"
|
||||
|
||||
#ifdef USE_STANDALONE_ASIO
|
||||
#include <asio/ssl.hpp>
|
||||
#else
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace SimpleWeb
|
||||
{
|
||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;
|
||||
namespace SimpleWeb {
|
||||
using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||
|
||||
template<>
|
||||
class Server<HTTPS> : public ServerBase<HTTPS>
|
||||
{
|
||||
template <>
|
||||
class Server<HTTPS> : public ServerBase<HTTPS> {
|
||||
std::string session_id_context;
|
||||
bool set_session_id_context = false;
|
||||
|
||||
public:
|
||||
Server(const std::string &cert_file, const std::string &private_key_file,
|
||||
const std::string &verify_file = std::string()) : ServerBase<HTTPS>::ServerBase(443),
|
||||
context(boost::asio::ssl::context::tlsv12)
|
||||
{
|
||||
Server(const std::string &cert_file, const std::string &private_key_file, const std::string &verify_file = std::string())
|
||||
: ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {
|
||||
context.use_certificate_chain_file(cert_file);
|
||||
context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
|
||||
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||
|
||||
if (verify_file.size() > 0)
|
||||
{
|
||||
if(verify_file.size() > 0) {
|
||||
context.load_verify_file(verify_file);
|
||||
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert |
|
||||
boost::asio::ssl::verify_client_once);
|
||||
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);
|
||||
set_session_id_context = true;
|
||||
}
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
if (set_session_id_context)
|
||||
{
|
||||
void start() override {
|
||||
if(set_session_id_context) {
|
||||
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
||||
session_id_context = std::to_string(config.port) + ':';
|
||||
session_id_context.append(config.address.rbegin(), config.address.rend());
|
||||
SSL_CTX_set_session_id_context(context.native_handle(),
|
||||
reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
||||
std::min<size_t>(session_id_context.size(),
|
||||
SSL_MAX_SSL_SESSION_ID_LENGTH));
|
||||
SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
||||
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
|
||||
}
|
||||
ServerBase::start();
|
||||
}
|
||||
|
||||
protected:
|
||||
boost::asio::ssl::context context;
|
||||
asio::ssl::context context;
|
||||
|
||||
virtual void accept()
|
||||
{
|
||||
//Create new socket for this connection
|
||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
||||
auto socket = std::make_shared<HTTPS>(*io_service, context);
|
||||
void accept() override {
|
||||
auto session = std::make_shared<Session>(create_connection(*io_service, context));
|
||||
|
||||
acceptor->async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code &ec)
|
||||
{
|
||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
||||
if (ec != boost::asio::error::operation_aborted)
|
||||
accept();
|
||||
acceptor->async_accept(session->connection->socket->lowest_layer(), [this, session](const error_code &ec) {
|
||||
auto lock = session->connection->handler_runner->continue_lock();
|
||||
if(!lock)
|
||||
return;
|
||||
|
||||
if(ec != asio::error::operation_aborted)
|
||||
this->accept();
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
boost::asio::ip::tcp::no_delay option(true);
|
||||
socket->lowest_layer().set_option(option);
|
||||
if(!ec) {
|
||||
asio::ip::tcp::no_delay option(true);
|
||||
error_code ec;
|
||||
session->connection->socket->lowest_layer().set_option(option, ec);
|
||||
|
||||
//Set timeout on the following boost::asio::ssl::stream::async_handshake
|
||||
auto timer = get_timeout_timer(socket, config.timeout_request);
|
||||
socket->async_handshake(boost::asio::ssl::stream_base::server, [this, socket, timer]
|
||||
(const boost::system::error_code &ec)
|
||||
{
|
||||
if (timer)
|
||||
timer->cancel();
|
||||
if (!ec)
|
||||
read_request_and_content(socket);
|
||||
else if (on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
session->connection->set_timeout(config.timeout_request);
|
||||
session->connection->socket->async_handshake(asio::ssl::stream_base::server, [this, session](const error_code &ec) {
|
||||
session->connection->cancel_timeout();
|
||||
auto lock = session->connection->handler_runner->continue_lock();
|
||||
if(!lock)
|
||||
return;
|
||||
if(!ec)
|
||||
this->read_request_and_content(session);
|
||||
else if(this->on_error)
|
||||
this->on_error(session->request, ec);
|
||||
});
|
||||
}
|
||||
else if (on_error)
|
||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
||||
else if(this->on_error)
|
||||
this->on_error(session->request, ec);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //HTTPS_SERVER_HPP
|
||||
} // namespace SimpleWeb
|
||||
|
@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace SimpleWeb {
|
||||
enum class StatusCode {
|
||||
unknown = 0,
|
||||
information_continue = 100,
|
||||
information_switching_protocols,
|
||||
information_processing,
|
||||
success_ok = 200,
|
||||
success_created,
|
||||
success_accepted,
|
||||
success_non_authoritative_information,
|
||||
success_no_content,
|
||||
success_reset_content,
|
||||
success_partial_content,
|
||||
success_multi_status,
|
||||
success_already_reported,
|
||||
success_im_used = 226,
|
||||
redirection_multiple_choices = 300,
|
||||
redirection_moved_permanently,
|
||||
redirection_found,
|
||||
redirection_see_other,
|
||||
redirection_not_modified,
|
||||
redirection_use_proxy,
|
||||
redirection_switch_proxy,
|
||||
redirection_temporary_redirect,
|
||||
redirection_permanent_redirect,
|
||||
client_error_bad_request = 400,
|
||||
client_error_unauthorized,
|
||||
client_error_payment_required,
|
||||
client_error_forbidden,
|
||||
client_error_not_found,
|
||||
client_error_method_not_allowed,
|
||||
client_error_not_acceptable,
|
||||
client_error_proxy_authentication_required,
|
||||
client_error_request_timeout,
|
||||
client_error_conflict,
|
||||
client_error_gone,
|
||||
client_error_length_required,
|
||||
client_error_precondition_failed,
|
||||
client_error_payload_too_large,
|
||||
client_error_uri_too_long,
|
||||
client_error_unsupported_media_type,
|
||||
client_error_range_not_satisfiable,
|
||||
client_error_expectation_failed,
|
||||
client_error_im_a_teapot,
|
||||
client_error_misdirection_required = 421,
|
||||
client_error_unprocessable_entity,
|
||||
client_error_locked,
|
||||
client_error_failed_dependency,
|
||||
client_error_upgrade_required = 426,
|
||||
client_error_precondition_required = 428,
|
||||
client_error_too_many_requests,
|
||||
client_error_request_header_fields_too_large = 431,
|
||||
client_error_unavailable_for_legal_reasons = 451,
|
||||
server_error_internal_server_error = 500,
|
||||
server_error_not_implemented,
|
||||
server_error_bad_gateway,
|
||||
server_error_service_unavailable,
|
||||
server_error_gateway_timeout,
|
||||
server_error_http_version_not_supported,
|
||||
server_error_variant_also_negotiates,
|
||||
server_error_insufficient_storage,
|
||||
server_error_loop_detected,
|
||||
server_error_not_extended = 510,
|
||||
server_error_network_authentication_required
|
||||
};
|
||||
|
||||
const static std::vector<std::pair<StatusCode, std::string>> &status_codes() noexcept {
|
||||
const static std::vector<std::pair<StatusCode, std::string>> status_codes = {
|
||||
{StatusCode::unknown, ""},
|
||||
{StatusCode::information_continue, "100 Continue"},
|
||||
{StatusCode::information_switching_protocols, "101 Switching Protocols"},
|
||||
{StatusCode::information_processing, "102 Processing"},
|
||||
{StatusCode::success_ok, "200 OK"},
|
||||
{StatusCode::success_created, "201 Created"},
|
||||
{StatusCode::success_accepted, "202 Accepted"},
|
||||
{StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"},
|
||||
{StatusCode::success_no_content, "204 No Content"},
|
||||
{StatusCode::success_reset_content, "205 Reset Content"},
|
||||
{StatusCode::success_partial_content, "206 Partial Content"},
|
||||
{StatusCode::success_multi_status, "207 Multi-Status"},
|
||||
{StatusCode::success_already_reported, "208 Already Reported"},
|
||||
{StatusCode::success_im_used, "226 IM Used"},
|
||||
{StatusCode::redirection_multiple_choices, "300 Multiple Choices"},
|
||||
{StatusCode::redirection_moved_permanently, "301 Moved Permanently"},
|
||||
{StatusCode::redirection_found, "302 Found"},
|
||||
{StatusCode::redirection_see_other, "303 See Other"},
|
||||
{StatusCode::redirection_not_modified, "304 Not Modified"},
|
||||
{StatusCode::redirection_use_proxy, "305 Use Proxy"},
|
||||
{StatusCode::redirection_switch_proxy, "306 Switch Proxy"},
|
||||
{StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"},
|
||||
{StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"},
|
||||
{StatusCode::client_error_bad_request, "400 Bad Request"},
|
||||
{StatusCode::client_error_unauthorized, "401 Unauthorized"},
|
||||
{StatusCode::client_error_payment_required, "402 Payment Required"},
|
||||
{StatusCode::client_error_forbidden, "403 Forbidden"},
|
||||
{StatusCode::client_error_not_found, "404 Not Found"},
|
||||
{StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"},
|
||||
{StatusCode::client_error_not_acceptable, "406 Not Acceptable"},
|
||||
{StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"},
|
||||
{StatusCode::client_error_request_timeout, "408 Request Timeout"},
|
||||
{StatusCode::client_error_conflict, "409 Conflict"},
|
||||
{StatusCode::client_error_gone, "410 Gone"},
|
||||
{StatusCode::client_error_length_required, "411 Length Required"},
|
||||
{StatusCode::client_error_precondition_failed, "412 Precondition Failed"},
|
||||
{StatusCode::client_error_payload_too_large, "413 Payload Too Large"},
|
||||
{StatusCode::client_error_uri_too_long, "414 URI Too Long"},
|
||||
{StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"},
|
||||
{StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"},
|
||||
{StatusCode::client_error_expectation_failed, "417 Expectation Failed"},
|
||||
{StatusCode::client_error_im_a_teapot, "418 I'm a teapot"},
|
||||
{StatusCode::client_error_misdirection_required, "421 Misdirected Request"},
|
||||
{StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"},
|
||||
{StatusCode::client_error_locked, "423 Locked"},
|
||||
{StatusCode::client_error_failed_dependency, "424 Failed Dependency"},
|
||||
{StatusCode::client_error_upgrade_required, "426 Upgrade Required"},
|
||||
{StatusCode::client_error_precondition_required, "428 Precondition Required"},
|
||||
{StatusCode::client_error_too_many_requests, "429 Too Many Requests"},
|
||||
{StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"},
|
||||
{StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"},
|
||||
{StatusCode::server_error_internal_server_error, "500 Internal Server Error"},
|
||||
{StatusCode::server_error_not_implemented, "501 Not Implemented"},
|
||||
{StatusCode::server_error_bad_gateway, "502 Bad Gateway"},
|
||||
{StatusCode::server_error_service_unavailable, "503 Service Unavailable"},
|
||||
{StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"},
|
||||
{StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"},
|
||||
{StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"},
|
||||
{StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"},
|
||||
{StatusCode::server_error_loop_detected, "508 Loop Detected"},
|
||||
{StatusCode::server_error_not_extended, "510 Not Extended"},
|
||||
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
|
||||
return status_codes;
|
||||
}
|
||||
|
||||
inline StatusCode status_code(const std::string &status_code_str) noexcept {
|
||||
for(auto &status_code : status_codes()) {
|
||||
if(status_code.second == status_code_str)
|
||||
return status_code.first;
|
||||
}
|
||||
return StatusCode::unknown;
|
||||
}
|
||||
|
||||
inline const std::string &status_code(StatusCode status_code_enum) noexcept {
|
||||
for(auto &status_code : status_codes()) {
|
||||
if(status_code.first == status_code_enum)
|
||||
return status_code.second;
|
||||
}
|
||||
return status_codes()[0].second;
|
||||
}
|
||||
} // namespace SimpleWeb
|
@ -0,0 +1,340 @@
|
||||
#pragma once
|
||||
|
||||
#include "status_code.hpp"
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace SimpleWeb {
|
||||
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
|
||||
return str1.size() == str2.size() &&
|
||||
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
|
||||
return tolower(a) == tolower(b);
|
||||
});
|
||||
}
|
||||
class CaseInsensitiveEqual {
|
||||
public:
|
||||
bool operator()(const std::string &str1, const std::string &str2) const noexcept {
|
||||
return case_insensitive_equal(str1, str2);
|
||||
}
|
||||
};
|
||||
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
|
||||
class CaseInsensitiveHash {
|
||||
public:
|
||||
size_t operator()(const std::string &str) const noexcept {
|
||||
size_t h = 0;
|
||||
std::hash<int> hash;
|
||||
for(auto c : str)
|
||||
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
|
||||
|
||||
/// Percent encoding and decoding
|
||||
class Percent {
|
||||
public:
|
||||
/// Returns percent-encoded string
|
||||
static std::string encode(const std::string &value) noexcept {
|
||||
static auto hex_chars = "0123456789ABCDEF";
|
||||
|
||||
std::string result;
|
||||
result.reserve(value.size()); // Minimum size of result
|
||||
|
||||
for(auto &chr : value) {
|
||||
if(chr == ' ')
|
||||
result += '+';
|
||||
else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
|
||||
result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
|
||||
else
|
||||
result += chr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns percent-decoded string
|
||||
static std::string decode(const std::string &value) noexcept {
|
||||
std::string result;
|
||||
result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
|
||||
|
||||
for(size_t i = 0; i < value.size(); ++i) {
|
||||
auto &chr = value[i];
|
||||
if(chr == '%' && i + 2 < value.size()) {
|
||||
auto hex = value.substr(i + 1, 2);
|
||||
auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
|
||||
result += decoded_chr;
|
||||
i += 2;
|
||||
}
|
||||
else if(chr == '+')
|
||||
result += ' ';
|
||||
else
|
||||
result += chr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// Query string creation and parsing
|
||||
class QueryString {
|
||||
public:
|
||||
/// Returns query string created from given field names and values
|
||||
static std::string create(const CaseInsensitiveMultimap &fields) noexcept {
|
||||
std::string result;
|
||||
|
||||
bool first = true;
|
||||
for(auto &field : fields) {
|
||||
result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns query keys with percent-decoded values.
|
||||
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept {
|
||||
CaseInsensitiveMultimap result;
|
||||
|
||||
if(query_string.empty())
|
||||
return result;
|
||||
|
||||
size_t name_pos = 0;
|
||||
auto name_end_pos = std::string::npos;
|
||||
auto value_pos = std::string::npos;
|
||||
for(size_t c = 0; c < query_string.size(); ++c) {
|
||||
if(query_string[c] == '&') {
|
||||
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
|
||||
if(!name.empty()) {
|
||||
auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
|
||||
result.emplace(std::move(name), Percent::decode(value));
|
||||
}
|
||||
name_pos = c + 1;
|
||||
name_end_pos = std::string::npos;
|
||||
value_pos = std::string::npos;
|
||||
}
|
||||
else if(query_string[c] == '=') {
|
||||
name_end_pos = c;
|
||||
value_pos = c + 1;
|
||||
}
|
||||
}
|
||||
if(name_pos < query_string.size()) {
|
||||
auto name = query_string.substr(name_pos, name_end_pos - name_pos);
|
||||
if(!name.empty()) {
|
||||
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
|
||||
result.emplace(std::move(name), Percent::decode(value));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class HttpHeader {
|
||||
public:
|
||||
/// Parse header fields
|
||||
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
|
||||
CaseInsensitiveMultimap result;
|
||||
std::string line;
|
||||
getline(stream, line);
|
||||
size_t param_end;
|
||||
while((param_end = line.find(':')) != std::string::npos) {
|
||||
size_t value_start = param_end + 1;
|
||||
if(value_start < line.size()) {
|
||||
if(line[value_start] == ' ')
|
||||
value_start++;
|
||||
if(value_start < line.size())
|
||||
result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
|
||||
}
|
||||
|
||||
getline(stream, line);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class RequestMessage {
|
||||
public:
|
||||
/// Parse request line and header fields
|
||||
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
|
||||
header.clear();
|
||||
std::string line;
|
||||
getline(stream, line);
|
||||
size_t method_end;
|
||||
if((method_end = line.find(' ')) != std::string::npos) {
|
||||
method = line.substr(0, method_end);
|
||||
|
||||
size_t query_start = std::string::npos;
|
||||
size_t path_and_query_string_end = std::string::npos;
|
||||
for(size_t i = method_end + 1; i < line.size(); ++i) {
|
||||
if(line[i] == '?' && (i + 1) < line.size())
|
||||
query_start = i + 1;
|
||||
else if(line[i] == ' ') {
|
||||
path_and_query_string_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(path_and_query_string_end != std::string::npos) {
|
||||
if(query_start != std::string::npos) {
|
||||
path = line.substr(method_end + 1, query_start - method_end - 2);
|
||||
query_string = line.substr(query_start, path_and_query_string_end - query_start);
|
||||
}
|
||||
else
|
||||
path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
|
||||
|
||||
size_t protocol_end;
|
||||
if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
|
||||
if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
|
||||
return false;
|
||||
version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
header = HttpHeader::parse(stream);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ResponseMessage {
|
||||
public:
|
||||
/// Parse status line and header fields
|
||||
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
|
||||
header.clear();
|
||||
std::string line;
|
||||
getline(stream, line);
|
||||
size_t version_end = line.find(' ');
|
||||
if(version_end != std::string::npos) {
|
||||
if(5 < line.size())
|
||||
version = line.substr(5, version_end - 5);
|
||||
else
|
||||
return false;
|
||||
if((version_end + 1) < line.size())
|
||||
status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
|
||||
else
|
||||
return false;
|
||||
|
||||
header = HttpHeader::parse(stream);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ContentDisposition {
|
||||
public:
|
||||
/// Can be used to parse the Content-Disposition header field value when
|
||||
/// clients are posting requests with enctype="multipart/form-data"
|
||||
static CaseInsensitiveMultimap parse(const std::string &line) {
|
||||
CaseInsensitiveMultimap result;
|
||||
|
||||
size_t para_start_pos = 0;
|
||||
size_t para_end_pos = std::string::npos;
|
||||
size_t value_start_pos = std::string::npos;
|
||||
for(size_t c = 0; c < line.size(); ++c) {
|
||||
if(para_start_pos != std::string::npos) {
|
||||
if(para_end_pos == std::string::npos) {
|
||||
if(line[c] == ';') {
|
||||
result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string());
|
||||
para_start_pos = std::string::npos;
|
||||
}
|
||||
else if(line[c] == '=')
|
||||
para_end_pos = c;
|
||||
}
|
||||
else {
|
||||
if(value_start_pos == std::string::npos) {
|
||||
if(line[c] == '"' && c + 1 < line.size())
|
||||
value_start_pos = c + 1;
|
||||
}
|
||||
else if(line[c] == '"') {
|
||||
result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos));
|
||||
para_start_pos = std::string::npos;
|
||||
para_end_pos = std::string::npos;
|
||||
value_start_pos = std::string::npos;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(line[c] != ' ' && line[c] != ';')
|
||||
para_start_pos = c;
|
||||
}
|
||||
if(para_start_pos != std::string::npos && para_end_pos == std::string::npos)
|
||||
result.emplace(line.substr(para_start_pos), std::string());
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
} // namespace SimpleWeb
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <emmintrin.h>
|
||||
namespace SimpleWeb {
|
||||
inline void spin_loop_pause() noexcept { _mm_pause(); }
|
||||
} // namespace SimpleWeb
|
||||
// TODO: need verification that the following checks are correct:
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
|
||||
#include <intrin.h>
|
||||
namespace SimpleWeb {
|
||||
inline void spin_loop_pause() noexcept { _mm_pause(); }
|
||||
} // namespace SimpleWeb
|
||||
#else
|
||||
namespace SimpleWeb {
|
||||
inline void spin_loop_pause() noexcept {}
|
||||
} // namespace SimpleWeb
|
||||
#endif
|
||||
|
||||
namespace SimpleWeb {
|
||||
/// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service
|
||||
class ScopeRunner {
|
||||
/// Scope count that is set to -1 if scopes are to be canceled
|
||||
std::atomic<long> count;
|
||||
|
||||
public:
|
||||
class SharedLock {
|
||||
friend class ScopeRunner;
|
||||
std::atomic<long> &count;
|
||||
SharedLock(std::atomic<long> &count) noexcept : count(count) {}
|
||||
SharedLock &operator=(const SharedLock &) = delete;
|
||||
SharedLock(const SharedLock &) = delete;
|
||||
|
||||
public:
|
||||
~SharedLock() noexcept {
|
||||
count.fetch_sub(1);
|
||||
}
|
||||
};
|
||||
|
||||
ScopeRunner() noexcept : count(0) {}
|
||||
|
||||
/// Returns nullptr if scope should be exited, or a shared lock otherwise
|
||||
std::unique_ptr<SharedLock> continue_lock() noexcept {
|
||||
long expected = count;
|
||||
while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
|
||||
spin_loop_pause();
|
||||
|
||||
if(expected < 0)
|
||||
return nullptr;
|
||||
else
|
||||
return std::unique_ptr<SharedLock>(new SharedLock(count));
|
||||
}
|
||||
|
||||
/// Blocks until all shared locks are released, then prevents future shared locks
|
||||
void stop() noexcept {
|
||||
long expected = 0;
|
||||
while(!count.compare_exchange_weak(expected, -1)) {
|
||||
if(expected < 0)
|
||||
return;
|
||||
expected = 0;
|
||||
spin_loop_pause();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace SimpleWeb
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue