Merge remote-tracking branch 'upstream/master'

coverity_scan^2
Internecine 9 years ago
commit 17ede44628

32
.gitignore vendored

@ -5,7 +5,7 @@ CMakeCache.txt
cmake_install.cmake
Makefile
makefile
build
build*
prebuilt
## doxygen
@ -21,6 +21,7 @@ Doxygen
.project
.settings
.directory
.idea
## qt-creator
CMakeLists.txt.user*
@ -33,16 +34,36 @@ resources
## binaries
/esmtool
/mwiniimport
/omwlauncher
/openmw
/opencs
/niftest
/bsatool
/openmw-cs
/openmw-essimporter
/openmw-iniimporter
/openmw-launcher
/openmw-wizard
## generated objects
apps/openmw/config.hpp
components/version/version.hpp
apps/launcher/ui_contentselector.h
apps/launcher/ui_settingspage.h
apps/opencs/ui_contentselector.h
apps/opencs/ui_filedialog.h
apps/wizard/qrc_wizard.cxx
apps/wizard/ui_componentselectionpage.h
apps/wizard/ui_conclusionpage.h
apps/wizard/ui_existinginstallationpage.h
apps/wizard/ui_importpage.h
apps/wizard/ui_installationpage.h
apps/wizard/ui_installationtargetpage.h
apps/wizard/ui_intropage.h
apps/wizard/ui_languageselectionpage.h
apps/wizard/ui_methodselectionpage.h
components/ui_contentselector.h
docs/mainpage.hpp
docs/Doxyfile
docs/DoxyfilePages
moc_*.cxx
*.cxx_parameters
*qrc_launcher.cxx
@ -54,3 +75,6 @@ moc_*.cxx
*ui_playpage.h
*.[ao]
*.so
gamecontrollerdb.txt
openmw.appdata.xml
venv/

@ -1,11 +1,38 @@
os:
- linux
- osx
osx_image: xcode7.2
language: cpp
sudo: required
dist: trusty
branches:
only:
- master
- coverity_scan
- /openmw-.*$/
env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE="
addons:
coverity_scan:
project:
name: "OpenMW/openmw"
description: "<Your project description here>"
notification_email: scrawl@baseoftrash.de
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE"
build_command: "make -j2"
branch_pattern: coverity_scan
matrix:
include:
- os: linux
env:
ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
compiler: clang
allow_failures:
- env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
before_install:
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/before_install.linux.sh; fi
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_install.osx.sh; fi
@ -14,9 +41,10 @@ before_script:
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi
script:
- cd ./build
- make -j4
after_script:
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
notifications:
recipients:
- corrmage+travis-ci@gmail.com

@ -0,0 +1,239 @@
Contributors
============
The OpenMW project was started in 2008 by Nicolay Korslund.
In the course of years many people have contributed to the project.
If you feel your name is missing from this list, please notify a developer.
Programmers
-----------
Marc Zinnschlag (Zini) - Lead Programmer/Project Manager
Adam Hogan (aurix)
Aesylwinn
aegis
Aleksandar Jovanov
Alex Haddad (rainChu)
Alex McKibben (WeirdSexy)
alexanderkjall
Alexander Nadeau (wareya)
Alexander Olofsson (Ace)
Allofich
AnyOldName3
Austin Salgat (Salgat)
Artem Kotsynyak (greye)
artemutin
Arthur Moore (EmperorArthur)
athile
Ben Shealy (bentsherman)
Bret Curtis (psi29a)
Britt Mathis (galdor557)
cc9cii
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
darkf
Dieho
Dmitry Shkurskiy (endorph)
Douglas Diniz (Dgdiniz)
Douglas Mencken (dougmencken)
dreamer-dead
David Teviotdale (dteviot)
Edmondo Tommasina (edmondo)
Eduard Cot (trombonecot)
Eli2
Emanuel Guével (potatoesmaster)
eroen
escondida
Evgeniy Mineev (sandstranger)
Fil Krynicki (filkry)
Gašper Sedej
gugus/gus
Hallfaer Tuilinn
hristoast
Internecine
Jacob Essex (Yacoby)
Jannik Heller (scrawl)
Jason Hooks (jhooks)
jeaye
Jeffrey Haines (Jyby)
Jengerer
Jiří Kuneš (kunesj)
Joe Wilkerson (neuralroberts)
Joel Graff (graffy)
John Blomberg (fstp)
Jordan Ayers
Jordan Milne
Julien Voisin (jvoisin/ap0)
Karl-Felix Glatzer (k1ll)
Kevin Poitra (PuppyKevin)
Koncord
Lars Söderberg (Lazaroth)
lazydev
Leon Saunders (emoose)
Lukasz Gromanowski (lgro)
Manuel Edelmann (vorenon)
Marc Bouvier (CramitDeFrog)
Marcin Hulist (Gohan)
Mark Siewert (mark76)
Marco Melletti (mellotanica)
Marco Schulze
Mateusz Kołaczek (PL_kolek)
Mateusz Malisz (malice)
megaton
Michael Hogan (Xethik)
Michael Mc Donnell
Michael Papageorgiou (werdanith)
Michał Bień (Glorf)
Michał Moroz (dragonee)
Miroslav Puda (pakanek)
MiroslavR
naclander
Narmo
Nathan Jeffords (blunted2night)
NeveHanter
Nikolay Kasyanov (corristo)
nobrakal
Nolan Poe (nopoe)
Paul Cercueil (pcercuei)
Paul McElroy (Greendogo)
Pi03k
Pieter van der Kloet (pvdk)
pkubik
Radu-Marius Popovici (rpopovici)
rdimesio
riothamus
Rob Cutmore (rcutmore)
Robert MacGregor (Ragora)
Rohit Nirmal
Roman Melnik (Kromgart)
Roman Proskuryakov (kpp)
Sandy Carter (bwrsandman)
Scott Howard
Sebastian Wick (swick)
Sergey Shambir
sir_herrbatka
smbas
Stefan Galowicz (bogglez)
Stanislav Bobrov (Jiub)
svaante
Sylvain Thesnieres (Garvek)
t6
terrorfisch
Thomas Luppi (Digmaster)
Tom Mason (wheybags)
Torben Leif Carrington (TorbenC)
viadanna
Vincent Heuken
vocollapse
zelurker
Manual
------
Bodillium
Cramal
Alejandro Sanchez (HiPhish)
sir_herrbatka
Packagers
---------
Alexander Olofsson (Ace) - Windows
Bret Curtis (psi29a) - Ubuntu Linux
Edmondo Tommasina (edmondo) - Gentoo Linux
Julian Ospald (hasufell) - Gentoo Linux
Karl-Felix Glatzer (k1ll) - Linux Binaries
Kenny Armstrong (artorius) - Fedora Linux
Nikolay Kasyanov (corristo) - Mac OS X
Sandy Carter (bwrsandman) - Arch Linux
Public Relations and Translations
---------------------------------
Alex McKibben (WeirdSexy) - Podcaster
Artem Kotsynyak (greye) - Russian News Writer
Jim Clauwaert (Zedd) - Public Outreach
Julien Voisin (jvoisin/ap0) - French News Writer
Tom Koenderink (Okulo) - English News Writer
Lukasz Gromanowski (lgro) - English News Writer
Mickey Lyle (raevol) - Release Manager
Pithorn - Chinese News Writer
sir_herrbatka - Polish News Writer
Dawid Lakomy (Vedyimyn) - Polish News Writer
Website
-------
Lukasz Gromanowski (Lgro) - Website Administrator
Ryan Sardonic (Wry) - Wiki Editor
sir_herrbatka - Forum Administrator
Formula Research
----------------
Hrnchamd
Epsilon
fragonard
Greendogo
HiPhish
modred11
Myckel
natirips
Sadler
Artwork
-------
Necrod - OpenMW Logo
Mickey Lyle (raevol) - Wordpress Theme
Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel - OpenMW Editor Icons
Inactive Contributors
---------------------
Ardekantur
Armin Preiml
Berulacks
Carl Maxwell
Diggory Hardy
Dmitry Marakasov (AMDmi3)
ElderTroll
guidoj
Jan-Peter Nilsson (peppe)
Jan Borsodi
Josua Grawitter
juanmnzsk8
Kingpix
Lordrea
Michal Sciubidlo
Nicolay Korslund
Nekochan
pchan3
penguinroad
psi29a
sergoz
spyboot
Star-Demon
Thoronador
Yuri Krupenin
Additional Credits
------------------
In this section we would like to thank people not part of OpenMW for their work.
Thanks to Maxim Nikolaev,
for allowing us to use his excellent Morrowind fan-art on our website and in other places.
Thanks to DokterDume,
for kindly providing us with the Moon and Star logo, used as the application icon and project logo.
Thanks to Kevin Ryan,
for creating the icon used for the Data Files tab of the OpenMW Launcher.
Thanks to DejaVu team,
for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms.

File diff suppressed because it is too large Load Diff

@ -1,15 +1,18 @@
#!/bin/sh
export CXX=g++
export CC=gcc
if [ "${ANALYZE}" ]; then
echo "yes" | sudo add-apt-repository "deb http://llvm.org/apt/`lsb_release -sc`/ llvm-toolchain-`lsb_release -sc`-3.6 main"
wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add -
fi
echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
echo "yes" | sudo apt-add-repository ppa:openmw/openmw
sudo apt-get update -qq
sudo apt-get install -qq libgtest-dev google-mock
sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev libboost-wave-dev
sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev
sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev
sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev
sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev
if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi
sudo mkdir /usr/src/gtest/build
cd /usr/src/gtest/build
sudo cmake .. -DBUILD_SHARED_LIBS=1

@ -1,9 +1,10 @@
#!/bin/sh
export CXX=clang++
export CC=clang
brew tap openmw/openmw
brew update
brew unlink boost
brew install openmw-mygui openmw-bullet openmw-sdl2 openmw-ffmpeg qt unshield
brew rm cmake || true
brew rm pkgconfig || true
brew rm qt5 || true
brew install cmake pkgconfig qt5
curl http://downloads.openmw.org/osx/dependencies/openmw-deps-263d4a8.zip -o ~/openmw-deps.zip
unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -1,5 +1,8 @@
#!/bin/sh
free -m
mkdir build
cd build
cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE
export CODE_COVERAGE=1
if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi
${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE

@ -0,0 +1,671 @@
#!/bin/bash
while [ $# -gt 0 ]; do
ARGSTR=$1
shift
if [ ${ARGSTR:0:1} != "-" ]; then
echo "Unknown argument $ARGSTR"
echo "Try '$0 -h'"
exit 1
fi
for (( i=1; i<${#ARGSTR}; i++ )); do
ARG=${ARGSTR:$i:1}
case $ARG in
V )
VERBOSE=true ;;
v )
VS_VERSION=$1
shift ;;
d )
SKIP_DOWNLOAD=true ;;
e )
SKIP_EXTRACT=true ;;
k )
KEEP=true ;;
u )
UNITY_BUILD=true ;;
p )
PLATFORM=$1
shift ;;
c )
CONFIGURATION=$1
shift ;;
h )
cat <<EOF
Usage: $0 [-cdehkpuvV]
Options:
-c <Release/Debug>
Set the configuration, can also be set with environment variable CONFIGURATION.
-d
Skip checking the downloads.
-e
Skip extracting dependencies.
-h
Show this message.
-k
Keep the old build directory, default is to delete it.
-p <Win32/Win64>
Set the build platform, can also be set with environment variable PLATFORM.
-u
Configure for unity builds.
-v <2013/2015>
Choose the Visual Studio version to use.
-V
Run verbosely
EOF
exit 0
;;
* )
echo "Unknown argument $ARG."
echo "Try '$0 -h'"
exit 1 ;;
esac
done
done
if [ -z $VERBOSE ]; then
STRIP="> /dev/null 2>&1"
fi
if [ -z $VS_VERSION ]; then
VS_VERSION="2013"
fi
if [ -z $APPVEYOR ]; then
echo "Running prebuild outside of Appveyor."
DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
cd $(dirname "$DIR")/..
else
echo "Running prebuild in Appveyor."
cd $APPVEYOR_BUILD_FOLDER
VERSION="$(cat README.md | grep Version: | awk '{ print $3; }')-$(git rev-parse --short HEAD)"
appveyor UpdateBuild -Version "$VERSION" > /dev/null &
fi
run_cmd() {
CMD="$1"
shift
if [ -z $VERBOSE ]; then
eval $CMD $@ > output.log 2>&1
RET=$?
if [ $RET -ne 0 ]; then
if [ -z $APPVEYOR ]; then
echo "Command $CMD failed, output can be found in `real_pwd`/output.log"
else
echo
echo "Command $CMD failed;"
cat output.log
fi
else
rm output.log
fi
return $RET
else
eval $CMD $@
return $?
fi
}
download() {
if [ $# -lt 3 ]; then
echo "Invalid parameters to download."
return 1
fi
NAME=$1
shift
echo "$NAME..."
while [ $# -gt 1 ]; do
URL=$1
FILE=$2
shift
shift
if ! [ -f $FILE ]; then
printf " Downloading $FILE... "
if [ -z $VERBOSE ]; then
curl --silent --retry 10 -kLy 5 -o $FILE $URL
RET=$?
else
curl --retry 10 -kLy 5 -o $FILE $URL
RET=$?
fi
if [ $RET -ne 0 ]; then
echo "Failed!"
else
echo "Done."
fi
else
echo " $FILE exists, skipping."
fi
done
if [ $# -ne 0 ]; then
echo "Missing parameter."
fi
}
real_pwd() {
pwd | sed "s,/\(.\),\1:,"
}
CMAKE_OPTS=""
add_cmake_opts() {
CMAKE_OPTS="$CMAKE_OPTS $@"
}
RUNTIME_DLLS=""
add_runtime_dlls() {
RUNTIME_DLLS="$RUNTIME_DLLS $@"
}
OSG_PLUGINS=""
add_osg_dlls() {
OSG_PLUGINS="$OSG_PLUGINS $@"
}
if [ -z $PLATFORM ]; then
PLATFORM=`uname -m`
fi
if [ -z $CONFIGURATION ]; then
CONFIGURATION="Debug"
fi
case $VS_VERSION in
14|2015 )
GENERATOR="Visual Studio 14 2015"
XP_TOOLSET="v140_xp"
;;
# 12|2013|
* )
GENERATOR="Visual Studio 12 2013"
XP_TOOLSET="v120_xp"
;;
esac
case $PLATFORM in
x64|x86_64|x86-64|win64|Win64 )
ARCHNAME=x86-64
ARCHSUFFIX=64
BITS=64
BASE_OPTS="-G\"$GENERATOR Win64\""
add_cmake_opts "-G\"$GENERATOR Win64\""
;;
x32|x86|i686|i386|win32|Win32 )
ARCHNAME=x86
ARCHSUFFIX=86
BITS=32
BASE_OPTS="-G\"$GENERATOR\" -T$XP_TOOLSET"
add_cmake_opts "-G\"$GENERATOR\"" -T$XP_TOOLSET
;;
* )
echo "Unknown platform $PLATFORM."
exit 1
;;
esac
if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi
case $CONFIGURATION in
debug|Debug|DEBUG )
CONFIGURATION=Debug
;;
release|Release|RELEASE )
CONFIGURATION=Release
;;
relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO )
CONFIGURATION=RelWithDebInfo
;;
esac
echo
echo "=========================="
echo "Starting prebuild on win$BITS"
echo "=========================="
echo
# cd OpenMW/AppVeyor-test
mkdir -p deps
cd deps
DEPS="`pwd`"
if [ -z $SKIP_DOWNLOAD ]; then
echo "Downloading dependency packages."
echo
# Boost
if [ -z $APPVEYOR ]; then
download "Boost 1.58.0" \
http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe \
boost-1.58.0-win$BITS.exe
fi
# Bullet
download "Bullet 2.83.5" \
http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.5-win$BITS.7z \
Bullet-2.83.5-win$BITS.7z
# FFmpeg
download "FFmpeg 2.5.2" \
http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z \
ffmpeg$BITS-2.5.2.7z \
http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z \
ffmpeg$BITS-2.5.2-dev.7z
# MyGUI
download "MyGUI 3.2.2" \
http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z \
MyGUI-3.2.2-win$BITS.7z
# OpenAL
download "OpenAL-Soft 1.16.0" \
http://kcat.strangesoft.net/openal-binaries/openal-soft-1.16.0-bin.zip \
OpenAL-Soft-1.16.0.zip
# OSG
download "OpenSceneGraph 3.3.8" \
http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.3.8-win$BITS.7z \
OSG-3.3.8-win$BITS.7z
# Qt
if [ -z $APPVEYOR ]; then
download "Qt 4.8.6" \
http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \
qt$BITS-4.8.6.7z
fi
# SDL2
download "SDL 2.0.3" \
https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip \
SDL2-2.0.3.zip
fi
cd .. #/..
# Set up dependencies
if [ -z $KEEP ]; then
echo
printf "Preparing build directory... "
rm -rf Build_$BITS
mkdir -p Build_$BITS/deps
echo Done.
fi
mkdir -p Build_$BITS/deps
cd Build_$BITS/deps
DEPS_INSTALL=`pwd`
cd $DEPS
echo
echo "Extracting dependencies..."
# Boost
printf "Boost 1.58.0... "
{
if [ -z $APPVEYOR ]; then
cd $DEPS_INSTALL
BOOST_SDK="`real_pwd`/Boost"
if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Boost
$DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent
fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
-DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0"
echo Done.
else
# Appveyor unstable has all the boost we need already
BOOST_SDK="c:/Libraries/boost"
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
-DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0"
echo AppVeyor.
fi
}
cd $DEPS
# Bullet
printf "Bullet 2.83.5... "
{
cd $DEPS_INSTALL
if [ -d Bullet ]; then
printf "Exists. (No version checking) "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Bullet
eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP
mv Bullet-2.83.5-win$BITS Bullet
fi
export BULLET_ROOT="`real_pwd`/Bullet"
echo Done.
}
cd $DEPS
# FFmpeg
printf "FFmpeg 2.5.2... "
{
cd $DEPS_INSTALL
if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf FFmpeg
eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP
eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP
mv ffmpeg-2.5.2-win$BITS-shared FFmpeg
cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/
rm -rf ffmpeg-2.5.2-win$BITS-dev
fi
export FFMPEG_HOME="`real_pwd`/FFmpeg"
add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll
if [ $BITS -eq 32 ]; then
add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\""
fi
echo Done.
}
cd $DEPS
# MyGUI
printf "MyGUI 3.2.2... "
{
cd $DEPS_INSTALL
if [ -d MyGUI ] && \
grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null
then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf MyGUI
eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP
mv MyGUI-3.2.2-win$BITS MyGUI
fi
MYGUI_SDK="`real_pwd`/MyGUI"
add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \
-DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \
-DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="_d"
else
SUFFIX=""
fi
add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll
echo Done.
}
cd $DEPS
# OpenAL
printf "OpenAL-Soft 1.16.0... "
{
if [ -d openal-soft-1.16.0-bin ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf openal-soft-1.16.0-bin
eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP
fi
OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin"
add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \
-DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib"
echo Done.
}
cd $DEPS
# OSG
printf "OSG 3.3.8... "
{
cd $DEPS_INSTALL
if [ -d OSG ] && \
grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \
grep "OPENSCENEGRAPH_MINOR_VERSION 3" OSG/include/osg/Version > /dev/null && \
grep "OPENSCENEGRAPH_PATCH_VERSION 8" OSG/include/osg/Version > /dev/null
then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf OSG
eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP
mv OSG-3.3.8-win$BITS OSG
fi
OSG_SDK="`real_pwd`/OSG"
add_cmake_opts -DOSG_DIR="$OSG_SDK"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d"
else
SUFFIX=""
fi
add_runtime_dlls `pwd`/OSG/bin/{OpenThreads,zlib}$SUFFIX.dll \
`pwd`/OSG/bin/osg{,Animation,DB,FX,GA,Particle,Qt,Text,Util,Viewer}$SUFFIX.dll
add_osg_dlls `pwd`/OSG/bin/osgPlugins-3.3.8/osgdb_{bmp,dds,gif,jpeg,png,tga}$SUFFIX.dll
echo Done.
}
cd $DEPS
# Qt
if [ -z $APPVEYOR ]; then
printf "Qt 4.8.6... "
else
printf "Qt 5.4... "
fi
{
if [ -z $APPVEYOR ]; then
cd $DEPS_INSTALL
QT_SDK="`real_pwd`/Qt"
if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Qt
eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP
mv qt-4.8.6-* Qt
cd Qt
eval ./qtbinpatcher.exe $STRIP
fi
cd $QT_SDK
add_cmake_opts -DDESIRED_QT_VERSION=4 \
-DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d4"
else
SUFFIX="4"
fi
add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll
echo Done.
else
if [ $BITS -eq 32 ]; then
QT_SDK="C:/Qt/5.4/msvc2013_opengl"
else
QT_SDK="C:/Qt/5.4/msvc2013_64_opengl"
fi
add_cmake_opts -DDESIRED_QT_VERSION=5 \
-DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
echo AppVeyor.
fi
}
cd $DEPS
# SDL2
printf "SDL 2.0.3... "
{
if [ -d SDL2-2.0.3 ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.3
eval 7z x -y SDL2-2.0.3.zip $STRIP
fi
SDL_SDK="`real_pwd`/SDL2-2.0.3"
add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \
-DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \
-DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib"
add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll
echo Done.
}
cd $DEPS_INSTALL/..
echo
echo "Setting up OpenMW build..."
add_cmake_opts -DBUILD_BSATOOL=no \
-DBUILD_ESMTOOL=no \
-DBUILD_MYGUI_PLUGIN=no \
-DOPENMW_MP_BUILD=on
if [ -z $CI ]; then
echo " (Outside of CI, doing full build.)"
else
case $STEP in
components )
echo " Subproject: Components."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
-DBUILD_LAUNCHER=no \
-DBUILD_MWINIIMPORTER=no \
-DBUILD_OPENCS=no \
-DBUILD_OPENMW=no \
-DBUILD_WIZARD=no
;;
openmw )
echo " Subproject: OpenMW."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
-DBUILD_LAUNCHER=no \
-DBUILD_MWINIIMPORTER=no \
-DBUILD_OPENCS=no \
-DBUILD_WIZARD=no
;;
opencs )
echo " Subproject: OpenCS."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
-DBUILD_LAUNCHER=no \
-DBUILD_MWINIIMPORTER=no \
-DBUILD_OPENMW=no \
-DBUILD_WIZARD=no
;;
misc )
echo " Subproject: Misc."
add_cmake_opts -DBUILD_OPENCS=no \
-DBUILD_OPENMW=no
;;
* )
echo " Building everything."
;;
esac
fi
if [ -z $VERBOSE ]; then
printf " Configuring... "
else
echo " cmake .. $CMAKE_OPTS"
fi
run_cmd cmake .. $CMAKE_OPTS
RET=$?
if [ -z $VERBOSE ]; then
if [ $RET -eq 0 ]; then echo Done.
else echo Failed.; fi
fi
echo
# NOTE: Disable this when/if we want to run test cases
if [ -z $CI ]; then
echo "Copying Runtime DLLs..."
mkdir -p $CONFIGURATION
for DLL in $RUNTIME_DLLS; do
echo " `basename $DLL`."
cp "$DLL" $CONFIGURATION/
done
echo "OSG Plugin DLLs..."
mkdir -p $CONFIGURATION/osgPlugins-3.3.8
for DLL in $OSG_PLUGINS; do
echo " `basename $DLL`."
cp "$DLL" $CONFIGURATION/osgPlugins-3.3.8
done
echo
echo "Copying Runtime Resources/Config Files"
echo " gamecontrollerdb.txt"
cp $CONFIGURATION/../gamecontrollerdb.txt $CONFIGURATION/gamecontrollerdb.txt
echo " openmw.cfg"
cp $CONFIGURATION/../openmw.cfg.install $CONFIGURATION/openmw.cfg
echo " openmw-cs.cfg"
cp $CONFIGURATION/../openmw-cs.cfg $CONFIGURATION/openmw-cs.cfg
echo " settings-default.cfg"
cp $CONFIGURATION/../settings-default.cfg $CONFIGURATION/settings-default.cfg
echo " resources/"
cp -r $CONFIGURATION/../resources $CONFIGURATION/resources
fi
exit $RET

@ -1,5 +1,24 @@
#!/bin/sh
export CXX=clang++
export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH="/usr/local/opt/qt5"
mkdir build
cd build
cmake -DCMAKE_FRAMEWORK_PATH="/usr/local/lib/macosx/Release" -DCMAKE_EXE_LINKER_FLAGS="-F/usr/local/lib/macosx/Release" -DCMAKE_CXX_FLAGS="-stdlib=libstdc++" -DCMAKE_BUILD_TYPE=Debug -DBUILD_MYGUI_PLUGIN=OFF -G"Unix Makefiles" ..
cmake \
-D CMAKE_EXE_LINKER_FLAGS="-lz" \
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.8" \
-D CMAKE_OSX_SYSROOT="macosx10.11" \
-D CMAKE_BUILD_TYPE=Debug \
-D OPENMW_OSX_DEPLOYMENT=TRUE \
-D DESIRED_QT_VERSION=5 \
-D OSG_PLUGIN_LIB_SEARCH_PATH="$DEPENDENCIES_ROOT/lib/osgPlugins-3.4.0" \
-D BUILD_ESMTOOL=FALSE \
-D BUILD_MYGUI_PLUGIN=FALSE \
-G"Unix Makefiles" \
..

@ -0,0 +1,59 @@
#!/bin/bash
if [ -z $PLATFORM ]; then
PLATFORM=`uname -m`
fi
if [ -z $CONFIGURATION ]; then
CONFIGURATION="Debug"
fi
case $PLATFORM in
x32|x86|i686|i386|win32|Win32 )
BITS=32
PLATFORM=Win32
;;
x64|x86_64|x86-64|win64|Win64 )
BITS=64
PLATFORM=x64
;;
* )
echo "Unknown platform $PLATFORM."
exit 1 ;;
esac
if [ -z $APPVEYOR ]; then
echo "Running $BITS-bit $CONFIGURATION build outside of Appveyor."
DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
cd $(dirname "$DIR")/..
else
echo "Running $BITS-bit $CONFIGURATION build in Appveyor."
cd $APPVEYOR_BUILD_FOLDER
fi
cd build_$BITS
which msbuild > /dev/null
if [ $? -ne 0 ]; then
msbuild() {
/c/Program\ Files\ \(x86\)/MSBuild/12.0/Bin/MSBuild.exe "$@"
}
fi
if [ -z $APPVEYOR ]; then
msbuild OpenMW.sln //t:Build //m:8
else
msbuild OpenMW.sln //t:Build //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
fi
RET=$?
if [ $RET -eq 0 ] && [ ! -z $PACKAGE ]; then
msbuild PACKAGE.vcxproj //t:Build //m:8
RET=$?
fi
exit $RET

@ -0,0 +1,9 @@
#!/bin/bash
OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} --exclude=ui_\* apps components)
if [[ $OUTPUT ]] ; then
echo "Error: Tab characters found!"
echo $OUTPUT
exit 1
fi

File diff suppressed because it is too large Load Diff

@ -0,0 +1,16 @@
Description
===========
Your pull request description should include (if applicable):
* A link back to the bug report or forum discussion that prompted the change
* Summary of the changes made
* Reasoning / motivation behind the change
* What testing you have carried out to verify the change
Other notes
===========
* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time.
* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title.
* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards).

@ -0,0 +1,107 @@
OpenMW
======
[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/scrawl/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740)
OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set.
* Version: 0.39.0
* License: GPL (see docs/license/GPL3.txt for more information)
* Website: http://www.openmw.org
* IRC: #openmw on irc.freenode.net
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information)
Current Status
--------------
The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces.
Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page.
Getting Started
---------------
* [Official forums](https://forum.openmw.org/)
* [Installation instructions](https://wiki.openmw.org/index.php?title=Installation_Instructions)
* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup)
* [Testing the game](https://wiki.openmw.org/index.php?title=Testing)
* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted)
* [Report a bug](http://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug!
* [Known issues](http://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker)
The data path
-------------
The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install).
Command line options
--------------------
Syntax: openmw <options>
Allowed options:
--help print help message
--version print version information and quit
--data arg (=data) set data directories (later directories
have higher priority)
--data-local arg set local data directory (highest
priority)
--fallback-archive arg (=fallback-archive)
set fallback BSA archives (later
archives have higher priority)
--resources arg (=resources) set resources directory
--start arg set initial cell
--content arg content file(s): esm/esp, or
omwgame/omwaddon
--no-sound [=arg(=1)] (=0) disable all sounds
--script-verbose [=arg(=1)] (=0) verbose script output
--script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue
scripts) at startup
--script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup
--script-console [=arg(=1)] (=0) enable console-only script
functionality
--script-run arg select a file containing a list of
console commands that is executed on
startup
--script-warn [=arg(=1)] (=1) handling of warnings when compiling
scripts
0 - ignore warning
1 - show warning but consider script as
correctly compiled anyway
2 - treat warnings as errors
--script-blacklist arg ignore the specified script (if the use
of the blacklist is enabled)
--script-blacklist-use [=arg(=1)] (=1)
enable script blacklisting
--load-savegame arg load a save game file on game startup
(specify an absolute filename or a
filename relative to the current
working directory)
--skip-menu [=arg(=1)] (=0) skip main menu on game startup
--new-game [=arg(=1)] (=0) run new game sequence (ignored if
skip-menu=0)
--fs-strict [=arg(=1)] (=0) strict file system handling (no case
folding)
--encoding arg (=win1252) Character encoding used in OpenMW game
messages:
win1250 - Central and Eastern European
such as Polish, Czech, Slovak,
Hungarian, Slovene, Bosnian, Croatian,
Serbian (Latin script), Romanian and
Albanian languages
win1251 - Cyrillic alphabet such as
Russian, Bulgarian, Serbian Cyrillic
and other languages
win1252 - Western European (Latin)
alphabet, used by default
--fallback arg fallback values
--no-grab Don't grab mouse cursor
--export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG
image and XML file in current directory
--activate-dist arg (=-1) activation distance override

@ -9,7 +9,8 @@ add_executable(bsatool
)
target_link_libraries(bsatool
${Boost_LIBRARIES}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)

@ -1,4 +1,5 @@
#include <iostream>
#include <iomanip>
#include <vector>
#include <exception>
@ -27,8 +28,8 @@ struct Arguments
void replaceAll(std::string& str, const std::string& needle, const std::string& substitute)
{
int pos = str.find(needle);
while(pos != -1)
size_t pos = str.find(needle);
while(pos != std::string::npos)
{
str.replace(pos, needle.size(), substitute);
pos = str.find(needle);
@ -138,8 +139,8 @@ bool parseOptions (int argc, char** argv, Arguments &info)
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
info.longformat = variables.count("long");
info.fullpath = variables.count("full-path");
info.longformat = variables.count("long") != 0;
info.fullpath = variables.count("full-path") != 0;
return true;
}
@ -150,34 +151,33 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info);
int main(int argc, char** argv)
{
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
// Open file
Bsa::BSAFile bsa;
try
{
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
// Open file
Bsa::BSAFile bsa;
bsa.open(info.filename);
if (info.mode == "list")
return list(bsa, info);
else if (info.mode == "extract")
return extract(bsa, info);
else if (info.mode == "extractall")
return extractAll(bsa, info);
else
{
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
return 1;
}
}
catch(std::exception &e)
catch (std::exception& e)
{
std::cout << "ERROR reading BSA archive '" << info.filename
<< "'\nDetails:\n" << e.what() << std::endl;
std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl;
return 2;
}
if (info.mode == "list")
return list(bsa, info);
else if (info.mode == "extract")
return extract(bsa, info);
else if (info.mode == "extractall")
return extractAll(bsa, info);
else
{
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
return 1;
}
}
int list(Bsa::BSAFile& bsa, Arguments& info)
@ -189,9 +189,11 @@ int list(Bsa::BSAFile& bsa, Arguments& info)
if(info.longformat)
{
// Long format
std::ios::fmtflags f(std::cout.flags());
std::cout << std::setw(50) << std::left << files[i].name;
std::cout << std::setw(8) << std::left << std::dec << files[i].fileSize;
std::cout << "@ 0x" << std::hex << files[i].offset << std::endl;
std::cout.flags(f);
}
else
std::cout << files[i].name << std::endl;
@ -236,12 +238,14 @@ int extract(Bsa::BSAFile& bsa, Arguments& info)
}
// Get a stream for the file to extract
Ogre::DataStreamPtr data = bsa.getFile(archivePath.c_str());
Files::IStreamPtr stream = bsa.getFile(archivePath.c_str());
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
out.write(data->getAsString().c_str(), data->size());
out << stream->rdbuf();
out.close();
return 0;
@ -275,12 +279,12 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info)
// Get a stream for the file to extract
// (inefficient because getFile iter on the list again)
Ogre::DataStreamPtr data = bsa.getFile(archivePath);
Files::IStreamPtr data = bsa.getFile(archivePath);
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << target << std::endl;
out.write(data->getAsString().c_str(), data->size());
out << data->rdbuf();
out.close();
}

@ -13,7 +13,7 @@ add_executable(esmtool
)
target_link_libraries(esmtool
${Boost_LIBRARIES}
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
)

@ -4,6 +4,8 @@
#include <list>
#include <map>
#include <set>
#include <fstream>
#include <cmath>
#include <boost/program_options.hpp>
@ -22,11 +24,12 @@ struct ESMData
{
std::string author;
std::string description;
int version;
unsigned int version;
std::vector<ESM::Header::MasterData> masters;
std::deque<EsmTool::RecordBase *> mRecords;
std::map<ESM::Cell *, std::deque<ESM::CellRef> > mCellRefs;
// Value: (Reference, Deleted flag)
std::map<ESM::Cell *, std::deque<std::pair<ESM::CellRef, bool> > > mCellRefs;
std::map<int, int> mRecordStats;
static const std::set<int> sLabeledRec;
@ -48,9 +51,9 @@ const std::set<int> ESMData::sLabeledRec =
// Based on the legacy struct
struct Arguments
{
unsigned int raw_given;
unsigned int quiet_given;
unsigned int loadcells_given;
bool raw_given;
bool quiet_given;
bool loadcells_given;
bool plain_given;
std::string mode;
@ -59,6 +62,7 @@ struct Arguments
std::string outname;
std::vector<std::string> types;
std::string name;
ESMData data;
ESM::ESMReader reader;
@ -78,6 +82,8 @@ bool parseOptions (int argc, char** argv, Arguments &info)
("type,t", bpo::value< std::vector<std::string> >(),
"Show only records of this type (four character record code). May "
"be specified multiple times. Only affects dump mode.")
("name,n", bpo::value<std::string>(),
"Show only the record with this name. Only affects dump mode.")
("plain,p", "Print contents of dialogs, books and scripts. "
"(skipped by default)"
"Only affects dump mode.")
@ -148,7 +154,9 @@ bool parseOptions (int argc, char** argv, Arguments &info)
}
if (variables.count("type") > 0)
info.types = variables["type"].as< std::vector<std::string> >();
info.types = variables["type"].as< std::vector<std::string> >();
if (variables.count("name") > 0)
info.name = variables["name"].as<std::string>();
info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp"))
@ -177,10 +185,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outname = variables["input-file"].as< std::vector<std::string> >()[1];
info.raw_given = variables.count ("raw");
info.quiet_given = variables.count ("quiet");
info.loadcells_given = variables.count ("loadcells");
info.plain_given = (variables.count("plain") > 0);
info.raw_given = variables.count ("raw") != 0;
info.quiet_given = variables.count ("quiet") != 0;
info.loadcells_given = variables.count ("loadcells") != 0;
info.plain_given = variables.count("plain") != 0;
// Font encoding settings
info.encoding = variables["encoding"].as<std::string>();
@ -203,19 +211,27 @@ int comp(Arguments& info);
int main(int argc, char**argv)
{
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
if (info.mode == "dump")
return load(info);
else if (info.mode == "clone")
return clone(info);
else if (info.mode == "comp")
return comp(info);
else
try
{
std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl;
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
if (info.mode == "dump")
return load(info);
else if (info.mode == "clone")
return clone(info);
else if (info.mode == "comp")
return comp(info);
else
{
std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl;
return 1;
}
}
catch (std::exception& e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
return 1;
}
@ -241,7 +257,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
while(cell.getNextRef(esm, ref, deleted))
{
if (save) {
info.data.mCellRefs[&cell].push_back(ref);
info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
}
if(quiet) continue;
@ -253,10 +269,12 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
std::cout << " Faction: '" << ref.mFaction << "'" << std::endl;
std::cout << " Faction rank: '" << ref.mFactionRank << "'" << std::endl;
std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n";
std::cout << " Uses/health: '" << ref.mCharge << "'\n";
std::cout << " Uses/health: '" << ref.mChargeInt << "'\n";
std::cout << " Gold value: '" << ref.mGoldValue << "'\n";
std::cout << " Blocked: '" << static_cast<int>(ref.mReferenceBlocked) << "'" << std::endl;
std::cout << " Deleted: " << deleted << std::endl;
if (!ref.mKey.empty())
std::cout << " Key: '" << ref.mKey << "'" << std::endl;
}
}
@ -269,12 +287,14 @@ void printRaw(ESM::ESMReader &esm)
esm.getRecHeader();
while(esm.hasMoreSubs())
{
uint64_t offs = esm.getOffset();
size_t offs = esm.getFileOffset();
esm.getSubName();
esm.skipHSub();
n = esm.retSubName();
std::ios::fmtflags f(std::cout.flags());
std::cout << " " << n.toString() << " - " << esm.getSubSize()
<< " bytes @ 0x" << std::hex << offs << "\n";
std::cout.flags(f);
}
}
}
@ -334,58 +354,58 @@ int load(Arguments& info)
uint32_t flags;
esm.getRecHeader(flags);
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
if (record == 0)
{
if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end())
{
std::cout << "Skipping " << n.toString() << " records." << std::endl;
skipped.push_back(n.intval);
}
esm.skipRecord();
if (quiet) break;
std::cout << " Skipping\n";
continue;
}
record->setFlags(static_cast<int>(flags));
record->setPrintPlain(info.plain_given);
record->load(esm);
// Is the user interested in this record type?
bool interested = true;
if (!info.types.empty())
{
std::vector<std::string>::iterator match;
match = std::find(info.types.begin(), info.types.end(),
n.toString());
match = std::find(info.types.begin(), info.types.end(), n.toString());
if (match == info.types.end()) interested = false;
}
std::string id = esm.getHNOString("NAME");
if (id.empty())
id = esm.getHNOString("INAM");
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId()))
interested = false;
if(!quiet && interested)
std::cout << "\nRecord: " << n.toString()
<< " '" << id << "'\n";
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
if (record == 0) {
if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end())
{
std::cout << "Skipping " << n.toString() << " records." << std::endl;
skipped.push_back(n.val);
}
{
std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n";
record->print();
}
esm.skipRecord();
if (quiet) break;
std::cout << " Skipping\n";
} else {
if (record->getType().val == ESM::REC_GMST) {
// preset id for GameSetting record
record->cast<ESM::GameSetting>()->get().mId = id;
}
record->setId(id);
record->setFlags((int) flags);
record->setPrintPlain(info.plain_given);
record->load(esm);
if (!quiet && interested) record->print();
if (record->getType().val == ESM::REC_CELL && loadCells) {
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
}
if (record->getType().intval == ESM::REC_CELL && loadCells && interested)
{
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
}
if (save) {
info.data.mRecords.push_back(record);
} else {
delete record;
}
++info.data.mRecordStats[n.val];
if (save)
{
info.data.mRecords.push_back(record);
}
else
{
delete record;
}
++info.data.mRecordStats[n.intval];
}
} catch(std::exception &e) {
@ -420,27 +440,22 @@ int clone(Arguments& info)
return 1;
}
int recordCount = info.data.mRecords.size();
size_t recordCount = info.data.mRecords.size();
int digitCount = 1; // For a nicer output
if (recordCount > 9) ++digitCount;
if (recordCount > 99) ++digitCount;
if (recordCount > 999) ++digitCount;
if (recordCount > 9999) ++digitCount;
if (recordCount > 99999) ++digitCount;
if (recordCount > 999999) ++digitCount;
if (recordCount > 0)
digitCount = (int)std::log10(recordCount) + 1;
std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl;
ESM::NAME name;
int i = 0;
typedef std::map<int, int> Stats;
Stats &stats = info.data.mRecordStats;
for (Stats::iterator it = stats.begin(); it != stats.end(); ++it)
{
name.val = it->first;
float amount = it->second;
ESM::NAME name;
name.intval = it->first;
int amount = it->second;
std::cout << std::setw(digitCount) << amount << " " << name.toString() << " ";
if (++i % 3 == 0)
@ -472,36 +487,27 @@ int clone(Arguments& info)
for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
{
EsmTool::RecordBase *record = *it;
const ESM::NAME& typeName = record->getType();
name.val = record->getType().val;
esm.startRecord(name.toString(), record->getFlags());
// TODO wrap this with std::set
if (ESMData::sLabeledRec.count(name.val) > 0) {
esm.writeHNCString("NAME", record->getId());
} else {
esm.writeHNOString("NAME", record->getId());
}
esm.startRecord(typeName.toString(), record->getFlags());
record->save(esm);
if (name.val == ESM::REC_CELL) {
if (typeName.intval == ESM::REC_CELL) {
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
if (!info.data.mCellRefs[ptr].empty()) {
typedef std::deque<ESM::CellRef> RefList;
typedef std::deque<std::pair<ESM::CellRef, bool> > RefList;
RefList &refs = info.data.mCellRefs[ptr];
for (RefList::iterator it = refs.begin(); it != refs.end(); ++it)
for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt)
{
it->save(esm);
refIt->first.save(esm, refIt->second);
}
}
}
esm.endRecord(name.toString());
esm.endRecord(typeName.toString());
saved++;
int perc = (saved / (float)recordCount)*100;
int perc = (int)((saved / (float)recordCount)*100);
if (perc % 10 == 0)
{
std::cerr << "\r" << perc << "%";

@ -13,14 +13,13 @@
#include <components/esm/loadweap.hpp>
#include <components/esm/aipackage.hpp>
#include <iostream>
#include <boost/format.hpp>
std::string bodyPartLabel(int idx)
{
if (idx >= 0 && idx <= 26)
{
const char *bodyPartLabels[] = {
static const char *bodyPartLabels[] = {
"Head",
"Hair",
"Neck",
@ -59,7 +58,7 @@ std::string meshPartLabel(int idx)
{
if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail)
{
const char *meshPartLabels[] = {
static const char *meshPartLabels[] = {
"Head",
"Hair",
"Neck",
@ -86,7 +85,7 @@ std::string meshTypeLabel(int idx)
{
if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor)
{
const char *meshTypeLabels[] = {
static const char *meshTypeLabels[] = {
"Skin",
"Clothing",
"Armor"
@ -101,7 +100,7 @@ std::string clothingTypeLabel(int idx)
{
if (idx >= 0 && idx <= 9)
{
const char *clothingTypeLabels[] = {
static const char *clothingTypeLabels[] = {
"Pants",
"Shoes",
"Shirt",
@ -120,10 +119,10 @@ std::string clothingTypeLabel(int idx)
}
std::string armorTypeLabel(int idx)
{
{
if (idx >= 0 && idx <= 10)
{
const char *armorTypeLabels[] = {
static const char *armorTypeLabels[] = {
"Helmet",
"Cuirass",
"Left Pauldron",
@ -146,7 +145,7 @@ std::string dialogTypeLabel(int idx)
{
if (idx >= 0 && idx <= 4)
{
const char *dialogTypeLabels[] = {
static const char *dialogTypeLabels[] = {
"Topic",
"Voice",
"Greeting",
@ -165,7 +164,7 @@ std::string questStatusLabel(int idx)
{
if (idx >= 0 && idx <= 4)
{
const char *questStatusLabels[] = {
static const char *questStatusLabels[] = {
"None",
"Name",
"Finished",
@ -182,7 +181,7 @@ std::string creatureTypeLabel(int idx)
{
if (idx >= 0 && idx <= 3)
{
const char *creatureTypeLabels[] = {
static const char *creatureTypeLabels[] = {
"Creature",
"Daedra",
"Undead",
@ -198,7 +197,7 @@ std::string soundTypeLabel(int idx)
{
if (idx >= 0 && idx <= 7)
{
const char *soundTypeLabels[] = {
static const char *soundTypeLabels[] = {
"Left Foot",
"Right Foot",
"Swim Left",
@ -218,7 +217,7 @@ std::string weaponTypeLabel(int idx)
{
if (idx >= 0 && idx <= 13)
{
const char *weaponTypeLabels[] = {
static const char *weaponTypeLabels[] = {
"Short Blade One Hand",
"Long Blade One Hand",
"Long Blade Two Hand",
@ -646,7 +645,7 @@ std::string ruleFunction(int idx)
else
return "Invalid";
}
// The "unused flag bits" should probably be defined alongside the
// defined bits in the ESM component. The names of the flag bits are
// very inconsistent.
@ -654,7 +653,7 @@ std::string ruleFunction(int idx)
std::string bodyPartFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags == 0) properties += "[None] ";
if (flags & ESM::BodyPart::BPF_Female) properties += "Female ";
if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable ";
int unused = (0xFFFFFFFF ^
@ -668,7 +667,7 @@ std::string bodyPartFlags(int flags)
std::string cellFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags == 0) properties += "[None] ";
if (flags & ESM::Cell::HasWater) properties += "HasWater ";
if (flags & ESM::Cell::Interior) properties += "Interior ";
if (flags & ESM::Cell::NoSleep) properties += "NoSleep ";
@ -831,12 +830,12 @@ std::string npcFlags(int flags)
std::string properties = "";
if (flags == 0) properties += "[None] ";
// Mythicmods and the ESM component differ. Mythicmods says
// 0x8=None and 0x10=AutoCalc, while our code defines 0x8 as
// AutoCalc. The former seems to be correct. All Bethesda
// records have bit 0x8 set. A suspiciously large portion of
// females have autocalc turned off.
if (flags & ESM::NPC::Autocalc) properties += "Unknown ";
if (flags & 0x00000010) properties += "Autocalc ";
// 0x8=None and 0x10=AutoCalc, while our code previously defined
// 0x8 as AutoCalc. The former seems to be correct. All Bethesda
// records have bit 0x8 set. Previously, suspiciously large portion
// of females had autocalc turned off.
if (flags & 0x00000008) properties += "Unknown ";
if (flags & ESM::NPC::Autocalc) properties += "Autocalc ";
if (flags & ESM::NPC::Female) properties += "Female ";
if (flags & ESM::NPC::Respawn) properties += "Respawn ";
if (flags & ESM::NPC::Essential) properties += "Essential ";
@ -848,8 +847,8 @@ std::string npcFlags(int flags)
// however the only unknown bit occurs on ALL records, and
// relatively few NPCs have this bit set.
int unused = (0xFFFFFFFF ^
(ESM::NPC::Autocalc|
0x00000010|
(0x00000008|
ESM::NPC::Autocalc|
ESM::NPC::Female|
ESM::NPC::Respawn|
ESM::NPC::Essential|

@ -2,8 +2,13 @@
#include "labels.hpp"
#include <iostream>
#include <sstream>
#include <boost/format.hpp>
namespace
{
void printAIPackage(ESM::AIPackage p)
{
std::cout << " AI Type: " << aiTypeLabel(p.mType)
@ -14,7 +19,7 @@ void printAIPackage(ESM::AIPackage p)
std::cout << " Duration: " << p.mWander.mDuration << std::endl;
std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl;
if (p.mWander.mShouldRepeat != 1)
std::cout << " Should repeat: " << (bool)p.mWander.mShouldRepeat << std::endl;
std::cout << " Should repeat: " << (bool)(p.mWander.mShouldRepeat != 0) << std::endl;
std::cout << " Idle: ";
for (int i = 0; i != 8; i++)
@ -25,7 +30,7 @@ void printAIPackage(ESM::AIPackage p)
{
std::cout << " Travel Coordinates: (" << p.mTravel.mX << ","
<< p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl;
std::cout << " Travel Unknown: " << (int)p.mTravel.mUnk << std::endl;
std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl;
}
else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort)
{
@ -33,12 +38,12 @@ void printAIPackage(ESM::AIPackage p)
<< p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl;
std::cout << " Duration: " << p.mTarget.mDuration << std::endl;
std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl;
std::cout << " Unknown: " << (int)p.mTarget.mUnk << std::endl;
std::cout << " Unknown: " << p.mTarget.mUnk << std::endl;
}
else if (p.mType == ESM::AI_Activate)
{
std::cout << " Name: " << p.mActivate.mName.toString() << std::endl;
std::cout << " Activate Unknown: " << (int)p.mActivate.mUnk << std::endl;
std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl;
}
else {
std::cout << " BadPackage: " << boost::format("0x%08x") % p.mType << std::endl;
@ -89,6 +94,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss)
case 'A': if (indicator == 'R') type_str = "Not Race"; break;
case 'B': if (indicator == 'L') type_str = "Not Cell"; break;
case 'C': if (indicator == 's') type_str = "Not Local"; break;
default: break;
}
// Append the variable name to the function string if any.
@ -110,6 +116,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss)
case '3': oper_str = ">="; break;
case '4': oper_str = "< "; break;
case '5': oper_str = "<="; break;
default: break;
}
std::ostringstream stream;
@ -145,6 +152,26 @@ void printEffectList(ESM::EffectList effects)
}
}
void printTransport(const std::vector<ESM::Transport::Dest>& transport)
{
std::vector<ESM::Transport::Dest>::const_iterator dit;
for (dit = transport.begin(); dit != transport.end(); ++dit)
{
std::cout << " Destination Position: "
<< boost::format("%12.3f") % dit->mPos.pos[0] << ","
<< boost::format("%12.3f") % dit->mPos.pos[1] << ","
<< boost::format("%12.3f") % dit->mPos.pos[2] << ")" << std::endl;
std::cout << " Destination Rotation: "
<< boost::format("%9.6f") % dit->mPos.rot[0] << ","
<< boost::format("%9.6f") % dit->mPos.rot[1] << ","
<< boost::format("%9.6f") % dit->mPos.rot[2] << ")" << std::endl;
if (dit->mCellName != "")
std::cout << " Destination Cell: " << dit->mCellName << std::endl;
}
}
}
namespace EsmTool {
RecordBase *
@ -152,7 +179,7 @@ RecordBase::create(ESM::NAME type)
{
RecordBase *record = 0;
switch (type.val) {
switch (type.intval) {
case ESM::REC_ACTI:
{
record = new EsmTool::Record<ESM::Activator>;
@ -378,6 +405,7 @@ void Record<ESM::Activator>::print()
std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -392,6 +420,7 @@ void Record<ESM::Potion>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl;
printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -420,6 +449,7 @@ void Record<ESM::Armor>::print()
if (pit->mFemale != "")
std::cout << " Female Name: " << pit->mFemale << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -430,10 +460,11 @@ void Record<ESM::Apparatus>::print()
std::cout << " Icon: " << mData.mIcon << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType)
<< " (" << (int)mData.mData.mType << ")" << std::endl;
<< " (" << mData.mData.mType << ")" << std::endl;
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -447,6 +478,7 @@ void Record<ESM::BodyPart>::print()
std::cout << " Part: " << meshPartLabel(mData.mData.mPart)
<< " (" << (int)mData.mData.mPart << ")" << std::endl;
std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -466,15 +498,16 @@ void Record<ESM::Book>::print()
std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
if (mPrintPlain)
{
std::cout << " Text:" << std::endl;
std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mText << std::endl;
std::cout << "END----------------------------------------" << std::endl;
std::cout << " Text:" << std::endl;
std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mText << std::endl;
std::cout << "END----------------------------------------" << std::endl;
}
else
{
std::cout << " Text: [skipped]" << std::endl;
std::cout << " Text: [skipped]" << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -486,6 +519,7 @@ void Record<ESM::BirthSign>::print()
std::vector<std::string>::iterator pit;
for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit)
std::cout << " Power: " << *pit << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -514,6 +548,7 @@ void Record<ESM::Cell>::print()
std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl;
std::cout << " Water Level Int: " << mData.mWaterInt << std::endl;
std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
@ -531,11 +566,12 @@ void Record<ESM::Class>::print()
std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization)
<< " (" << mData.mData.mSpecialization << ")" << std::endl;
for (int i = 0; i != 5; i++)
std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][0])
std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][0])
<< " (" << mData.mData.mSkills[i][0] << ")" << std::endl;
for (int i = 0; i != 5; i++)
std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][1])
std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1])
<< " (" << mData.mData.mSkills[i][1] << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -562,6 +598,7 @@ void Record<ESM::Clothing>::print()
if (pit->mFemale != "")
std::cout << " Female Name: " << pit->mFemale << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -577,6 +614,7 @@ void Record<ESM::Container>::print()
for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit)
std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
<< " Item: " << cit->mItem.toString() << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -627,6 +665,8 @@ void Record<ESM::Creature>::print()
for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); ++sit)
std::cout << " Spell: " << *sit << std::endl;
printTransport(mData.getTransport());
std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl;
std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl;
std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl;
@ -641,6 +681,7 @@ void Record<ESM::Creature>::print()
std::vector<ESM::AIPackage>::iterator pit;
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
printAIPackage(*pit);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -648,6 +689,7 @@ void Record<ESM::Dialogue>::print()
{
std::cout << " Type: " << dialogTypeLabel(mData.mType)
<< " (" << (int)mData.mType << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
// Sadly, there are no DialInfos, because the loader dumps as it
// loads, rather than loading and then dumping. :-( Anyone mind if
// I change this?
@ -664,6 +706,7 @@ void Record<ESM::Door>::print()
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -675,6 +718,7 @@ void Record<ESM::Enchantment>::print()
std::cout << " Charge: " << mData.mData.mCharge << std::endl;
std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl;
printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -708,12 +752,14 @@ void Record<ESM::Faction>::print()
std::map<std::string, int>::iterator rit;
for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit)
std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
void Record<ESM::Global>::print()
{
std::cout << " " << mData.mValue << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -750,7 +796,7 @@ void Record<ESM::DialInfo>::print()
if (mData.mCell != "")
std::cout << " Cell: " << mData.mCell << std::endl;
if (mData.mData.mDisposition > 0)
std::cout << " Disposition: " << mData.mData.mDisposition << std::endl;
std::cout << " Disposition/Journal index: " << mData.mData.mDisposition << std::endl;
if (mData.mData.mGender != ESM::DialInfo::NA)
std::cout << " Gender: " << mData.mData.mGender << std::endl;
if (mData.mSound != "")
@ -770,16 +816,17 @@ void Record<ESM::DialInfo>::print()
{
if (mPrintPlain)
{
std::cout << " Result Script:" << std::endl;
std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mResultScript << std::endl;
std::cout << "END----------------------------------------" << std::endl;
std::cout << " Result Script:" << std::endl;
std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mResultScript << std::endl;
std::cout << "END----------------------------------------" << std::endl;
}
else
{
std::cout << " Result Script: [skipped]" << std::endl;
std::cout << " Result Script: [skipped]" << std::endl;
}
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -803,6 +850,7 @@ void Record<ESM::Ingredient>::print()
std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i])
<< " (" << mData.mData.mAttributes[i] << ")" << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -810,22 +858,17 @@ void Record<ESM::Land>::print()
{
std::cout << " Coordinates: (" << mData.mX << "," << mData.mY << ")" << std::endl;
std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl;
std::cout << " HasData: " << mData.mHasData << std::endl;
std::cout << " DataTypes: " << mData.mDataTypes << std::endl;
// Seems like this should done with reference counting in the
// loader to me. But I'm not really knowledgable about this
// record type yet. --Cory
bool wasLoaded = mData.mDataLoaded;
if (mData.mDataTypes) mData.loadData(mData.mDataTypes);
if (mData.mDataLoaded)
if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes))
{
std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl;
std::cout << " Height Offset: " << data->mHeightOffset << std::endl;
// Lots of missing members.
std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl;
std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl;
std::cout << " Unknown1: " << data->mUnk1 << std::endl;
std::cout << " Unknown2: " << data->mUnk2 << std::endl;
}
if (!wasLoaded) mData.unloadData();
mData.unloadData();
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -834,10 +877,11 @@ void Record<ESM::CreatureLevList>::print()
std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl;
std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl;
std::cout << " Number of items: " << mData.mList.size() << std::endl;
std::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
std::vector<ESM::LevelledListBase::LevelItem>::iterator iit;
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
std::cout << " Creature: Level: " << iit->mLevel
<< " Creature: " << iit->mId << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -846,10 +890,11 @@ void Record<ESM::ItemLevList>::print()
std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl;
std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl;
std::cout << " Number of items: " << mData.mList.size() << std::endl;
std::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
std::vector<ESM::LevelledListBase::LevelItem>::iterator iit;
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
std::cout << " Inventory: Level: " << iit->mLevel
<< " Item: " << iit->mId << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -870,6 +915,7 @@ void Record<ESM::Light>::print()
std::cout << " Duration: " << mData.mData.mTime << std::endl;
std::cout << " Radius: " << mData.mData.mRadius << std::endl;
std::cout << " Color: " << mData.mData.mColor << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -884,6 +930,7 @@ void Record<ESM::Lockpick>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -898,6 +945,7 @@ void Record<ESM::Probe>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -912,6 +960,7 @@ void Record<ESM::Repair>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -920,6 +969,7 @@ void Record<ESM::LandTexture>::print()
std::cout << " Id: " << mData.mId << std::endl;
std::cout << " Index: " << mData.mIndex << std::endl;
std::cout << " Texture: " << mData.mTexture << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -970,6 +1020,7 @@ void Record<ESM::Miscellaneous>::print()
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Is Key: " << mData.mData.mIsKey << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -999,7 +1050,7 @@ void Record<ESM::NPC>::print()
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl;
std::cout << " Unknown3: "
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl;
std::cout << " Gold: " << (int)mData.mNpdt12.mGold << std::endl;
std::cout << " Gold: " << mData.mNpdt12.mGold << std::endl;
}
else {
std::cout << " Level: " << mData.mNpdt52.mLevel << std::endl;
@ -1021,7 +1072,7 @@ void Record<ESM::NPC>::print()
std::cout << " Skills:" << std::endl;
for (int i = 0; i != ESM::Skill::Length; i++)
std::cout << " " << skillLabel(i) << ": "
<< (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl;
<< (int)(mData.mNpdt52.mSkills[i]) << std::endl;
std::cout << " Health: " << mData.mNpdt52.mHealth << std::endl;
std::cout << " Magicka: " << mData.mNpdt52.mMana << std::endl;
@ -1039,20 +1090,7 @@ void Record<ESM::NPC>::print()
for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); ++sit)
std::cout << " Spell: " << *sit << std::endl;
std::vector<ESM::NPC::Dest>::iterator dit;
for (dit = mData.mTransport.begin(); dit != mData.mTransport.end(); ++dit)
{
std::cout << " Destination Position: "
<< boost::format("%12.3f") % dit->mPos.pos[0] << ","
<< boost::format("%12.3f") % dit->mPos.pos[1] << ","
<< boost::format("%12.3f") % dit->mPos.pos[2] << ")" << std::endl;
std::cout << " Destination Rotation: "
<< boost::format("%9.6f") % dit->mPos.rot[0] << ","
<< boost::format("%9.6f") % dit->mPos.rot[1] << ","
<< boost::format("%9.6f") % dit->mPos.rot[2] << ")" << std::endl;
if (dit->mCellName != "")
std::cout << " Destination Cell: " << dit->mCellName << std::endl;
}
printTransport(mData.getTransport());
std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl;
std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl;
@ -1068,6 +1106,8 @@ void Record<ESM::NPC>::print()
std::vector<ESM::AIPackage>::iterator pit;
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
printAIPackage(*pit);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1102,6 +1142,8 @@ void Record<ESM::Pathgrid>::print()
std::cout << " BAD POINT IN EDGE!" << std::endl;
i++;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1123,9 +1165,9 @@ void Record<ESM::Race>::print()
std::cout << (male ? " Male:" : " Female:") << std::endl;
for (int i=0; i<8; ++i)
std::cout << " " << sAttributeNames[i] << ": "
<< mData.mData.mAttributeValues[i].getValue (male) << std::endl;
for (int j=0; j<8; ++j)
std::cout << " " << sAttributeNames[j] << ": "
<< mData.mData.mAttributeValues[j].getValue (male) << std::endl;
std::cout << " Height: " << mData.mData.mHeight.getValue (male) << std::endl;
std::cout << " Weight: " << mData.mData.mWeight.getValue (male) << std::endl;
@ -1142,6 +1184,8 @@ void Record<ESM::Race>::print()
std::vector<std::string>::iterator sit;
for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit)
std::cout << " Power: " << *sit << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1192,15 +1236,17 @@ void Record<ESM::Script>::print()
if (mPrintPlain)
{
std::cout << " Script:" << std::endl;
std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mScriptText << std::endl;
std::cout << "END----------------------------------------" << std::endl;
std::cout << " Script:" << std::endl;
std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mScriptText << std::endl;
std::cout << "END----------------------------------------" << std::endl;
}
else
{
std::cout << " Script: [skipped]" << std::endl;
std::cout << " Script: [skipped]" << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1224,6 +1270,7 @@ void Record<ESM::SoundGenerator>::print()
std::cout << " Sound: " << mData.mSound << std::endl;
std::cout << " Type: " << soundTypeLabel(mData.mType)
<< " (" << mData.mType << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1234,6 +1281,7 @@ void Record<ESM::Sound>::print()
if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0)
std::cout << " Range: " << (int)mData.mData.mMinRange << " - "
<< (int)mData.mData.mMaxRange << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1245,13 +1293,15 @@ void Record<ESM::Spell>::print()
std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl;
std::cout << " Cost: " << mData.mData.mCost << std::endl;
printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
void Record<ESM::StartScript>::print()
{
std::cout << "Start Script: " << mData.mScript << std::endl;
std::cout << "Start Data: " << mData.mData << std::endl;
std::cout << " Start Script: " << mData.mId << std::endl;
std::cout << " Start Data: " << mData.mData << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1292,6 +1342,37 @@ void Record<ESM::Weapon>::print()
if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0)
std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-"
<< (int)mData.mData.mThrust[1] << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
std::string Record<ESM::Cell>::getId() const
{
return mData.mName;
}
template<>
std::string Record<ESM::Land>::getId() const
{
return ""; // No ID for Land record
}
template<>
std::string Record<ESM::MagicEffect>::getId() const
{
return ""; // No ID for MagicEffect record
}
template<>
std::string Record<ESM::Pathgrid>::getId() const
{
return ""; // No ID for Pathgrid record
}
template<>
std::string Record<ESM::Skill>::getId() const
{
return ""; // No ID for Skill record
}
} // end namespace

@ -19,7 +19,7 @@ namespace EsmTool
{
protected:
std::string mId;
int mFlags;
uint32_t mFlags;
ESM::NAME mType;
bool mPrintPlain;
@ -32,19 +32,13 @@ namespace EsmTool
virtual ~RecordBase() {}
const std::string &getId() const {
return mId;
}
void setId(const std::string &id) {
mId = id;
}
virtual std::string getId() const = 0;
int getFlags() const {
uint32_t getFlags() const {
return mFlags;
}
void setFlags(int flags) {
void setFlags(uint32_t flags) {
mFlags = flags;
}
@ -52,12 +46,8 @@ namespace EsmTool
return mType;
}
bool getPrintPlain() const {
return mPrintPlain;
}
void setPrintPlain(bool plain) {
mPrintPlain = plain;
mPrintPlain = plain;
}
virtual void load(ESM::ESMReader &esm) = 0;
@ -77,22 +67,37 @@ namespace EsmTool
class Record : public RecordBase
{
T mData;
bool mIsDeleted;
public:
Record()
: mIsDeleted(false)
{}
std::string getId() const {
return mData.mId;
}
T &get() {
return mData;
}
void save(ESM::ESMWriter &esm) {
mData.save(esm);
mData.save(esm, mIsDeleted);
}
void load(ESM::ESMReader &esm) {
mData.load(esm);
mData.load(esm, mIsDeleted);
}
void print();
};
template<> std::string Record<ESM::Cell>::getId() const;
template<> std::string Record<ESM::Land>::getId() const;
template<> std::string Record<ESM::MagicEffect>::getId() const;
template<> std::string Record<ESM::Pathgrid>::getId() const;
template<> std::string Record<ESM::Skill>::getId() const;
template<> void Record<ESM::Activator>::print();
template<> void Record<ESM::Potion>::print();

@ -0,0 +1,44 @@
set(ESSIMPORTER_FILES
main.cpp
importer.cpp
importplayer.cpp
importnpcc.cpp
importcrec.cpp
importcellref.cpp
importacdt.cpp
importinventory.cpp
importklst.cpp
importcntc.cpp
importgame.cpp
importinfo.cpp
importdial.cpp
importques.cpp
importjour.cpp
importscri.cpp
importscpt.cpp
importercontext.cpp
converter.cpp
convertacdt.cpp
convertnpcc.cpp
convertinventory.cpp
convertcrec.cpp
convertcntc.cpp
convertscri.cpp
convertscpt.cpp
convertplayer.cpp
)
add_executable(openmw-essimporter
${ESSIMPORTER_FILES}
)
target_link_libraries(openmw-essimporter
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(openmw-essimporter gcov)
endif()

@ -0,0 +1,52 @@
#include "convertacdt.hpp"
namespace ESSImport
{
int translateDynamicIndex(int mwIndex)
{
if (mwIndex == 1)
return 2;
else if (mwIndex == 2)
return 1;
return mwIndex;
}
void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats)
{
for (int i=0; i<3; ++i)
{
int writeIndex = translateDynamicIndex(i);
cStats.mDynamic[writeIndex].mBase = acdt.mDynamic[i][1];
cStats.mDynamic[writeIndex].mMod = acdt.mDynamic[i][1];
cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0];
}
for (int i=0; i<8; ++i)
{
cStats.mAttributes[i].mBase = static_cast<int>(acdt.mAttributes[i][1]);
cStats.mAttributes[i].mMod = static_cast<int>(acdt.mAttributes[i][0]);
cStats.mAttributes[i].mCurrent = static_cast<int>(acdt.mAttributes[i][0]);
}
cStats.mGoldPool = acdt.mGoldPool;
cStats.mTalkedTo = (acdt.mFlags & TalkedToPlayer) != 0;
cStats.mAttacked = (acdt.mFlags & Attacked) != 0;
}
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats)
{
cStats.mDead = (acsc.mFlags & Dead) != 0;
}
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats)
{
for (int i=0; i<ESM::Skill::Length; ++i)
{
npcStats.mSkills[i].mMod = actorData.mSkills[i][1];
npcStats.mSkills[i].mCurrent = actorData.mSkills[i][1];
npcStats.mSkills[i].mBase = actorData.mSkills[i][0];
}
npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter;
}
}

@ -0,0 +1,23 @@
#ifndef OPENMW_ESSIMPORT_CONVERTACDT_H
#define OPENMW_ESSIMPORT_CONVERTACDT_H
#include <components/esm/creaturestats.hpp>
#include <components/esm/npcstats.hpp>
#include <components/esm/loadskil.hpp>
#include "importacdt.hpp"
namespace ESSImport
{
// OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka
int translateDynamicIndex(int mwIndex);
void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats);
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats);
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats);
}
#endif

@ -0,0 +1,13 @@
#include "convertcntc.hpp"
#include "convertinventory.hpp"
namespace ESSImport
{
void convertCNTC(const CNTC &cntc, ESM::ContainerState &state)
{
convertInventory(cntc.mInventory, state.mInventory);
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTCNTC_H
#define OPENMW_ESSIMPORT_CONVERTCNTC_H
#include "importcntc.hpp"
#include <components/esm/containerstate.hpp>
namespace ESSImport
{
void convertCNTC(const CNTC& cntc, ESM::ContainerState& state);
}
#endif

@ -0,0 +1,13 @@
#include "convertcrec.hpp"
#include "convertinventory.hpp"
namespace ESSImport
{
void convertCREC(const CREC &crec, ESM::CreatureState &state)
{
convertInventory(crec.mInventory, state.mInventory);
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTCREC_H
#define OPENMW_ESSIMPORT_CONVERTCREC_H
#include "importcrec.hpp"
#include <components/esm/creaturestate.hpp>
namespace ESSImport
{
void convertCREC(const CREC& crec, ESM::CreatureState& state);
}
#endif

@ -0,0 +1,413 @@
#include "converter.hpp"
#include <stdexcept>
#include <osgDB/WriteFile>
#include <components/esm/creaturestate.hpp>
#include <components/esm/containerstate.hpp>
#include "convertcrec.hpp"
#include "convertcntc.hpp"
#include "convertscri.hpp"
namespace
{
void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out)
{
osg::ref_ptr<osg::Image> image (new osg::Image);
image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE);
memcpy(image->data(), data, size);
image->flipVertical();
osgDB::writeImageFile(*image, out);
}
void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate)
{
objstate.mEnabled = cellref.mEnabled;
objstate.mPosition = cellref.mPos;
objstate.mRef.mRefNum = cellref.mRefNum;
if (cellref.mDeleted)
objstate.mCount = 0;
convertSCRI(cellref.mSCRI, objstate.mLocals);
objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
}
bool isIndexedRefId(const std::string& indexedRefId)
{
if (indexedRefId.size() <= 8)
return false;
if (indexedRefId.find_first_not_of("0123456789") == std::string::npos)
return false; // entirely numeric refid, this is a reference to
// a dynamically created record e.g. player-enchanted weapon
std::string index = indexedRefId.substr(indexedRefId.size()-8);
if(index.find_first_not_of("0123456789ABCDEF") == std::string::npos )
return true;
return false;
}
}
namespace ESSImport
{
struct MAPH
{
unsigned int size;
unsigned int value;
};
void ConvertFMAP::read(ESM::ESMReader &esm)
{
MAPH maph;
esm.getHNT(maph, "MAPH");
std::vector<char> data;
esm.getSubNameIs("MAPD");
esm.getSubHeader();
data.resize(esm.getSubSize());
esm.getExact(&data[0], data.size());
mGlobalMapImage = new osg::Image;
mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE);
memcpy(mGlobalMapImage->data(), &data[0], data.size());
// to match openmw size
// FIXME: filtering?
mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE);
}
void ConvertFMAP::write(ESM::ESMWriter &esm)
{
int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly
// with the 512x512 map the game has by default
int cellSize = mGlobalMapImage->s()/numcells;
// Note the upper left corner of the (0,0) cell should be at (width/2, height/2)
mContext->mGlobalMapState.mBounds.mMinX = -numcells/2;
mContext->mGlobalMapState.mBounds.mMaxX = (numcells-1)/2;
mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2;
mContext->mGlobalMapState.mBounds.mMaxY = numcells/2;
osg::ref_ptr<osg::Image> image2 (new osg::Image);
int width = cellSize*numcells;
int height = cellSize*numcells;
std::vector<unsigned char> data;
data.resize(width*height*4, 0);
image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
memcpy(image2->data(), &data[0], data.size());
for (std::set<std::pair<int, int> >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it)
{
if (it->first > mContext->mGlobalMapState.mBounds.mMaxX
|| it->first < mContext->mGlobalMapState.mBounds.mMinX
|| it->second > mContext->mGlobalMapState.mBounds.mMaxY
|| it->second < mContext->mGlobalMapState.mBounds.mMinY)
{
// out of bounds, I think this could happen, since the original engine had a fixed-size map
continue;
}
int imageLeftSrc = mGlobalMapImage->s()/2;
int imageTopSrc = mGlobalMapImage->t()/2;
imageLeftSrc += it->first * cellSize;
imageTopSrc -= it->second * cellSize;
int imageLeftDst = width/2;
int imageTopDst = height/2;
imageLeftDst += it->first * cellSize;
imageTopDst -= it->second * cellSize;
for (int x=0; x<cellSize; ++x)
for (int y=0; y<cellSize; ++y)
{
unsigned int col = *(unsigned int*)mGlobalMapImage->data(imageLeftSrc+x, imageTopSrc+y, 0);
*(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col;
}
}
std::stringstream ostream;
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
if (!readerwriter)
{
std::cerr << "can't write global map image, no png readerwriter found" << std::endl;
return;
}
image2->flipVertical();
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream);
if (!result.success())
{
std::cerr << "can't write global map image: " << result.message() << " code " << result.status() << std::endl;
return;
}
std::string outData = ostream.str();
mContext->mGlobalMapState.mImageData = std::vector<char>(outData.begin(), outData.end());
esm.startRecord(ESM::REC_GMAP);
mContext->mGlobalMapState.save(esm);
esm.endRecord(ESM::REC_GMAP);
}
void ConvertCell::read(ESM::ESMReader &esm)
{
ESM::Cell cell;
bool isDeleted = false;
cell.load(esm, isDeleted, false);
// I wonder what 0x40 does?
if (cell.isExterior() && cell.mData.mFlags & 0x20)
{
mContext->mGlobalMapState.mMarkers.insert(std::make_pair(cell.mData.mX, cell.mData.mY));
}
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
if (cell.mName == mContext->mPlayerCellName)
{
mContext->mPlayer.mCellId = cell.getCellId();
}
Cell newcell;
newcell.mCell = cell;
// fog of war
// seems to be a 1-bit pixel format, 16*16 pixels
// TODO: add bleeding of FOW into neighbouring cells (openmw handles this by writing to the textures,
// MW handles it when rendering only)
unsigned char nam8[32];
// exterior has 1 NAM8, interior can have multiple ones, and have an extra 4 byte flag at the start
// (probably offset of that specific fog texture?)
while (esm.isNextSub("NAM8"))
{
if (cell.isExterior()) // TODO: NAM8 occasionally exists for cells that haven't been explored.
// are there any flags marking explored cells?
mContext->mExploredCells.insert(std::make_pair(cell.mData.mX, cell.mData.mY));
esm.getSubHeader();
if (esm.getSubSize() == 36)
{
// flag on interiors
esm.skip(4);
}
esm.getExact(nam8, 32);
newcell.mFogOfWar.reserve(16*16);
for (int x=0; x<16; ++x)
{
for (int y=0; y<16; ++y)
{
size_t pos = x*16+y;
size_t bytepos = pos/8;
assert(bytepos<32);
int bit = pos%8;
newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff);
}
}
if (cell.isExterior())
{
std::ostringstream filename;
filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga";
convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str());
}
}
// moved reference, not handled yet
// NOTE: MVRF can also occur in within normal references (importcellref.cpp)?
// this does not match the ESM file implementation,
// verify if that can happen with ESM files too
while (esm.isNextSub("MVRF"))
{
esm.skipHSub(); // skip MVRF
esm.getSubName();
esm.skipHSub(); // skip CNDT
}
std::vector<CellRef> cellrefs;
while (esm.hasMoreSubs() && esm.isNextSub("FRMR"))
{
CellRef ref;
ref.load (esm);
cellrefs.push_back(ref);
}
while (esm.isNextSub("MPCD"))
{
float notepos[3];
esm.getHT(notepos, 3*sizeof(float));
// Markers seem to be arranged in a 32*32 grid, notepos has grid-indices.
// This seems to be the reason markers can't be placed everywhere in interior cells,
// i.e. when the grid is exceeded.
// Converting the interior markers correctly could be rather tricky, but is probably similar logic
// as used for the FoW texture placement, which we need to figure out anyway
notepos[1] += 31.f;
notepos[0] += 0.5;
notepos[1] += 0.5;
notepos[0] = 8192 * notepos[0] / 32.f;
notepos[1] = 8192 * notepos[1] / 32.f;
if (cell.isExterior())
{
notepos[0] += 8192 * cell.mData.mX;
notepos[1] += 8192 * cell.mData.mY;
}
// TODO: what encoding is this in?
std::string note = esm.getHNString("MPNT");
ESM::CustomMarker marker;
marker.mWorldX = notepos[0];
marker.mWorldY = notepos[1];
marker.mNote = note;
marker.mCell = cell.getCellId();
mMarkers.push_back(marker);
}
newcell.mRefs = cellrefs;
if (cell.isExterior())
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
else
mIntCells[cell.mName] = newcell;
}
void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)
{
ESM::Cell esmcell = cell.mCell;
esm.startRecord(ESM::REC_CSTA);
ESM::CellState csta;
csta.mHasFogOfWar = 0;
csta.mId = esmcell.getCellId();
csta.mId.save(esm);
// TODO csta.mLastRespawn;
// shouldn't be needed if we respawn on global schedule like in original MW
csta.mWaterLevel = esmcell.mWater;
csta.save(esm);
for (std::vector<CellRef>::const_iterator refIt = cell.mRefs.begin(); refIt != cell.mRefs.end(); ++refIt)
{
const CellRef& cellref = *refIt;
ESM::CellRef out (cellref);
// TODO: use mContext->mCreatures/mNpcs
if (!isIndexedRefId(cellref.mIndexedRefId))
{
// non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it
// this could be any type of object really (even creatures/npcs too)
out.mRefID = cellref.mIndexedRefId;
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
objstate.mHasCustomState = false;
convertCellRef(cellref, objstate);
esm.writeHNT ("OBJE", 0);
objstate.save(esm);
continue;
}
else
{
std::stringstream stream;
stream << std::hex << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8);
int refIndex;
stream >> refIndex;
out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8);
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
std::make_pair(refIndex, out.mRefID));
if (npccIt != mContext->mNpcChanges.end())
{
ESM::NpcState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
// TODO: need more micromanagement here so we don't overwrite values
// from the ESM with default values
if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats);
if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertNpcData(cellref, objstate.mNpcStats);
convertNPCC(npccIt->second, objstate);
convertCellRef(cellref, objstate);
esm.writeHNT ("OBJE", ESM::REC_NPC_);
objstate.save(esm);
continue;
}
std::map<std::pair<int, std::string>, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find(
std::make_pair(refIndex, out.mRefID));
if (cntcIt != mContext->mContainerChanges.end())
{
ESM::ContainerState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
convertCNTC(cntcIt->second, objstate);
convertCellRef(cellref, objstate);
esm.writeHNT ("OBJE", ESM::REC_CONT);
objstate.save(esm);
continue;
}
std::map<std::pair<int, std::string>, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find(
std::make_pair(refIndex, out.mRefID));
if (crecIt != mContext->mCreatureChanges.end())
{
ESM::CreatureState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
// TODO: need more micromanagement here so we don't overwrite values
// from the ESM with default values
if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats);
if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertCREC(crecIt->second, objstate);
convertCellRef(cellref, objstate);
esm.writeHNT ("OBJE", ESM::REC_CREA);
objstate.save(esm);
continue;
}
std::stringstream error;
error << "Can't find type for " << cellref.mIndexedRefId << std::endl;
throw std::runtime_error(error.str());
}
}
esm.endRecord(ESM::REC_CSTA);
}
void ConvertCell::write(ESM::ESMWriter &esm)
{
for (std::map<std::string, Cell>::const_iterator it = mIntCells.begin(); it != mIntCells.end(); ++it)
writeCell(it->second, esm);
for (std::map<std::pair<int, int>, Cell>::const_iterator it = mExtCells.begin(); it != mExtCells.end(); ++it)
writeCell(it->second, esm);
for (std::vector<ESM::CustomMarker>::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it)
{
esm.startRecord(ESM::REC_MARK);
it->save(esm);
esm.endRecord(ESM::REC_MARK);
}
}
}

@ -0,0 +1,587 @@
#ifndef OPENMW_ESSIMPORT_CONVERTER_H
#define OPENMW_ESSIMPORT_CONVERTER_H
#include <limits>
#include <osg/Image>
#include <osg/ref_ptr>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadbook.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadglob.hpp>
#include <components/esm/cellstate.hpp>
#include <components/esm/loadfact.hpp>
#include <components/esm/dialoguestate.hpp>
#include <components/esm/custommarkerstate.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/weatherstate.hpp>
#include <components/esm/globalscript.hpp>
#include <components/esm/queststate.hpp>
#include <components/esm/stolenitems.hpp>
#include "importcrec.hpp"
#include "importcntc.hpp"
#include "importercontext.hpp"
#include "importcellref.hpp"
#include "importklst.hpp"
#include "importgame.hpp"
#include "importinfo.hpp"
#include "importdial.hpp"
#include "importques.hpp"
#include "importjour.hpp"
#include "importscpt.hpp"
#include "convertacdt.hpp"
#include "convertnpcc.hpp"
#include "convertscpt.hpp"
#include "convertplayer.hpp"
namespace ESSImport
{
class Converter
{
public:
/// @return the order for writing this converter's records to the output file, in relation to other converters
virtual int getStage() { return 1; }
virtual ~Converter() {}
void setContext(Context& context) { mContext = &context; }
/// @note The load method of ESM records accept the deleted flag as a parameter.
/// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored.
virtual void read(ESM::ESMReader& esm)
{
}
/// Called after the input file has been read in completely, which may be necessary
/// if the conversion process relies on information in other records
virtual void write(ESM::ESMWriter& esm)
{
}
protected:
Context* mContext;
};
/// Default converter: simply reads the record and writes it unmodified to the output
template <typename T>
class DefaultConverter : public Converter
{
public:
virtual int getStage() { return 0; }
virtual void read(ESM::ESMReader& esm)
{
T record;
bool isDeleted = false;
record.load(esm, isDeleted);
mRecords[record.mId] = record;
}
virtual void write(ESM::ESMWriter& esm)
{
for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
{
esm.startRecord(T::sRecordId);
it->second.save(esm);
esm.endRecord(T::sRecordId);
}
}
protected:
std::map<std::string, T> mRecords;
};
class ConvertNPC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
ESM::NPC npc;
bool isDeleted = false;
npc.load(esm, isDeleted);
if (npc.mId != "player")
{
// Handles changes to the NPC struct, but since there is no index here
// it will apply to ALL instances of the class. seems to be the reason for the
// "feature" in MW where changing AI settings of one guard will change it for all guards of that refID.
mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc;
}
else
{
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel;
mContext->mPlayerBase = npc;
ESM::SpellState::SpellParams empty;
// FIXME: player start spells and birthsign spells aren't listed here,
// need to fix openmw to account for this
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty;
// Clear the list now that we've written it, this prevents issues cropping up with
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.
mContext->mPlayerBase.mSpells.mList.clear();
// Same with inventory. Actually it's strange this would contain something, since there's already an
// inventory list in NPCC. There seems to be a fair amount of redundancy in this format.
mContext->mPlayerBase.mInventory.mList.clear();
}
}
};
class ConvertCREA : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
// See comment in ConvertNPC
ESM::Creature creature;
bool isDeleted = false;
creature.load(esm, isDeleted);
mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature;
}
};
// Do we need ConvertCONT?
// I've seen a CONT record in a certain save file, but the container contents in it
// were identical to a corresponding CNTC record. See previous comment about redundancy...
class ConvertGlobal : public DefaultConverter<ESM::Global>
{
public:
virtual void read(ESM::ESMReader &esm)
{
ESM::Global global;
bool isDeleted = false;
global.load(esm, isDeleted);
if (Misc::StringUtils::ciEqual(global.mId, "gamehour"))
mContext->mHour = global.mValue.getFloat();
if (Misc::StringUtils::ciEqual(global.mId, "day"))
mContext->mDay = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(global.mId, "month"))
mContext->mMonth = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(global.mId, "year"))
mContext->mYear = global.mValue.getInteger();
mRecords[global.mId] = global;
}
};
class ConvertClass : public DefaultConverter<ESM::Class>
{
public:
virtual void read(ESM::ESMReader &esm)
{
ESM::Class class_;
bool isDeleted = false;
class_.load(esm, isDeleted);
if (class_.mId == "NEWCLASSID_CHARGEN")
mContext->mCustomPlayerClassName = class_.mName;
mRecords[class_.mId] = class_;
}
};
class ConvertBook : public DefaultConverter<ESM::Book>
{
public:
virtual void read(ESM::ESMReader &esm)
{
ESM::Book book;
bool isDeleted = false;
book.load(esm, isDeleted);
if (book.mData.mSkillID == -1)
mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId));
mRecords[book.mId] = book;
}
};
class ConvertNPCC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
NPCC npcc;
npcc.load(esm);
if (id == "PlayerSaveGame")
{
convertNPCC(npcc, mContext->mPlayer.mObject);
}
else
{
int index = npcc.mNPDT.mIndex;
mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index,id), npcc));
}
}
};
class ConvertREFR : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
REFR refr;
refr.load(esm);
assert(refr.mRefID == "PlayerSaveGame");
mContext->mPlayer.mObject.mPosition = refr.mPos;
ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats;
convertACDT(refr.mActorData.mACDT, cStats);
ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats;
convertNpcData(refr.mActorData, npcStats);
mSelectedSpell = refr.mActorData.mSelectedSpell;
if (!refr.mActorData.mSelectedEnchantItem.empty())
{
ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory;
for (unsigned int i=0; i<invState.mItems.size(); ++i)
{
// FIXME: in case of conflict (multiple items with this refID) use the already equipped one?
if (Misc::StringUtils::ciEqual(invState.mItems[i].mRef.mRefID, refr.mActorData.mSelectedEnchantItem))
invState.mSelectedEnchantItem = i;
}
}
}
virtual void write(ESM::ESMWriter& esm)
{
esm.startRecord(ESM::REC_ASPL);
esm.writeHNString("ID__", mSelectedSpell);
esm.endRecord(ESM::REC_ASPL);
}
private:
std::string mSelectedSpell;
};
class ConvertPCDT : public Converter
{
public:
ConvertPCDT() : mFirstPersonCam(true) {}
virtual void read(ESM::ESMReader &esm)
{
PCDT pcdt;
pcdt.load(esm);
convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam);
}
virtual void write(ESM::ESMWriter &esm)
{
esm.startRecord(ESM::REC_CAM_);
esm.writeHNT("FIRS", mFirstPersonCam);
esm.endRecord(ESM::REC_CAM_);
}
private:
bool mFirstPersonCam;
};
class ConvertCNTC : public Converter
{
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
CNTC cntc;
cntc.load(esm);
mContext->mContainerChanges.insert(std::make_pair(std::make_pair(cntc.mIndex,id), cntc));
}
};
class ConvertCREC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
CREC crec;
crec.load(esm);
mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec));
}
};
class ConvertFMAP : public Converter
{
public:
virtual void read(ESM::ESMReader &esm);
virtual void write(ESM::ESMWriter &esm);
private:
osg::ref_ptr<osg::Image> mGlobalMapImage;
};
class ConvertCell : public Converter
{
public:
virtual void read(ESM::ESMReader& esm);
virtual void write(ESM::ESMWriter& esm);
private:
struct Cell
{
ESM::Cell mCell;
std::vector<CellRef> mRefs;
std::vector<unsigned int> mFogOfWar;
};
std::map<std::string, Cell> mIntCells;
std::map<std::pair<int, int>, Cell> mExtCells;
std::vector<ESM::CustomMarker> mMarkers;
void writeCell(const Cell& cell, ESM::ESMWriter &esm);
};
class ConvertKLST : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
KLST klst;
klst.load(esm);
mKillCounter = klst.mKillCounter;
mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills;
}
virtual void write(ESM::ESMWriter &esm)
{
esm.startRecord(ESM::REC_DCOU);
for (std::map<std::string, int>::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it)
{
esm.writeHNString("ID__", it->first);
esm.writeHNT ("COUN", it->second);
}
esm.endRecord(ESM::REC_DCOU);
}
private:
std::map<std::string, int> mKillCounter;
};
class ConvertFACT : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
ESM::Faction faction;
bool isDeleted = false;
faction.load(esm, isDeleted);
std::string id = Misc::StringUtils::lowerCase(faction.mId);
for (std::map<std::string, int>::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
{
std::string faction2 = Misc::StringUtils::lowerCase(it->first);
mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second));
}
}
};
/// Stolen items
class ConvertSTLN : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string itemid = esm.getHNString("NAME");
Misc::StringUtils::lowerCaseInPlace(itemid);
while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM"))
{
if (esm.retSubName().toString() == "FNAM")
{
std::string factionid = esm.getHString();
mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(factionid), true));
}
else
{
std::string ownerid = esm.getHString();
mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false));
}
}
}
virtual void write(ESM::ESMWriter &esm)
{
ESM::StolenItems items;
for (std::map<std::string, std::set<Owner> >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it)
{
std::map<std::pair<std::string, bool>, int> owners;
for (std::set<Owner>::const_iterator ownerIt = it->second.begin(); ownerIt != it->second.end(); ++ownerIt)
{
owners.insert(std::make_pair(std::make_pair(ownerIt->first, ownerIt->second)
// Since OpenMW doesn't suffer from the owner contamination bug,
// it needs a count argument. But for legacy savegames, we don't know
// this count, so must assume all items of that ID are stolen,
// like vanilla MW did.
,std::numeric_limits<int>::max()));
}
items.mStolenItems.insert(std::make_pair(it->first, owners));
}
esm.startRecord(ESM::REC_STLN);
items.write(esm);
esm.endRecord(ESM::REC_STLN);
}
private:
typedef std::pair<std::string, bool> Owner; // <owner id, bool isFaction>
std::map<std::string, std::set<Owner> > mStolenItems;
};
/// Seen responses for a dialogue topic?
/// Each DIAL record is followed by a number of INFO records, I believe, just like in ESMs
/// Dialogue conversion problems:
/// - Journal is stored in one continuous HTML markup rather than each entry separately with associated info ID.
/// - Seen dialogue responses only store the INFO id, rather than the fulltext.
/// - Quest stages only store the INFO id, rather than the journal entry fulltext.
class ConvertINFO : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
INFO info;
info.load(esm);
}
};
class ConvertDIAL : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
std::string id = esm.getHNString("NAME");
DIAL dial;
dial.load(esm);
if (dial.mIndex > 0)
mDials[id] = dial;
}
virtual void write(ESM::ESMWriter &esm)
{
for (std::map<std::string, DIAL>::const_iterator it = mDials.begin(); it != mDials.end(); ++it)
{
esm.startRecord(ESM::REC_QUES);
ESM::QuestState state;
state.mFinished = 0;
state.mState = it->second.mIndex;
state.mTopic = Misc::StringUtils::lowerCase(it->first);
state.save(esm);
esm.endRecord(ESM::REC_QUES);
}
}
private:
std::map<std::string, DIAL> mDials;
};
class ConvertQUES : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
std::string id = esm.getHNString("NAME");
QUES quest;
quest.load(esm);
}
};
class ConvertJOUR : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
JOUR journal;
journal.load(esm);
}
};
class ConvertGAME : public Converter
{
public:
ConvertGAME() : mHasGame(false) {}
virtual void read(ESM::ESMReader &esm)
{
mGame.load(esm);
mHasGame = true;
}
int validateWeatherID(int weatherID)
{
if(weatherID >= -1 && weatherID < 10)
{
return weatherID;
}
else
{
std::stringstream error;
error << "Invalid weather ID:" << weatherID << std::endl;
throw std::runtime_error(error.str());
}
}
virtual void write(ESM::ESMWriter &esm)
{
if (!mHasGame)
return;
esm.startRecord(ESM::REC_WTHR);
ESM::WeatherState weather;
weather.mTimePassed = 0.0f;
weather.mFastForward = false;
weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour;
weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f);
weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather);
weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather);
weather.mQueuedWeather = -1;
// TODO: Determine how ModRegion modifiers are saved in Morrowind.
weather.save(esm);
esm.endRecord(ESM::REC_WTHR);
}
private:
bool mHasGame;
GAME mGame;
};
/// Running global script
class ConvertSCPT : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
SCPT script;
script.load(esm);
ESM::GlobalScript out;
convertSCPT(script, out);
mScripts.push_back(out);
}
virtual void write(ESM::ESMWriter &esm)
{
for (std::vector<ESM::GlobalScript>::const_iterator it = mScripts.begin(); it != mScripts.end(); ++it)
{
esm.startRecord(ESM::REC_GSCR);
it->save(esm);
esm.endRecord(ESM::REC_GSCR);
}
}
private:
std::vector<ESM::GlobalScript> mScripts;
};
}
#endif

@ -0,0 +1,31 @@
#include "convertinventory.hpp"
#include <components/misc/stringops.hpp>
#include <cstdlib>
namespace ESSImport
{
void convertInventory(const Inventory &inventory, ESM::InventoryState &state)
{
int index = 0;
for (std::vector<Inventory::InventoryItem>::const_iterator it = inventory.mItems.begin();
it != inventory.mItems.end(); ++it)
{
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = *it;
objstate.mRef.mRefID = Misc::StringUtils::lowerCase(it->mId);
objstate.mCount = std::abs(it->mCount); // restocking items have negative count in the savefile
// openmw handles them differently, so no need to set any flags
state.mItems.push_back(objstate);
if (it->mRelativeEquipmentSlot != -1)
// Note we should really write the absolute slot here, which we do not know about
// Not a big deal, OpenMW will auto-correct to a valid slot, the only problem is when
// an item could be equipped in two different slots (e.g. equipped two rings)
state.mEquipmentSlots[index] = it->mRelativeEquipmentSlot;
++index;
}
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTINVENTORY_H
#define OPENMW_ESSIMPORT_CONVERTINVENTORY_H
#include "importinventory.hpp"
#include <components/esm/inventorystate.hpp>
namespace ESSImport
{
void convertInventory (const Inventory& inventory, ESM::InventoryState& state);
}
#endif

@ -0,0 +1,15 @@
#include "convertnpcc.hpp"
#include "convertinventory.hpp"
namespace ESSImport
{
void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState)
{
npcState.mNpcStats.mDisposition = npcc.mNPDT.mDisposition;
npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation;
convertInventory(npcc.mInventory, npcState.mInventory);
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTNPCC_H
#define OPENMW_ESSIMPORT_CONVERTNPCC_H
#include "importnpcc.hpp"
#include <components/esm/npcstate.hpp>
namespace ESSImport
{
void convertNPCC (const NPCC& npcc, ESM::NpcState& npcState);
}
#endif

@ -0,0 +1,42 @@
#include "convertplayer.hpp"
#include <components/misc/stringops.hpp>
namespace ESSImport
{
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam)
{
out.mBirthsign = pcdt.mBirthsign;
out.mObject.mNpcStats.mBounty = pcdt.mBounty;
for (std::vector<PCDT::FNAM>::const_iterator it = pcdt.mFactions.begin(); it != pcdt.mFactions.end(); ++it)
{
ESM::NpcStats::Faction faction;
faction.mExpelled = (it->mFlags & 0x2) != 0;
faction.mRank = it->mRank;
faction.mReputation = it->mReputation;
out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction;
}
for (int i=0; i<3; ++i)
out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i];
for (int i=0; i<8; ++i)
out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i];
for (int i=0; i<27; ++i)
out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i];
out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress;
if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Weapon)
out.mObject.mCreatureStats.mDrawState = 1;
if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Spell)
out.mObject.mCreatureStats.mDrawState = 2;
firstPersonCam = !(pcdt.mPNAM.mCameraFlags & PCDT::CameraFlag_ThirdPerson);
for (std::vector<std::string>::const_iterator it = pcdt.mKnownDialogueTopics.begin();
it != pcdt.mKnownDialogueTopics.end(); ++it)
{
outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it));
}
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTPLAYER_H
#define OPENMW_ESSIMPORT_CONVERTPLAYER_H
#include "importplayer.hpp"
#include <components/esm/player.hpp>
namespace ESSImport
{
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam);
}
#endif

@ -0,0 +1,17 @@
#include "convertscpt.hpp"
#include <components/misc/stringops.hpp>
#include "convertscri.hpp"
namespace ESSImport
{
void convertSCPT(const SCPT &scpt, ESM::GlobalScript &out)
{
out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString());
out.mRunning = scpt.mRunning;
convertSCRI(scpt.mSCRI, out.mLocals);
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H
#define OPENMW_ESSIMPORT_CONVERTSCPT_H
#include <components/esm/globalscript.hpp>
#include "importscpt.hpp"
namespace ESSImport
{
void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out);
}
#endif

@ -0,0 +1,32 @@
#include "convertscri.hpp"
#include <iostream>
namespace
{
template <typename T, ESM::VarType VariantType>
void storeVariables(const std::vector<T>& variables, ESM::Locals& locals, const std::string& scriptname)
{
for (typename std::vector<T>::const_iterator it = variables.begin(); it != variables.end(); ++it)
{
ESM::Variant val(*it);
val.setType(VariantType);
locals.mVariables.push_back(std::make_pair(std::string(), val));
}
}
}
namespace ESSImport
{
void convertSCRI(const SCRI &scri, ESM::Locals &locals)
{
// order *is* important, as we do not have variable names available in this format
storeVariables<short, ESM::VT_Short> (scri.mShorts, locals, scri.mScript);
storeVariables<int, ESM::VT_Int> (scri.mLongs, locals, scri.mScript);
storeVariables<float, ESM::VT_Float> (scri.mFloats, locals, scri.mScript);
}
}

@ -0,0 +1,16 @@
#ifndef OPENMW_ESSIMPORT_CONVERTSCRI_H
#define OPENMW_ESSIMPORT_CONVERTSCRI_H
#include "importscri.hpp"
#include <components/esm/locals.hpp>
namespace ESSImport
{
/// Convert script variable assignments
void convertSCRI (const SCRI& scri, ESM::Locals& locals);
}
#endif

@ -0,0 +1,130 @@
#include "importacdt.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm/cellref.hpp>
namespace ESSImport
{
void ActorData::load(ESM::ESMReader &esm)
{
if (esm.isNextSub("ACTN"))
{
/*
Activation flags:
ActivationFlag_UseEnabled = 1
ActivationFlag_OnActivate = 2
ActivationFlag_OnDeath = 10h
ActivationFlag_OnKnockout = 20h
ActivationFlag_OnMurder = 40h
ActivationFlag_DoorOpening = 100h
ActivationFlag_DoorClosing = 200h
ActivationFlag_DoorJammedOpening = 400h
ActivationFlag_DoorJammedClosing = 800h
*/
esm.skipHSub();
}
if (esm.isNextSub("STPR"))
esm.skipHSub();
if (esm.isNextSub("MNAM"))
esm.skipHSub();
bool isDeleted = false;
ESM::CellRef::loadData(esm, isDeleted);
mHasACDT = false;
if (esm.isNextSub("ACDT"))
{
mHasACDT = true;
esm.getHT(mACDT);
}
mHasACSC = false;
if (esm.isNextSub("ACSC"))
{
mHasACSC = true;
esm.getHT(mACSC);
}
if (esm.isNextSub("ACSL"))
esm.skipHSubSize(112);
if (esm.isNextSub("CSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
if (esm.isNextSub("LSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure at which point between LSTN and TGTN
if (esm.isNextSub("CSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure if before or after CSTN/LSTN
if (esm.isNextSub("LSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("TGTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("FGTN"))
esm.getHString(); // fight target?
// unsure at which point between TGTN and CRED
if (esm.isNextSub("AADT"))
{
// occurred when a creature was in the middle of its attack, 44 bytes
esm.skipHSub();
}
// unsure at which point between FGTN and CHRD
if (esm.isNextSub("PWPC"))
esm.skipHSub();
if (esm.isNextSub("PWPS"))
esm.skipHSub();
if (esm.isNextSub("WNAM"))
{
std::string id = esm.getHString();
if (esm.isNextSub("XNAM"))
mSelectedEnchantItem = esm.getHString();
else
mSelectedSpell = id;
if (esm.isNextSub("YNAM"))
esm.skipHSub(); // 4 byte, 0
}
while (esm.isNextSub("APUD"))
{
// used power
esm.getSubHeader();
std::string id = esm.getString(32);
(void)id;
// timestamp can't be used: this is the total hours passed, calculated by
// timestamp = 24 * (365 * year + cumulativeDays[month] + day)
// unfortunately cumulativeDays[month] is not clearly defined,
// in the (non-MCP) vanilla version the first month was missing, but MCP added it.
double timestamp;
esm.getT(timestamp);
}
// FIXME: not all actors have this, add flag
if (esm.isNextSub("CHRD")) // npc only
esm.getHExact(mSkills, 27*2*sizeof(int));
if (esm.isNextSub("CRED")) // creature only
esm.getHExact(mCombatStats, 3*2*sizeof(int));
mSCRI.load(esm);
if (esm.isNextSub("ND3D"))
esm.skipHSub();
if (esm.isNextSub("ANIS"))
esm.skipHSub();
}
}

@ -0,0 +1,85 @@
#ifndef OPENMW_ESSIMPORT_ACDT_H
#define OPENMW_ESSIMPORT_ACDT_H
#include <string>
#include <components/esm/cellref.hpp>
#include "importscri.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
enum ACDTFlags
{
TalkedToPlayer = 0x4,
Attacked = 0x100,
Unknown = 0x200
};
enum ACSCFlags
{
Dead = 0x2
};
/// Actor data, shared by (at least) REFR and CellRef
#pragma pack(push)
#pragma pack(1)
struct ACDT
{
// Note, not stored at *all*:
// - Level changes are lost on reload, except for the player (there it's in the NPC record).
unsigned char mUnknown[12];
unsigned int mFlags;
float mBreathMeter; // Seconds left before drowning
unsigned char mUnknown2[20];
float mDynamic[3][2];
unsigned char mUnknown3[16];
float mAttributes[8][2];
float mMagicEffects[27]; // Effect attributes: https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes
unsigned char mUnknown4[4];
unsigned int mGoldPool;
unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe
// this one is for respawning?
unsigned char mUnknown5[3];
};
struct ACSC
{
unsigned char mUnknown1[17];
unsigned char mFlags; // ACSCFlags
unsigned char mUnknown2[22];
unsigned char mCorpseClearCountdown; // hours?
unsigned char mUnknown3[71];
};
#pragma pack(pop)
struct ActorData : public ESM::CellRef
{
bool mHasACDT;
ACDT mACDT;
bool mHasACSC;
ACSC mACSC;
int mSkills[27][2]; // skills, base and modified
// creature combat stats, base and modified
// I think these can be ignored in the conversion, because it is not possible
// to change them ingame
int mCombatStats[3][2];
std::string mSelectedSpell;
std::string mSelectedEnchantItem;
SCRI mSCRI;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,57 @@
#include "importcellref.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CellRef::load(ESM::ESMReader &esm)
{
blank();
// (FRMR subrecord name is already read by the loop in ConvertCell)
esm.getHT(mRefNum.mIndex); // FRMR
// this is required since openmw supports more than 255 content files
int pluginIndex = (mRefNum.mIndex & 0xff000000) >> 24;
mRefNum.mContentFile = pluginIndex-1;
mRefNum.mIndex &= 0x00ffffff;
mIndexedRefId = esm.getHNString("NAME");
ActorData::load(esm);
if (esm.isNextSub("LVCR"))
{
// occurs on levelled creature spawner references
// probably some identifier for the creature that has been spawned?
unsigned char lvcr;
esm.getHT(lvcr);
//std::cout << "LVCR: " << (int)lvcr << std::endl;
}
mEnabled = true;
esm.getHNOT(mEnabled, "ZNAM");
// DATA should occur for all references, except levelled creature spawners
// I've seen DATA *twice* on a creature record, and with the exact same content too! weird
// alarmvoi0000.ess
esm.getHNOT(mPos, "DATA", 24);
esm.getHNOT(mPos, "DATA", 24);
mDeleted = 0;
if (esm.isNextSub("DELE"))
{
unsigned int deleted;
esm.getHT(deleted);
mDeleted = ((deleted >> 24) & 0x2) != 0; // the other 3 bytes seem to be uninitialized garbage
}
if (esm.isNextSub("MVRF"))
{
esm.skipHSub();
esm.getSubName();
esm.skipHSub();
}
}
}

@ -0,0 +1,33 @@
#ifndef OPENMW_ESSIMPORT_CELLREF_H
#define OPENMW_ESSIMPORT_CELLREF_H
#include <string>
#include <components/esm/cellref.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct CellRef : public ActorData
{
std::string mIndexedRefId;
std::string mScript;
bool mEnabled;
bool mDeleted;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,16 @@
#include "importcntc.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CNTC::load(ESM::ESMReader &esm)
{
mIndex = 0;
esm.getHNT(mIndex, "INDX");
mInventory.load(esm);
}
}

@ -0,0 +1,25 @@
#ifndef OPENMW_ESSIMPORT_IMPORTCNTC_H
#define OPENMW_ESSIMPORT_IMPORTCNTC_H
#include "importinventory.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Changed container contents
struct CNTC
{
int mIndex;
Inventory mInventory;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,25 @@
#include "importcrec.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CREC::load(ESM::ESMReader &esm)
{
esm.getHNT(mIndex, "INDX");
// equivalent of ESM::Creature XSCL? probably don't have to convert this,
// since the value can't be changed
float scale;
esm.getHNOT(scale, "XSCL");
while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F")
|| esm.isNextSub("AI_A"))
mAiPackages.add(esm);
mInventory.load(esm);
}
}

@ -0,0 +1,28 @@
#ifndef OPENMW_ESSIMPORT_CREC_H
#define OPENMW_ESSIMPORT_CREC_H
#include "importinventory.hpp"
#include <components/esm/aipackage.hpp>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Creature changes
struct CREC
{
int mIndex;
Inventory mInventory;
ESM::AIPackageList mAiPackages;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,23 @@
#include "importdial.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void DIAL::load(ESM::ESMReader &esm)
{
// See ESM::Dialogue::Type enum, not sure why we would need this here though
int type = 0;
esm.getHNOT(type, "DATA");
// Deleted dialogue in a savefile. No clue what this means...
int deleted = 0;
esm.getHNOT(deleted, "DELE");
mIndex = 0;
// *should* always occur except when the dialogue is deleted, but leaving it optional just in case...
esm.getHNOT(mIndex, "XIDX");
}
}

@ -0,0 +1,20 @@
#ifndef OPENMW_ESSIMPORT_IMPORTDIAL_H
#define OPENMW_ESSIMPORT_IMPORTDIAL_H
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct DIAL
{
int mIndex; // Journal index
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,428 @@
#include "importer.hpp"
#include <iomanip>
#include <boost/shared_ptr.hpp>
#include <osgDB/ReadFile>
#include <osg/ImageUtils>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/savedgame.hpp>
#include <components/esm/player.hpp>
#include <components/esm/loadalch.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadench.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm/loadglob.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include "importercontext.hpp"
#include "converter.hpp"
namespace
{
void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out)
{
if (fileHeader.mSCRS.size() != 128*128*4)
{
std::cerr << "unexpected screenshot size " << std::endl;
return;
}
osg::ref_ptr<osg::Image> image (new osg::Image);
image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);
// need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise
std::vector<unsigned char>::const_iterator it = fileHeader.mSCRS.begin();
for (int y=0; y<128; ++y)
{
for (int x=0; x<128; ++x)
{
*(image->data(x,y)+2) = *it++;
*(image->data(x,y)+1) = *it++;
*image->data(x,y) = *it++;
++it; // skip alpha
}
}
image->flipVertical();
std::stringstream ostream;
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
if (!readerwriter)
{
std::cerr << "can't write screenshot: no jpg readerwriter found" << std::endl;
return;
}
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image, ostream);
if (!result.success())
{
std::cerr << "can't write screenshot: " << result.message() << " code " << result.status() << std::endl;
return;
}
std::string data = ostream.str();
out.mScreenshot = std::vector<char>(data.begin(), data.end());
}
}
namespace ESSImport
{
Importer::Importer(const std::string &essfile, const std::string &outfile, const std::string &encoding)
: mEssFile(essfile)
, mOutFile(outfile)
, mEncoding(encoding)
{
}
struct File
{
struct Subrecord
{
std::string mName;
size_t mFileOffset;
std::vector<unsigned char> mData;
};
struct Record
{
std::string mName;
size_t mFileOffset;
std::vector<Subrecord> mSubrecords;
};
std::vector<Record> mRecords;
};
void read(const std::string& filename, File& file)
{
ESM::ESMReader esm;
esm.open(filename);
while (esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
File::Record rec;
rec.mName = n.toString();
rec.mFileOffset = esm.getFileOffset();
while (esm.hasMoreSubs())
{
File::Subrecord sub;
esm.getSubName();
esm.getSubHeader();
sub.mFileOffset = esm.getFileOffset();
sub.mName = esm.retSubName().toString();
sub.mData.resize(esm.getSubSize());
esm.getExact(&sub.mData[0], sub.mData.size());
rec.mSubrecords.push_back(sub);
}
file.mRecords.push_back(rec);
}
}
void Importer::compare()
{
// data that always changes (and/or is already fully decoded) should be blacklisted
std::set<std::pair<std::string, std::string> > blacklist;
blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour
blacklist.insert(std::make_pair("REFR", "DATA")); // player position
blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war
blacklist.insert(std::make_pair("GAME", "GMDT")); // weather data, current time always changes
blacklist.insert(std::make_pair("CELL", "DELE")); // first 3 bytes are uninitialized
// this changes way too often, name suggests some renderer internal data?
blacklist.insert(std::make_pair("CELL", "ND3D"));
blacklist.insert(std::make_pair("REFR", "ND3D"));
File file1;
read(mEssFile, file1);
File file2;
read(mOutFile, file2); // todo rename variable
// FIXME: use max(size1, size2)
for (unsigned int i=0; i<file1.mRecords.size(); ++i)
{
File::Record rec = file1.mRecords[i];
if (i >= file2.mRecords.size())
{
std::ios::fmtflags f(std::cout.flags());
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl;
std::cout.flags(f);
return;
}
File::Record rec2 = file2.mRecords[i];
if (rec.mName != rec2.mName)
{
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
std::cout.flags(f);
return; // TODO: try to recover
}
// FIXME: use max(size1, size2)
for (unsigned int j=0; j<rec.mSubrecords.size(); ++j)
{
File::Subrecord sub = rec.mSubrecords[j];
if (j >= rec2.mSubrecords.size())
{
std::ios::fmtflags f(std::cout.flags());
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
std::cout.flags(f);
return;
}
File::Subrecord sub2 = rec2.mSubrecords[j];
if (sub.mName != sub2.mName)
{
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout.flags(f);
break; // TODO: try to recover
}
if (sub.mData != sub2.mData)
{
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
continue;
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout << "Data 1:" << std::endl;
for (unsigned int k=0; k<sub.mData.size(); ++k)
{
bool different = false;
if (k >= sub2.mData.size() || sub2.mData[k] != sub.mData[k])
different = true;
if (different)
std::cout << "\033[033m";
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub.mData[k] << " ";
if (different)
std::cout << "\033[0m";
}
std::cout << std::endl;
std::cout << "Data 2:" << std::endl;
for (unsigned int k=0; k<sub2.mData.size(); ++k)
{
bool different = false;
if (k >= sub.mData.size() || sub.mData[k] != sub2.mData[k])
different = true;
if (different)
std::cout << "\033[033m";
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub2.mData[k] << " ";
if (different)
std::cout << "\033[0m";
}
std::cout << std::endl;
std::cout.flags(f);
}
}
}
}
void Importer::run()
{
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding));
ESM::ESMReader esm;
esm.open(mEssFile);
esm.setEncoder(&encoder);
Context context;
const ESM::Header& header = esm.getHeader();
context.mPlayerCellName = header.mGameData.mCurrentCell.toString();
const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value;
const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value;
const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value;
const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value;
const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value;
const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value;
const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value;
std::map<unsigned int, boost::shared_ptr<Converter> > converters;
converters[ESM::REC_GLOB] = boost::shared_ptr<Converter>(new ConvertGlobal());
converters[ESM::REC_BOOK] = boost::shared_ptr<Converter>(new ConvertBook());
converters[ESM::REC_NPC_] = boost::shared_ptr<Converter>(new ConvertNPC());
converters[ESM::REC_CREA] = boost::shared_ptr<Converter>(new ConvertCREA());
converters[ESM::REC_NPCC] = boost::shared_ptr<Converter>(new ConvertNPCC());
converters[ESM::REC_CREC] = boost::shared_ptr<Converter>(new ConvertCREC());
converters[recREFR ] = boost::shared_ptr<Converter>(new ConvertREFR());
converters[recPCDT ] = boost::shared_ptr<Converter>(new ConvertPCDT());
converters[recFMAP ] = boost::shared_ptr<Converter>(new ConvertFMAP());
converters[recKLST ] = boost::shared_ptr<Converter>(new ConvertKLST());
converters[recSTLN ] = boost::shared_ptr<Converter>(new ConvertSTLN());
converters[recGAME ] = boost::shared_ptr<Converter>(new ConvertGAME());
converters[ESM::REC_CELL] = boost::shared_ptr<Converter>(new ConvertCell());
converters[ESM::REC_ALCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Potion>());
converters[ESM::REC_CLAS] = boost::shared_ptr<Converter>(new ConvertClass());
converters[ESM::REC_SPEL] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Spell>());
converters[ESM::REC_ARMO] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Armor>());
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_CLOT] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Clothing>());
converters[ESM::REC_ENCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Enchantment>());
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_LEVC] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::CreatureLevList>());
converters[ESM::REC_LEVI] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::ItemLevList>());
converters[ESM::REC_CNTC] = boost::shared_ptr<Converter>(new ConvertCNTC());
converters[ESM::REC_FACT] = boost::shared_ptr<Converter>(new ConvertFACT());
converters[ESM::REC_INFO] = boost::shared_ptr<Converter>(new ConvertINFO());
converters[ESM::REC_DIAL] = boost::shared_ptr<Converter>(new ConvertDIAL());
converters[ESM::REC_QUES] = boost::shared_ptr<Converter>(new ConvertQUES());
converters[recJOUR ] = boost::shared_ptr<Converter>(new ConvertJOUR());
converters[ESM::REC_SCPT] = boost::shared_ptr<Converter>(new ConvertSCPT());
// TODO:
// - REGN (weather in certain regions?)
// - VFXM
// - SPLM (active spell effects)
// - PROJ (magic projectiles in air)
std::set<unsigned int> unknownRecords;
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
it->second->setContext(context);
}
while (esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
std::map<unsigned int, boost::shared_ptr<Converter> >::iterator it = converters.find(n.intval);
if (it != converters.end())
{
it->second->read(esm);
}
else
{
if (unknownRecords.insert(n.intval).second)
{
std::ios::fmtflags f(std::cerr.flags());
std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
std::cerr.flags(f);
}
esm.skipRecord();
}
}
ESM::ESMWriter writer;
writer.setFormat (ESM::SavedGame::sCurrentFormat);
std::ofstream stream(mOutFile.c_str(), std::ios::binary);
// all unused
writer.setVersion(0);
writer.setType(0);
writer.setAuthor("");
writer.setDescription("");
writer.setRecordCount (0);
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
writer.addMaster (it->name, 0); // not using the size information anyway -> use value of 0
writer.save (stream);
ESM::SavedGame profile;
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
{
profile.mContentFiles.push_back(it->name);
}
profile.mDescription = esm.getDesc();
profile.mInGameTime.mDay = context.mDay;
profile.mInGameTime.mGameHour = context.mHour;
profile.mInGameTime.mMonth = context.mMonth;
profile.mInGameTime.mYear = context.mYear;
profile.mPlayerCell = header.mGameData.mCurrentCell.toString();
if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN")
profile.mPlayerClassName = context.mCustomPlayerClassName;
else
profile.mPlayerClassId = context.mPlayerBase.mClass;
profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel;
profile.mPlayerName = header.mGameData.mPlayerName.toString();
writeScreenshot(header, profile);
writer.startRecord (ESM::REC_SAVE);
profile.save (writer);
writer.endRecord (ESM::REC_SAVE);
// Writing order should be Dynamic Store -> Cells -> Player,
// so that references to dynamic records can be recognized when loading
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 0)
continue;
it->second->write(writer);
}
writer.startRecord(ESM::REC_NPC_);
context.mPlayerBase.mId = "player";
context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_);
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 1)
continue;
it->second->write(writer);
}
writer.startRecord(ESM::REC_PLAY);
if (context.mPlayer.mCellId.mPaged)
{
// exterior cell -> determine cell coordinates based on position
const int cellSize = 8192;
int cellX = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[0]/cellSize));
int cellY = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[1] / cellSize));
context.mPlayer.mCellId.mIndex.mX = cellX;
context.mPlayer.mCellId.mIndex.mY = cellY;
}
context.mPlayer.save(writer);
writer.endRecord(ESM::REC_PLAY);
writer.startRecord (ESM::REC_DIAS);
context.mDialogueState.save(writer);
writer.endRecord(ESM::REC_DIAS);
}
}

@ -0,0 +1,26 @@
#ifndef OPENMW_ESSIMPORTER_IMPORTER_H
#define OPENMW_ESSIMPORTER_IMPORTER_H
#include <string>
namespace ESSImport
{
class Importer
{
public:
Importer(const std::string& essfile, const std::string& outfile, const std::string& encoding);
void run();
void compare();
private:
std::string mEssFile;
std::string mOutFile;
std::string mEncoding;
};
}
#endif

@ -0,0 +1,77 @@
#ifndef OPENMW_ESSIMPORT_CONTEXT_H
#define OPENMW_ESSIMPORT_CONTEXT_H
#include <map>
#include <components/esm/loadnpc.hpp>
#include <components/esm/player.hpp>
#include <components/esm/dialoguestate.hpp>
#include <components/esm/globalmap.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/loadnpc.hpp>
#include "importnpcc.hpp"
#include "importcrec.hpp"
#include "importcntc.hpp"
#include "importplayer.hpp"
namespace ESSImport
{
struct Context
{
// set from the TES3 header
std::string mPlayerCellName;
ESM::Player mPlayer;
ESM::NPC mPlayerBase;
std::string mCustomPlayerClassName;
ESM::DialogueState mDialogueState;
// cells which should show an explored overlay on the global map
std::set<std::pair<int, int> > mExploredCells;
ESM::GlobalMap mGlobalMapState;
int mDay, mMonth, mYear;
float mHour;
// key <refIndex, refId>
std::map<std::pair<int, std::string>, CREC> mCreatureChanges;
std::map<std::pair<int, std::string>, NPCC> mNpcChanges;
std::map<std::pair<int, std::string>, CNTC> mContainerChanges;
std::map<std::string, ESM::Creature> mCreatures;
std::map<std::string, ESM::NPC> mNpcs;
Context()
: mDay(0)
, mMonth(0)
, mYear(0)
, mHour(0.f)
{
mPlayer.mAutoMove = 0;
ESM::CellId playerCellId;
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId;
//mPlayer.mLastKnownExteriorPosition
mPlayer.mHasMark = 0; // TODO
mPlayer.mCurrentCrimeId = 0; // TODO
mPlayer.mObject.blank();
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
mGlobalMapState.mBounds.mMinX = 0;
mGlobalMapState.mBounds.mMaxX = 0;
mGlobalMapState.mBounds.mMinY = 0;
mGlobalMapState.mBounds.mMaxY = 0;
}
};
}
#endif

@ -0,0 +1,29 @@
#include "importgame.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void GAME::load(ESM::ESMReader &esm)
{
esm.getSubNameIs("GMDT");
esm.getSubHeader();
if (esm.getSubSize() == 92)
{
esm.getExact(&mGMDT, 92);
mGMDT.mSecundaPhase = 0;
}
else if (esm.getSubSize() == 96)
{
esm.getT(mGMDT);
}
else
esm.fail("unexpected subrecord size for GAME.GMDT");
mGMDT.mWeatherTransition &= (0x000000ff);
mGMDT.mSecundaPhase &= (0x000000ff);
mGMDT.mMasserPhase &= (0x000000ff);
}
}

@ -0,0 +1,33 @@
#ifndef OPENMW_ESSIMPORT_GAME_H
#define OPENMW_ESSIMPORT_GAME_H
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Weather data
struct GAME
{
struct GMDT
{
char mCellName[64];
int mFogColour;
float mFogDensity;
int mCurrentWeather, mNextWeather;
int mWeatherTransition; // 0-100 transition between weathers, top 3 bytes may be garbage
float mTimeOfNextTransition; // weather changes when gamehour == timeOfNextTransition
int mMasserPhase, mSecundaPhase; // top 3 bytes may be garbage
};
GMDT mGMDT;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,14 @@
#include "importinfo.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void INFO::load(ESM::ESMReader &esm)
{
mInfo = esm.getHNString("INAM");
mActorRefId = esm.getHNString("ACDT");
}
}

@ -0,0 +1,24 @@
#ifndef OPENMW_ESSIMPORT_IMPORTINFO_H
#define OPENMW_ESSIMPORT_IMPORTINFO_H
#include <string>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct INFO
{
std::string mInfo;
std::string mActorRefId;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,78 @@
#include "importinventory.hpp"
#include <stdexcept>
#include <components/esm/esmreader.hpp>
#include <components/esm/loadcont.hpp>
namespace ESSImport
{
void Inventory::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("NPCO"))
{
ESM::ContItem contItem;
esm.getHT(contItem);
InventoryItem item;
item.mId = contItem.mItem.toString();
item.mCount = contItem.mCount;
item.mRelativeEquipmentSlot = -1;
unsigned int itemCount = std::abs(item.mCount);
bool separateStacks = false;
for (unsigned int i=0;i<itemCount;++i)
{
bool newStack = esm.isNextSub("XIDX");
if (newStack)
{
unsigned int idx;
esm.getHT(idx);
separateStacks = true;
item.mCount = 1;
}
item.mSCRI.load(esm);
// for XSOL and XCHG seen so far, but probably others too
bool isDeleted = false;
item.ESM::CellRef::loadData(esm, isDeleted);
int charge=-1;
esm.getHNOT(charge, "XHLT");
item.mChargeInt = charge;
if (newStack)
mItems.push_back(item);
}
if (!separateStacks)
mItems.push_back(item);
}
// equipped items
while (esm.isNextSub("WIDX"))
{
// note: same item can be equipped 2 items (e.g. 2 rings)
// and will be *stacked* in the NPCO list, unlike openmw!
// this is currently not handled properly.
esm.getSubHeader();
int itemIndex; // index of the item in the NPCO list
esm.getT(itemIndex);
if (itemIndex < 0 || itemIndex >= int(mItems.size()))
esm.fail("equipment item index out of range");
// appears to be a relative index for only the *possible* slots this item can be equipped in,
// i.e. 0 most of the time
int slotIndex;
esm.getT(slotIndex);
mItems[itemIndex].mRelativeEquipmentSlot = slotIndex;
}
}
}

@ -0,0 +1,34 @@
#ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#define OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#include <vector>
#include <string>
#include <components/esm/cellref.hpp>
#include "importscri.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct Inventory
{
struct InventoryItem : public ESM::CellRef
{
std::string mId;
int mCount;
int mRelativeEquipmentSlot;
SCRI mSCRI;
};
std::vector<InventoryItem> mItems;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,13 @@
#include "importjour.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void JOUR::load(ESM::ESMReader &esm)
{
mText = esm.getHNString("NAME");
}
}

@ -0,0 +1,25 @@
#ifndef OPENMW_ESSIMPORT_IMPORTJOUR_H
#define OPENMW_ESSIMPORT_IMPORTJOUR_H
#include <string>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Journal
struct JOUR
{
// The entire journal, in HTML
std::string mText;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,22 @@
#include "importklst.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void KLST::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("KNAM"))
{
std::string refId = esm.getHString();
int count;
esm.getHNT(count, "CNAM");
mKillCounter[refId] = count;
}
mWerewolfKills = 0;
esm.getHNOT(mWerewolfKills, "INTV");
}
}

@ -0,0 +1,28 @@
#ifndef OPENMW_ESSIMPORT_KLST_H
#define OPENMW_ESSIMPORT_KLST_H
#include <string>
#include <map>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Kill Stats
struct KLST
{
void load(ESM::ESMReader& esm);
/// RefId, kill count
std::map<std::string, int> mKillCounter;
int mWerewolfKills;
};
}
#endif

@ -0,0 +1,19 @@
#include "importnpcc.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void NPCC::load(ESM::ESMReader &esm)
{
esm.getHNT(mNPDT, "NPDT");
while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F")
|| esm.isNextSub("AI_A"))
mAiPackages.add(esm);
mInventory.load(esm);
}
}

@ -0,0 +1,37 @@
#ifndef OPENMW_ESSIMPORT_NPCC_H
#define OPENMW_ESSIMPORT_NPCC_H
#include <components/esm/loadcont.hpp>
#include <components/esm/aipackage.hpp>
#include "importinventory.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct NPCC
{
struct NPDT
{
unsigned char mDisposition;
unsigned char unknown;
unsigned char mReputation;
unsigned char unknown2;
int mIndex;
} mNPDT;
Inventory mInventory;
ESM::AIPackageList mAiPackages;
void load(ESM::ESMReader &esm);
};
}
#endif

@ -0,0 +1,85 @@
#include "importplayer.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void REFR::load(ESM::ESMReader &esm)
{
esm.getHNT(mRefNum.mIndex, "FRMR");
mRefID = esm.getHNString("NAME");
mActorData.load(esm);
esm.getHNOT(mPos, "DATA", 24);
}
void PCDT::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("DNAM"))
{
mKnownDialogueTopics.push_back(esm.getHString());
}
if (esm.isNextSub("MNAM"))
esm.skipHSub(); // If this field is here it seems to specify the interior cell the player is in,
// but it's not always here, so it's kinda useless
esm.getHNT(mPNAM, "PNAM");
if (esm.isNextSub("SNAM"))
esm.skipHSub();
if (esm.isNextSub("NAM9"))
esm.skipHSub();
mBounty = 0;
esm.getHNOT(mBounty, "CNAM");
mBirthsign = esm.getHNOString("BNAM");
// Holds the names of the last used Alchemy apparatus. Don't need to import this ATM,
// because our GUI auto-selects the best apparatus.
if (esm.isNextSub("NAM0"))
esm.skipHSub();
if (esm.isNextSub("NAM1"))
esm.skipHSub();
if (esm.isNextSub("NAM2"))
esm.skipHSub();
if (esm.isNextSub("NAM3"))
esm.skipHSub();
if (esm.isNextSub("ENAM"))
esm.skipHSub();
if (esm.isNextSub("LNAM"))
esm.skipHSub();
while (esm.isNextSub("FNAM"))
{
FNAM fnam;
esm.getHT(fnam);
mFactions.push_back(fnam);
}
if (esm.isNextSub("AADT"))
esm.skipHSub(); // 44 bytes, no clue
if (esm.isNextSub("KNAM"))
esm.skipHSub(); // assigned Quick Keys, I think
if (esm.isNextSub("WERE"))
{
// some werewolf data, 152 bytes
// maybe current skills and attributes for werewolf form
esm.getSubHeader();
esm.skip(152);
}
// unsure if before or after WERE
if (esm.isNextSub("ANIS"))
esm.skipHSub();
}
}

@ -0,0 +1,84 @@
#ifndef OPENMW_ESSIMPORT_PLAYER_H
#define OPENMW_ESSIMPORT_PLAYER_H
#include <vector>
#include <string>
#include <components/esm/defs.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Player-agnostic player data
struct REFR
{
ActorData mActorData;
std::string mRefID;
ESM::Position mPos;
ESM::RefNum mRefNum;
void load(ESM::ESMReader& esm);
};
/// Other player data
struct PCDT
{
int mBounty;
std::string mBirthsign;
std::vector<std::string> mKnownDialogueTopics;
enum DrawState_
{
DrawState_Weapon = 0x80,
DrawState_Spell = 0x100
};
enum CameraFlags
{
CameraFlag_ThirdPerson = 0x2
};
#pragma pack(push)
#pragma pack(1)
struct FNAM
{
unsigned char mRank;
unsigned char mUnknown1[3];
int mReputation;
unsigned char mFlags; // 0x1: unknown, 0x2: expelled
unsigned char mUnknown2[3];
ESM::NAME32 mFactionName;
};
struct PNAM
{
short mDrawState; // DrawState
short mCameraFlags; // CameraFlags
unsigned int mLevelProgress;
float mSkillProgress[27]; // skill progress, non-uniform scaled
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
unsigned char mUnknown3[84];
unsigned char mSpecIncreases[3]; // number of skill increases for each specialization
unsigned char mUnknown4;
};
#pragma pack(pop)
std::vector<FNAM> mFactions;
PNAM mPNAM;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,14 @@
#include "importques.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void QUES::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("DATA"))
mInfo.push_back(esm.getHString());
}
}

@ -0,0 +1,28 @@
#ifndef OPENMW_ESSIMPORT_IMPORTQUES_H
#define OPENMW_ESSIMPORT_IMPORTQUES_H
#include <string>
#include <vector>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// State for a quest
/// Presumably this record only exists when Tribunal is installed,
/// since pre-Tribunal there weren't any quest names in the data files.
struct QUES
{
std::string mName; // NAME, should be assigned from outside as usual
std::vector<std::string> mInfo; // list of journal entries for the quest
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,26 @@
#include "importscpt.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void SCPT::load(ESM::ESMReader &esm)
{
esm.getHNT(mSCHD, "SCHD");
mSCRI.load(esm);
mRefNum = -1;
if (esm.isNextSub("RNAM"))
{
mRunning = true;
esm.getHT(mRefNum);
}
else
mRunning = false;
}
}

@ -0,0 +1,32 @@
#ifndef OPENMW_ESSIMPORT_IMPORTSCPT_H
#define OPENMW_ESSIMPORT_IMPORTSCPT_H
#include "importscri.hpp"
#include <components/esm/loadscpt.hpp>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
// A running global script
struct SCPT
{
ESM::Script::SCHD mSCHD;
// values of local variables
SCRI mSCRI;
bool mRunning;
int mRefNum; // Targeted reference, -1: no reference
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,55 @@
#include "importscri.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void SCRI::load(ESM::ESMReader &esm)
{
mScript = esm.getHNOString("SCRI");
int numShorts = 0, numLongs = 0, numFloats = 0;
if (esm.isNextSub("SLCS"))
{
esm.getSubHeader();
esm.getT(numShorts);
esm.getT(numLongs);
esm.getT(numFloats);
}
if (esm.isNextSub("SLSD"))
{
esm.getSubHeader();
for (int i=0; i<numShorts; ++i)
{
short val;
esm.getT(val);
mShorts.push_back(val);
}
}
// I haven't seen Longs in a save file yet, but SLLD would make sense for the name
// TODO: test this
if (esm.isNextSub("SLLD"))
{
esm.getSubHeader();
for (int i=0; i<numLongs; ++i)
{
int val;
esm.getT(val);
mLongs.push_back(val);
}
}
if (esm.isNextSub("SLFD"))
{
esm.getSubHeader();
for (int i=0; i<numFloats; ++i)
{
float val;
esm.getT(val);
mFloats.push_back(val);
}
}
}
}

@ -0,0 +1,30 @@
#ifndef OPENMW_ESSIMPORT_IMPORTSCRI_H
#define OPENMW_ESSIMPORT_IMPORTSCRI_H
#include <components/esm/variant.hpp>
#include <vector>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Local variable assigments for a running script
struct SCRI
{
std::string mScript;
std::vector<short> mShorts;
std::vector<int> mLongs;
std::vector<float> mFloats;
void load(ESM::ESMReader& esm);
};
}
#endif

@ -0,0 +1,77 @@
#include <iostream>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/files/configurationmanager.hpp>
#include "importer.hpp"
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
int main(int argc, char** argv)
{
try
{
bpo::options_description desc("Syntax: openmw-essimporter <options> infile.ess outfile.omwsave\nAllowed options");
bpo::positional_options_description p_desc;
desc.add_options()
("help,h", "produce help message")
("mwsave,m", bpo::value<std::string>(), "morrowind .ess save file")
("output,o", bpo::value<std::string>(), "output file (.omwsave)")
("compare,c", "compare two .ess files")
("encoding", boost::program_options::value<std::string>()->default_value("win1252"), "encoding of the save file")
;
p_desc.add("mwsave", 1).add("output", 1);
bpo::variables_map variables;
bpo::parsed_options parsed = bpo::command_line_parser(argc, argv)
.options(desc)
.positional(p_desc)
.run();
bpo::store(parsed, variables);
if(variables.count("help") || !variables.count("mwsave") || !variables.count("output")) {
std::cout << desc;
return 0;
}
bpo::notify(variables);
Files::ConfigurationManager cfgManager(true);
cfgManager.readConfiguration(variables, desc);
std::string essFile = variables["mwsave"].as<std::string>();
std::string outputFile = variables["output"].as<std::string>();
std::string encoding = variables["encoding"].as<std::string>();
ESSImport::Importer importer(essFile, outputFile, encoding);
if (variables.count("compare"))
importer.compare();
else
{
const std::string& ext = ".omwsave";
if (boost::filesystem::exists(boost::filesystem::path(outputFile))
&& (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext))
{
throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?");
}
importer.run();
}
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

@ -7,8 +7,6 @@ set(LAUNCHER
textslotmsgbox.cpp
settingspage.cpp
settings/graphicssettings.cpp
utils/profilescombobox.cpp
utils/textinputdialog.cpp
utils/lineedit.cpp
@ -24,8 +22,6 @@ set(LAUNCHER_HEADER
textslotmsgbox.hpp
settingspage.hpp
settings/graphicssettings.hpp
utils/profilescombobox.hpp
utils/textinputdialog.hpp
utils/lineedit.hpp
@ -57,7 +53,6 @@ set(LAUNCHER_UI
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
find_package(Qt4 REQUIRED)
set(QT_USE_QTGUI 1)
# Set some platform specific settings
@ -66,19 +61,24 @@ if(WIN32)
set(QT_USE_QTMAIN TRUE)
endif(WIN32)
QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
if (DESIRED_QT_VERSION MATCHES 4)
include(${QT_USE_FILE})
QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
else()
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
endif()
include(${QT_USE_FILE})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(NOT WIN32)
include_directories(${LIBUNSHIELD_INCLUDE_DIR})
endif(NOT WIN32)
# Main executable
add_executable(omwlauncher
add_executable(openmw-launcher
${GUI_TYPE}
${LAUNCHER}
${LAUNCHER_HEADER}
@ -87,18 +87,23 @@ add_executable(omwlauncher
${UI_HDRS}
)
target_link_libraries(omwlauncher
${Boost_LIBRARIES}
${OGRE_LIBRARIES}
${OGRE_STATIC_PLUGINS}
target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY}
${QT_LIBRARIES}
components
)
if (DESIRED_QT_VERSION MATCHES 4)
target_link_libraries(openmw-launcher ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY})
if(WIN32)
target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY})
endif(WIN32)
else()
qt5_use_modules(openmw-launcher Widgets Core)
endif()
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(omwlauncher gcov)
target_link_libraries(openmw-launcher gcov)
endif()

@ -20,17 +20,20 @@
#include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp"
const char *Launcher::DataFilesPage::mDefaultContentListName = "Default";
Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent)
: mCfgMgr(cfg)
: QWidget(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, QWidget(parent)
{
ui.setupUi (this);
setObjectName ("DataFilesPage");
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this);
mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this);
connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)),
this, SLOT(updateOkButton(QString)));
@ -44,13 +47,13 @@ void Launcher::DataFilesPage::buildView()
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
//tool buttons
ui.newProfileButton->setToolTip ("Create a new profile");
ui.deleteProfileButton->setToolTip ("Delete an existing profile");
ui.newProfileButton->setToolTip ("Create a new Content List");
ui.deleteProfileButton->setToolTip ("Delete an existing Content List");
//combo box
ui.profilesComboBox->addItem ("Default");
ui.profilesComboBox->setPlaceholderText (QString("Select a profile..."));
ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String("Default")));
ui.profilesComboBox->addItem(mDefaultContentListName);
ui.profilesComboBox->setPlaceholderText (QString("Select a Content List..."));
ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String(mDefaultContentListName)));
// Add the actions to the toolbuttons
ui.newProfileButton->setDefaultAction (ui.newProfileAction);
@ -68,10 +71,27 @@ void Launcher::DataFilesPage::buildView()
}
bool Launcher::DataFilesPage::loadSettings()
{
QStringList profiles = mLauncherSettings.getContentLists();
QString currentProfile = mLauncherSettings.getCurrentContentListName();
qDebug() << "The current profile is: " << currentProfile;
foreach (const QString &item, profiles)
addProfile (item, false);
// Hack: also add the current profile
if (!currentProfile.isEmpty())
addProfile(currentProfile, true);
return true;
}
void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
{
QStringList paths = mGameSettings.getDataDirs();
foreach (const QString &path, paths)
foreach(const QString &path, paths)
mSelector->addFiles(path);
mDataLocal = mGameSettings.getDataLocal();
@ -79,35 +99,26 @@ bool Launcher::DataFilesPage::loadSettings()
if (!mDataLocal.isEmpty())
mSelector->addFiles(mDataLocal);
paths.insert (0, mDataLocal);
PathIterator pathIterator (paths);
QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/"));
QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile");
qDebug() << "current profile is: " << currentProfile;
foreach (const QString &item, profiles)
addProfile (item, false);
paths.insert(0, mDataLocal);
PathIterator pathIterator(paths);
// Hack: also add the current profile
if (!currentProfile.isEmpty())
addProfile(currentProfile, true);
mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator));
}
QStringList files = mLauncherSettings.values(QString("Profiles/") + currentProfile + QString("/content"), Qt::MatchExactly);
QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, PathIterator& pathIterator)
{
QStringList files = mLauncherSettings.getContentListFiles(profileName);
QStringList filepaths;
foreach (const QString &file, files)
foreach(const QString& file, files)
{
QString filepath = pathIterator.findFirstPath (file);
QString filepath = pathIterator.findFirstPath(file);
if (!filepath.isEmpty())
filepaths << filepath;
}
mSelector->setProfileContent (filepaths);
return true;
return filepaths;
}
void Launcher::DataFilesPage::saveSettings(const QString &profile)
@ -120,24 +131,20 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
//retrieve the files selected for the profile
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
removeProfile (profileName);
mGameSettings.remove(QString("content"));
//set the value of the current profile (not necessarily the profile being saved!)
mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText());
mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText());
QStringList fileNames;
foreach(const ContentSelectorModel::EsmFile *item, items) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/content"), item->fileName());
mGameSettings.setMultiValue(QString("content"), item->fileName());
fileNames.append(item->fileName());
}
mLauncherSettings.setContentList(profileName, fileNames);
mGameSettings.setContentList(fileNames);
}
void Launcher::DataFilesPage::removeProfile(const QString &profile)
{
mLauncherSettings.remove(QString("Profiles/") + profile);
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/content"));
mLauncherSettings.removeContentList(profile);
}
QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const
@ -174,7 +181,7 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString
ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current));
loadSettings();
populateFileViews(current);
checkForDefaultProfile();
}
@ -225,16 +232,9 @@ void Launcher::DataFilesPage::on_newProfileAction_triggered()
saveSettings();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile);
mLauncherSettings.setCurrentContentListName(profile);
addProfile(profile, true);
mSelector->clearCheckStates();
mSelector->setGameFile();
saveSettings();
emit signalProfileChanged (ui.profilesComboBox->findText(profile));
}
void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent)
@ -261,15 +261,13 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered()
// this should work since the Default profile can't be deleted and is always index 0
int next = ui.profilesComboBox->currentIndex()-1;
// changing the profile forces a reload of plugin file views.
ui.profilesComboBox->setCurrentIndex(next);
removeProfile(profile);
ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile));
saveSettings();
loadSettings();
checkForDefaultProfile();
}
@ -289,7 +287,7 @@ void Launcher::DataFilesPage::updateOkButton(const QString &text)
void Launcher::DataFilesPage::checkForDefaultProfile()
{
//don't allow deleting "Default" profile
bool success = (ui.profilesComboBox->currentText() != "Default");
bool success = (ui.profilesComboBox->currentText() != mDefaultContentListName);
ui.deleteProfileAction->setEnabled (success);
ui.profilesComboBox->setEditEnabled (success);
@ -298,10 +296,10 @@ void Launcher::DataFilesPage::checkForDefaultProfile()
bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text)
{
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Delete Profile"));
msgBox.setWindowTitle(tr("Delete Content List"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(text));
msgBox.setText(tr("Are you sure you want to delete <b>%1</b>?").arg(text));
QAbstractButton *deleteButton =
msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);

@ -58,6 +58,10 @@ namespace Launcher
void on_newProfileAction_triggered();
void on_deleteProfileAction_triggered();
public:
/// Content List that is always present
const static char *mDefaultContentListName;
private:
TextInputDialog *mProfileDialog;
@ -82,6 +86,7 @@ namespace Launcher
bool showDeleteMessageBox (const QString &text);
void addProfile (const QString &profile, bool setAsCurrent);
void checkForDefaultProfile();
void populateFileViews(const QString& contentModelName);
class PathIterator
{
@ -134,6 +139,8 @@ namespace Launcher
}
};
QStringList filesInProfile(const QString& profileName, PathIterator& pathIterator);
};
}
#endif

@ -10,7 +10,7 @@
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
#include <SDL.h>
#include <SDL_video.h>
#include <boost/math/common_factor.hpp>
@ -18,7 +18,7 @@
#include <components/contentselector/model/naturalsort.hpp>
#include "settings/graphicssettings.hpp"
#include <components/settings/settings.hpp>
QString getAspect(int x, int y)
{
@ -32,14 +32,10 @@ QString getAspect(int x, int y)
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
}
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent)
: mOgre(NULL)
, mSelectedRenderSystem(NULL)
, mOpenGLRenderSystem(NULL)
, mDirect3DRenderSystem(NULL)
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent)
, mCfgMgr(cfg)
, mGraphicsSettings(graphicsSetting)
, QWidget(parent)
, mEngineSettings(engineSettings)
{
setObjectName ("GraphicsPage");
setupUi(this);
@ -49,79 +45,12 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsS
customWidthSpinBox->setMaximum(res.width());
customHeightSpinBox->setMaximum(res.height());
connect(rendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&)));
connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int)));
connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool)));
connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
}
bool Launcher::GraphicsPage::setupOgre()
{
try
{
mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log");
}
catch(Ogre::Exception &ex)
{
QString ogreError = QString::fromStdString(ex.getFullDescription().c_str());
QMessageBox msgBox;
msgBox.setWindowTitle("Error creating Ogre::Root");
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Failed to create the Ogre::Root object</b><br><br> \
Press \"Show Details...\" for more information.<br>"));
msgBox.setDetailedText(ogreError);
msgBox.exec();
qCritical("Error creating Ogre::Root, the error reported was:\n %s", qPrintable(ogreError));
return false;
}
// Get the available renderers and put them in the combobox
const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers();
for (Ogre::RenderSystemList::const_iterator r = renderers.begin(); r != renderers.end(); ++r) {
mSelectedRenderSystem = *r;
rendererComboBox->addItem((*r)->getName().c_str());
}
QString openGLName = QString("OpenGL Rendering Subsystem");
QString direct3DName = QString("Direct3D9 Rendering Subsystem");
// Create separate rendersystems
mOpenGLRenderSystem = mOgre->getRenderSystemByName(openGLName.toStdString());
mDirect3DRenderSystem = mOgre->getRenderSystemByName(direct3DName.toStdString());
if (!mOpenGLRenderSystem && !mDirect3DRenderSystem) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error creating renderer"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not select a valid render system</b><br><br> \
Please make sure Ogre plugins were installed correctly.<br>"));
msgBox.exec();
return false;
}
// Now fill the GUI elements
int index = rendererComboBox->findText(mGraphicsSettings.value(QString("Video/render system")));
if ( index != -1) {
rendererComboBox->setCurrentIndex(index);
} else {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
rendererComboBox->setCurrentIndex(rendererComboBox->findText(direct3DName));
#else
rendererComboBox->setCurrentIndex(rendererComboBox->findText(openGLName));
#endif
}
antiAliasingComboBox->clear();
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
return true;
}
bool Launcher::GraphicsPage::setupSDL()
{
int displays = SDL_GetNumVideoDisplays();
@ -132,7 +61,7 @@ bool Launcher::GraphicsPage::setupSDL()
msgBox.setWindowTitle(tr("Error receiving number of screens"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromUtf8(SDL_GetError()) + "<br>");
msgBox.exec();
return false;
}
@ -150,28 +79,27 @@ bool Launcher::GraphicsPage::loadSettings()
{
if (!setupSDL())
return false;
if (!mOgre && !setupOgre())
return false;
if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true"))
if (mEngineSettings.getBool("vsync", "Video"))
vSyncCheckBox->setCheckState(Qt::Checked);
if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true"))
if (mEngineSettings.getBool("fullscreen", "Video"))
fullScreenCheckBox->setCheckState(Qt::Checked);
if (mGraphicsSettings.value(QString("Video/window border")) == QLatin1String("true"))
if (mEngineSettings.getBool("window border", "Video"))
windowBorderCheckBox->setCheckState(Qt::Checked);
int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing")));
// aaValue is the actual value (0, 1, 2, 4, 8, 16)
int aaValue = mEngineSettings.getInt("antialiasing", "Video");
// aaIndex is the index into the allowed values in the pull down.
int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue));
if (aaIndex != -1)
antiAliasingComboBox->setCurrentIndex(aaIndex);
QString width = mGraphicsSettings.value(QString("Video/resolution x"));
QString height = mGraphicsSettings.value(QString("Video/resolution y"));
QString resolution = width + QString(" x ") + height;
QString screen = mGraphicsSettings.value(QString("Video/screen"));
screenComboBox->setCurrentIndex(screen.toInt());
int width = mEngineSettings.getInt("resolution x", "Video");
int height = mEngineSettings.getInt("resolution y", "Video");
QString resolution = QString::number(width) + QString(" x ") + QString::number(height);
screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video"));
int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
@ -180,9 +108,8 @@ bool Launcher::GraphicsPage::loadSettings()
resolutionComboBox->setCurrentIndex(resIndex);
} else {
customRadioButton->toggle();
customWidthSpinBox->setValue(width.toInt());
customHeightSpinBox->setValue(height.toInt());
customWidthSpinBox->setValue(width);
customHeightSpinBox->setValue(height);
}
return true;
@ -190,65 +117,46 @@ bool Launcher::GraphicsPage::loadSettings()
void Launcher::GraphicsPage::saveSettings()
{
vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/vsync"), QString("false"));
fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false"));
windowBorderCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/window border"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/window border"), QString("false"));
mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText());
mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText());
// 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)
bool cVSync = vSyncCheckBox->checkState();
if (cVSync != mEngineSettings.getBool("vsync", "Video"))
mEngineSettings.setBool("vsync", "Video", cVSync);
bool cFullScreen = fullScreenCheckBox->checkState();
if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video"))
mEngineSettings.setBool("fullscreen", "Video", cFullScreen);
bool cWindowBorder = windowBorderCheckBox->checkState();
if (cWindowBorder != mEngineSettings.getBool("window border", "Video"))
mEngineSettings.setBool("window border", "Video", cWindowBorder);
int cAAValue = antiAliasingComboBox->currentText().toInt();
if (cAAValue != mEngineSettings.getInt("antialiasing", "Video"))
mEngineSettings.setInt("antialiasing", "Video", cAAValue);
int cWidth = 0;
int cHeight = 0;
if (standardRadioButton->isChecked()) {
QRegExp resolutionRe(QString("(\\d+) x (\\d+).*"));
if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) {
mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1));
mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2));
cWidth = resolutionRe.cap(1).toInt();
cHeight = resolutionRe.cap(2).toInt();
}
} else {
mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value()));
mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value()));
cWidth = customWidthSpinBox->value();
cHeight = customHeightSpinBox->value();
}
mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex()));
}
if (cWidth != mEngineSettings.getInt("resolution x", "Video"))
mEngineSettings.setInt("resolution x", "Video", cWidth);
QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer)
{
QStringList result;
uint row = 0;
Ogre::ConfigOptionMap options = renderer->getConfigOptions();
if (cHeight != mEngineSettings.getInt("resolution y", "Video"))
mEngineSettings.setInt("resolution y", "Video", cHeight);
for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); ++i, ++row)
{
Ogre::StringVector::iterator opt_it;
uint idx = 0;
for (opt_it = i->second.possibleValues.begin();
opt_it != i->second.possibleValues.end(); ++opt_it, ++idx)
{
if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) {
result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified();
}
}
}
// Sort ascending
qSort(result.begin(), result.end(), naturalSortLessThanCI);
// Replace the zero option with Off
int index = result.indexOf("MSAA 0");
if (index != -1)
result.replace(index, tr("Off"));
return result;
int cScreen = screenComboBox->currentIndex();
if (cScreen != mEngineSettings.getInt("screen", "Video"))
mEngineSettings.setInt("screen", "Video", cScreen);
}
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
@ -263,7 +171,7 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
msgBox.setWindowTitle(tr("Error receiving resolutions"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromUtf8(SDL_GetError()) + "<br>");
msgBox.exec();
return result;
}
@ -276,7 +184,7 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
msgBox.setWindowTitle(tr("Error receiving resolutions"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>SDL_GetDisplayMode failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
msgBox.setText(tr("<br><b>SDL_GetDisplayMode failed:</b><br><br>") + QString::fromUtf8(SDL_GetError()) + "<br>");
msgBox.exec();
return result;
}
@ -313,15 +221,6 @@ QRect Launcher::GraphicsPage::getMaximumResolution()
return max;
}
void Launcher::GraphicsPage::rendererChanged(const QString &renderer)
{
mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString());
antiAliasingComboBox->clear();
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
}
void Launcher::GraphicsPage::screenChanged(int screen)
{
if (screen >= 0) {

@ -3,14 +3,9 @@
#include <QWidget>
#include <OgreRoot.h>
#include <OgreRenderSystem.h>
#include <components/ogreinit/ogreinit.hpp>
#include "ui_graphicspage.h"
#include <components/settings/settings.hpp>
namespace Files { struct ConfigurationManager; }
@ -23,13 +18,12 @@ namespace Launcher
Q_OBJECT
public:
GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0);
GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0);
void saveSettings();
bool loadSettings();
public slots:
void rendererChanged(const QString &renderer);
void screenChanged(int screen);
private slots:
@ -37,20 +31,12 @@ namespace Launcher
void slotStandardToggled(bool checked);
private:
OgreInit::OgreInit mOgreInit;
Ogre::Root *mOgre;
Ogre::RenderSystem *mSelectedRenderSystem;
Ogre::RenderSystem *mOpenGLRenderSystem;
Ogre::RenderSystem *mDirect3DRenderSystem;
Files::ConfigurationManager &mCfgMgr;
GraphicsSettings &mGraphicsSettings;
Settings::Manager &mEngineSettings;
QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer);
QStringList getAvailableResolutions(int screen);
QRect getMaximumResolution();
bool setupOgre();
bool setupSDL();
};
}

@ -1,3 +1,6 @@
#include <iostream>
#include <csignal>
#include <QApplication>
#include <QTextCodec>
#include <QDir>
@ -15,53 +18,49 @@
int main(int argc, char *argv[])
{
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
SDL_SetMainReady();
if (SDL_Init(SDL_INIT_VIDEO) != 0)
try
{
qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError());
return 0;
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
SDL_SetMainReady();
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
qDebug() << "SDL_Init failed: " << QString::fromUtf8(SDL_GetError());
return 0;
}
signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher,
// so reset SIGINT which SDL wants to redirect to an SDL_Quit event.
QApplication app(argc, argv);
// Now we make sure the current dir is set to application path
QDir dir(QCoreApplication::applicationDirPath());
#ifdef Q_OS_MAC
if (dir.dirName() == "MacOS") {
dir.cdUp();
dir.cdUp();
dir.cdUp();
}
#endif
QDir::setCurrent(dir.absolutePath());
Launcher::MainDialog mainWin;
Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog();
if (result == Launcher::FirstRunDialogResultFailure)
return 0;
if (result == Launcher::FirstRunDialogResultContinue)
mainWin.show();
int returnValue = app.exec();
SDL_Quit();
return returnValue;
}
QApplication app(argc, argv);
// Now we make sure the current dir is set to application path
QDir dir(QCoreApplication::applicationDirPath());
#ifdef Q_OS_MAC
if (dir.dirName() == "MacOS") {
dir.cdUp();
dir.cdUp();
dir.cdUp();
}
// force Qt to load only LOCAL plugins, don't touch system Qt installation
QDir pluginsPath(QCoreApplication::applicationDirPath());
pluginsPath.cdUp();
pluginsPath.cd("Plugins");
QStringList libraryPaths;
libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath();
app.setLibraryPaths(libraryPaths);
#endif
QDir::setCurrent(dir.absolutePath());
// Support non-latin characters
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
Launcher::MainDialog mainWin;
if (!mainWin.showFirstRunDialog())
catch (std::exception& e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
return 0;
// if (!mainWin.setup()) {
// return 0;
// }
mainWin.show();
int returnValue = app.exec();
SDL_Quit();
return returnValue;
}
}

@ -24,28 +24,18 @@
using namespace Process;
void cfgError(const QString& title, const QString& msg) {
QMessageBox msgBox;
msgBox.setWindowTitle(title);
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(msg);
msgBox.exec();
}
Launcher::MainDialog::MainDialog(QWidget *parent)
: mGameSettings(mCfgMgr), QMainWindow (parent)
: QMainWindow(parent), mGameSettings (mCfgMgr)
{
// Install the stylesheet font
QFile file;
QFontDatabase fontDatabase;
const QStringList fonts = fontDatabase.families();
// Check if the font is installed
if (!fonts.contains("EB Garamond")) {
QString font = QString::fromUtf8(mCfgMgr.getGlobalDataPath().string().c_str()) + QString("resources/mygui/EBGaramond-Regular.ttf");
file.setFileName(font);
if (!file.exists()) {
font = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str()) + QString("resources/mygui/EBGaramond-Regular.ttf");
}
fontDatabase.addApplicationFont(font);
}
setupUi(this);
mGameInvoker = new ProcessInvoker();
@ -76,25 +66,6 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
// Remove what's this? button
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Add version information to bottom of the window
QString revision(OPENMW_VERSION_COMMITHASH);
QString tag(OPENMW_VERSION_TAGHASH);
if (!revision.isEmpty() && !tag.isEmpty())
{
if (revision == tag) {
versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION));
} else {
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
}
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
}
createIcons();
}
@ -143,7 +114,7 @@ void Launcher::MainDialog::createPages()
{
mPlayPage = new PlayPage(this);
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage
@ -166,10 +137,10 @@ void Launcher::MainDialog::createPages()
}
bool Launcher::MainDialog::showFirstRunDialog()
Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
{
if (!setupLauncherSettings())
return false;
return FirstRunDialogResultFailure;
if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true"))
{
@ -194,14 +165,35 @@ bool Launcher::MainDialog::showFirstRunDialog()
if (msgBox.clickedButton() == wizardButton)
{
if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) {
return false;
return FirstRunDialogResultFailure;
} else {
return true;
return FirstRunDialogResultWizard;
}
}
}
return setup();
return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure;
}
void Launcher::MainDialog::setVersionLabel()
{
// Add version information to bottom of the window
Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData());
QString revision(QString::fromUtf8(v.mCommitHash.c_str()));
QString tag(QString::fromUtf8(v.mTagHash.c_str()));
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag))
versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str())));
else
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
}
bool Launcher::MainDialog::setup()
@ -209,13 +201,17 @@ bool Launcher::MainDialog::setup()
if (!setupGameSettings())
return false;
setVersionLabel();
mLauncherSettings.setContentList(mGameSettings);
if (!setupGraphicsSettings())
return false;
// Now create the pages as they need the settings
createPages();
// Call this so we can exit on Ogre/SDL errors before mainwindow is shown
// Call this so we can exit on SDL errors before mainwindow is shown
if (!mGraphicsPage->loadSettings())
return false;
@ -232,6 +228,8 @@ bool Launcher::MainDialog::reloadSettings()
if (!setupGameSettings())
return false;
mLauncherSettings.setContentList(mGameSettings);
if (!setupGraphicsSettings())
return false;
@ -253,49 +251,31 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem
current = previous;
int currentIndex = iconWidget->row(current);
// int previousIndex = iconWidget->row(previous);
pagesWidget->setCurrentIndex(currentIndex);
// DataFilesPage *previousPage = dynamic_cast<DataFilesPage *>(pagesWidget->widget(previousIndex));
// DataFilesPage *currentPage = dynamic_cast<DataFilesPage *>(pagesWidget->widget(currentIndex));
// //special call to update/save data files page list view when it's displayed/hidden.
// if (previousPage)
// {
// if (previousPage->objectName() == "DataFilesPage")
// previousPage->saveSettings();
// }
// else if (currentPage)
// {
// if (currentPage->objectName() == "DataFilesPage")
// currentPage->loadSettings();
// }
mSettingsPage->resetProgressBar();
}
bool Launcher::MainDialog::setupLauncherSettings()
{
mLauncherSettings.clear();
mLauncherSettings.setMultiValueEnabled(true);
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
QStringList paths;
paths.append(QString("launcher.cfg"));
paths.append(userPath + QString("launcher.cfg"));
paths.append(QString(Config::LauncherSettings::sLauncherConfigFileName));
paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName));
foreach (const QString &path, paths) {
qDebug() << "Loading config file:" << qPrintable(path);
qDebug() << "Loading config file:" << path.toUtf8().constData();
QFile file(path);
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
}
QTextStream stream(&file);
@ -311,6 +291,8 @@ bool Launcher::MainDialog::setupLauncherSettings()
bool Launcher::MainDialog::setupGameSettings()
{
mGameSettings.clear();
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str());
@ -319,18 +301,14 @@ bool Launcher::MainDialog::setupGameSettings()
QString path = userPath + QLatin1String("openmw.cfg");
QFile file(path);
qDebug() << "Loading config file:" << qPrintable(path);
qDebug() << "Loading config file:" << path.toUtf8().constData();
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
}
QTextStream stream(&file);
@ -339,26 +317,22 @@ bool Launcher::MainDialog::setupGameSettings()
mGameSettings.readUserFile(stream);
}
// Now the rest
// Now the rest - priority: user > local > global
QStringList paths;
paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(globalPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(userPath + QString("openmw.cfg"));
foreach (const QString &path, paths) {
qDebug() << "Loading config file:" << qPrintable(path);
qDebug() << "Loading config file:" << path.toUtf8().constData();
QFile file(path);
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
}
QTextStream stream(&file);
@ -410,53 +384,54 @@ bool Launcher::MainDialog::setupGameSettings()
bool Launcher::MainDialog::setupGraphicsSettings()
{
mGraphicsSettings.setMultiValueEnabled(false);
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str());
QFile localDefault(QString("settings-default.cfg"));
QFile globalDefault(globalPath + QString("settings-default.cfg"));
if (!localDefault.exists() && !globalDefault.exists()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error reading OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not find settings-default.cfg</b><br><br> \
The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem."));
msgBox.exec();
// This method is almost a copy of OMW::Engine::loadSettings(). They should definitely
// remain consistent, and possibly be merged into a shared component. At the very least
// the filenames should be in the CfgMgr component.
// Ensure to clear previous settings in case we had already loaded settings.
mEngineSettings.clear();
// Create the settings manager and load default settings file
const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string();
const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string();
std::string defaultPath;
// Prefer the settings-default.cfg in the current directory.
if (boost::filesystem::exists(localDefault))
defaultPath = localDefault;
else if (boost::filesystem::exists(globalDefault))
defaultPath = globalDefault;
// Something's very wrong if we can't find the file at all.
else {
cfgError(tr("Error reading OpenMW configuration file"),
tr("<br><b>Could not find settings-default.cfg</b><br><br> \
The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem."));
return false;
}
// Load the default settings, report any parsing errors.
try {
mEngineSettings.loadDefault(defaultPath);
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading settings-default.cfg</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
return false;
}
QStringList paths;
paths.append(globalPath + QString("settings-default.cfg"));
paths.append(QString("settings-default.cfg"));
paths.append(userPath + QString("settings.cfg"));
foreach (const QString &path, paths) {
qDebug() << "Loading config file:" << qPrintable(path);
QFile file(path);
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
// Load user settings if they exist
const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
// User settings are not required to exist, so if they don't we're done.
if (!boost::filesystem::exists(userPath)) return true;
mGraphicsSettings.readFile(stream);
}
file.close();
try {
mEngineSettings.loadUser(userPath);
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading settings.cfg</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
return false;
}
return true;
@ -505,78 +480,55 @@ bool Launcher::MainDialog::writeSettings()
if (!dir.exists()) {
if (!dir.mkpath(userPath)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not create %0</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(userPath));
msgBox.exec();
return false;
cfgError(tr("Error creating OpenMW configuration directory"),
tr("<br><b>Could not create %0</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(userPath));
return false;
}
}
// Game settings
QFile file(userPath + QString("openmw.cfg"));
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
// File cannot be opened or created
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
cfgError(tr("Error writing OpenMW configuration file"),
tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.writeFile(stream);
mGameSettings.writeFileWithComments(file);
file.close();
// Graphics settings
file.setFileName(userPath + QString("settings.cfg"));
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
// File cannot be opened or created
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
try {
mEngineSettings.saveUser(settingsPath);
}
catch (std::exception& e) {
std::string msg = "<br><b>Error writing settings.cfg</b><br><br>" +
settingsPath + "<br><br>" + e.what();
cfgError(tr("Error writing user settings file"), tr(msg.c_str()));
return false;
}
stream.setDevice(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGraphicsSettings.writeFile(stream);
file.close();
// Launcher settings
file.setFileName(userPath + QString("launcher.cfg"));
file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName));
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
// File cannot be opened or created
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error writing Launcher configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
cfgError(tr("Error writing Launcher configuration file"),
tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
}
QTextStream stream(&file);
stream.setDevice(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));

@ -13,7 +13,7 @@
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include "settings/graphicssettings.hpp"
#include <components/settings/settings.hpp>
#include "ui_mainwindow.h"
@ -31,6 +31,13 @@ namespace Launcher
class UnshieldThread;
class SettingsPage;
enum FirstRunDialogResult
{
FirstRunDialogResultFailure,
FirstRunDialogResultContinue,
FirstRunDialogResultWizard
};
#ifndef WIN32
bool expansions(Launcher::UnshieldThread& cd);
#endif
@ -43,8 +50,7 @@ namespace Launcher
explicit MainDialog(QWidget *parent = 0);
~MainDialog();
bool setup();
bool showFirstRunDialog();
FirstRunDialogResult showFirstRunDialog();
bool reloadSettings();
bool writeSettings();
@ -58,6 +64,8 @@ namespace Launcher
void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus);
private:
bool setup();
void createIcons();
void createPages();
@ -65,6 +73,8 @@ namespace Launcher
bool setupGameSettings();
bool setupGraphicsSettings();
void setVersionLabel();
void loadSettings();
void saveSettings();
@ -84,7 +94,7 @@ namespace Launcher
Files::ConfigurationManager mCfgMgr;
Config::GameSettings mGameSettings;
GraphicsSettings mGraphicsSettings;
Settings::Manager mEngineSettings;
Config::LauncherSettings mLauncherSettings;
};

@ -2,20 +2,11 @@
#include <QListView>
#ifdef Q_OS_MAC
#include <QPlastiqueStyle>
#endif
Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
{
setObjectName ("PlayPage");
setupUi(this);
// Hacks to get the stylesheet look properly
#ifdef Q_OS_MAC
QPlastiqueStyle *style = new QPlastiqueStyle;
profilesComboBox->setStyle(style);
#endif
profilesComboBox->setView(new QListView());
connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int)));

@ -1,44 +0,0 @@
#include "graphicssettings.hpp"
#include <QTextStream>
#include <QString>
#include <QRegExp>
#include <QMap>
Launcher::GraphicsSettings::GraphicsSettings()
{
}
Launcher::GraphicsSettings::~GraphicsSettings()
{
}
bool Launcher::GraphicsSettings::writeFile(QTextStream &stream)
{
QString sectionPrefix;
QRegExp sectionRe("([^/]+)/(.+)$");
QMap<QString, QString> settings = SettingsBase::getSettings();
QMapIterator<QString, QString> i(settings);
while (i.hasNext()) {
i.next();
QString prefix;
QString key;
if (sectionRe.exactMatch(i.key())) {
prefix = sectionRe.cap(1);
key = sectionRe.cap(2);
}
if (sectionPrefix != prefix) {
sectionPrefix = prefix;
stream << "\n[" << prefix << "]\n";
}
stream << key << " = " << i.value() << "\n";
}
return true;
}

@ -1,18 +0,0 @@
#ifndef GRAPHICSSETTINGS_HPP
#define GRAPHICSSETTINGS_HPP
#include <components/config/settingsbase.hpp>
namespace Launcher
{
class GraphicsSettings : public Config::SettingsBase<QMap<QString, QString> >
{
public:
GraphicsSettings();
~GraphicsSettings();
bool writeFile(QTextStream &stream);
};
}
#endif // GRAPHICSSETTINGS_HPP

@ -11,16 +11,17 @@
#include <components/config/launchersettings.hpp>
#include "utils/textinputdialog.hpp"
#include "datafilespage.hpp"
using namespace Process;
Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg,
Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, MainDialog *parent)
: mCfgMgr(cfg)
: QWidget(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, QWidget(parent)
, mMain(parent)
{
setupUi(this);
@ -38,6 +39,7 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg,
mWizardInvoker = new ProcessInvoker();
mImporterInvoker = new ProcessInvoker();
resetProgressBar();
connect(mWizardInvoker->getProcess(), SIGNAL(started()),
this, SLOT(wizardStarted()));
@ -51,7 +53,7 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg,
connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(importerFinished(int,QProcess::ExitStatus)));
mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this);
mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this);
connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)),
this, SLOT(updateOkButton(QString)));
@ -93,7 +95,7 @@ Launcher::SettingsPage::~SettingsPage()
void Launcher::SettingsPage::on_wizardButton_clicked()
{
saveSettings();
mMain->writeSettings();
if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false))
return;
@ -101,7 +103,7 @@ void Launcher::SettingsPage::on_wizardButton_clicked()
void Launcher::SettingsPage::on_importerButton_clicked()
{
saveSettings();
mMain->writeSettings();
// Create the file if it doesn't already exist, else the importer will fail
QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()));
@ -140,8 +142,13 @@ void Launcher::SettingsPage::on_importerButton_clicked()
qDebug() << "arguments " << arguments;
if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false))
return;
// start the progress bar as a "bouncing ball"
progressBar->setMaximum(0);
progressBar->setValue(0);
if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false))
{
resetProgressBar();
}
}
void Launcher::SettingsPage::on_browseButton_clicked()
@ -196,36 +203,35 @@ void Launcher::SettingsPage::importerStarted()
void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (exitCode != 0 || exitStatus == QProcess::CrashExit)
return;
// Re-read the settings in their current state
mMain->reloadSettings();
// Import selected data files from openmw.cfg
if (addonsCheckBox->isChecked())
{
if (mProfileDialog->exec() == QDialog::Accepted)
{
const QString profile(mProfileDialog->lineEdit()->text());
const QStringList files(mGameSettings.values(QLatin1String("content")));
qDebug() << "Profile " << profile << files;
// Doesn't quite work right now
mLauncherSettings.setValue(QLatin1String("Profiles/currentprofile"), profile);
foreach (const QString &file, files) {
mLauncherSettings.setMultiValue(QLatin1String("Profiles/") + profile + QLatin1String("/content"), file);
}
resetProgressBar();
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Importer finished"));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText(tr("Failed to import settings from INI file."));
msgBox.exec();
}
else
{
// indicate progress finished
progressBar->setMaximum(1);
progressBar->setValue(1);
mGameSettings.remove(QLatin1String("content"));
}
// Importer may have changed settings, so refresh
mMain->reloadSettings();
}
mMain->reloadSettings();
importerButton->setEnabled(true);
}
void Launcher::SettingsPage::resetProgressBar()
{
// set progress bar to 0 %
progressBar->reset();
}
void Launcher::SettingsPage::updateOkButton(const QString &text)
{
// We do this here because we need to access the profiles
@ -234,7 +240,7 @@ void Launcher::SettingsPage::updateOkButton(const QString &text)
return;
}
const QStringList profiles(mLauncherSettings.subKeys(QString("Profiles/")));
const QStringList profiles(mLauncherSettings.getContentLists());
(profiles.contains(text))
? mProfileDialog->setOkButtonEnabled(false)

@ -29,6 +29,9 @@ namespace Launcher
void saveSettings();
bool loadSettings();
/// set progress bar on page to 0%
void resetProgressBar();
private slots:
@ -57,7 +60,6 @@ namespace Launcher
MainDialog *mMain;
TextInputDialog *mProfileDialog;
};
}

@ -9,16 +9,26 @@ set(MWINIIMPORT_HEADER
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
add_executable(mwiniimport
add_executable(openmw-iniimporter
${MWINIIMPORT}
)
target_link_libraries(mwiniimport
${Boost_LIBRARIES}
target_link_libraries(openmw-iniimporter
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)
if (WIN32)
target_link_libraries(openmw-iniimporter
${Boost_LOCALE_LIBRARY})
endif()
if (MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
endif()
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(mwiniimport gcov)
target_link_libraries(openmw-iniimporter gcov)
endif()

@ -1,5 +1,6 @@
#include "importer.hpp"
#include <ctime>
#include <iostream>
#include <string>
#include <map>
@ -8,7 +9,8 @@
#include <sstream>
#include <components/misc/stringops.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/version.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
namespace bfs = boost::filesystem;
@ -19,7 +21,6 @@ MwIniImporter::MwIniImporter()
{
const char *map[][2] =
{
{ "fps", "General:Show FPS" },
{ "no-sound", "General:Disable Audio" },
{ 0, 0 }
};
@ -638,6 +639,9 @@ MwIniImporter::MwIniImporter()
"Blood:Texture Name 1",
"Blood:Texture Name 2",
// werewolf (Bloodmoon)
"General:Werewolf FOV",
0
};
@ -660,7 +664,7 @@ std::string MwIniImporter::numberToString(int n) {
return str.str();
}
MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filename) const {
MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const {
std::cout << "load ini file: " << filename << std::endl;
std::string section("");
@ -719,7 +723,7 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filenam
return map;
}
MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::string& filename) {
MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::path& filename) {
std::cout << "load cfg file: " << filename << std::endl;
MwIniImporter::multistrmap map;
@ -825,10 +829,14 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
}
}
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const {
std::vector<std::string> contentFiles;
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const {
std::vector<std::pair<std::time_t, std::string> > contentFiles;
std::string baseGameFile("Game Files:GameFile");
std::string gameFile("");
std::time_t defaultTime = 0;
// assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini
const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files");
multistrmap::const_iterator it = ini.begin();
for(int i=0; it != ini.end(); i++) {
@ -842,21 +850,23 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) co
for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
std::string filetype(entry->substr(entry->length()-3));
Misc::StringUtils::toLower(filetype);
Misc::StringUtils::lowerCaseInPlace(filetype);
if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) {
contentFiles.push_back(*entry);
boost::filesystem::path filepath(gameFilesDir);
filepath /= *entry;
contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry));
}
}
gameFile = "";
}
cfg.erase("content");
cfg.insert( std::make_pair("content", std::vector<std::string>() ) );
for(std::vector<std::string>::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) {
cfg["content"].push_back(*it);
// this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed.
sort(contentFiles.begin(), contentFiles.end());
for(std::vector<std::pair<std::time_t, std::string> >::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) {
cfg["content"].push_back(it->second);
}
}
@ -873,3 +883,32 @@ void MwIniImporter::setInputEncoding(const ToUTF8::FromType &encoding)
{
mEncoding = encoding;
}
std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime)
{
std::time_t writeTime(defaultTime);
if (boost::filesystem::exists(filename))
{
// FixMe: remove #if when Boost dependency for Linux builds updated
// This allows Linux to build until then
#if (BOOST_VERSION >= 104800)
// need to resolve any symlinks so that we get time of file, not symlink
boost::filesystem::path resolved = boost::filesystem::canonical(filename);
#else
boost::filesystem::path resolved = filename;
#endif
writeTime = boost::filesystem::last_write_time(resolved);
// print timestamp
const int size=1024;
char timeStrBuffer[size];
if (std::strftime(timeStrBuffer, size, "%x %X", localtime(&writeTime)) > 0)
std::cout << "content file: " << resolved << " timestamp = (" << writeTime <<
") " << timeStrBuffer << std::endl;
}
else
{
std::cout << "content file: " << filename << " not found" << std::endl;
}
return writeTime;
}

@ -6,6 +6,7 @@
#include <vector>
#include <exception>
#include <iosfwd>
#include <boost/filesystem/path.hpp>
#include <components/to_utf8/to_utf8.hpp>
@ -17,17 +18,22 @@ class MwIniImporter {
MwIniImporter();
void setInputEncoding(const ToUTF8::FromType& encoding);
void setVerbose(bool verbose);
multistrmap loadIniFile(const std::string& filename) const;
static multistrmap loadCfgFile(const std::string& filename);
multistrmap loadIniFile(const boost::filesystem::path& filename) const;
static multistrmap loadCfgFile(const boost::filesystem::path& filename);
void merge(multistrmap &cfg, const multistrmap &ini) const;
void mergeFallback(multistrmap &cfg, const multistrmap &ini) const;
void importGameFiles(multistrmap &cfg, const multistrmap &ini) const;
void importGameFiles(multistrmap &cfg, const multistrmap &ini,
const boost::filesystem::path& iniFilename) const;
void importArchives(multistrmap &cfg, const multistrmap &ini) const;
static void writeToFile(std::ostream &out, const multistrmap &cfg);
private:
static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value);
static std::string numberToString(int n);
/// \return file's "last modified time", used in original MW to determine plug-in load order
static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime);
bool mVerbose;
strmap mMergeMap;
std::vector<std::string> mMergeFallback;

@ -56,93 +56,87 @@ int wmain(int argc, wchar_t *wargv[]) {
char **argv = converter.get();
boost::filesystem::path::imbue(boost::locale::generator().generate(""));
#endif
bpo::options_description desc("Syntax: mwiniimporter <options> inifile configfile\nAllowed options");
bpo::positional_options_description p_desc;
desc.add_options()
("help,h", "produce help message")
("verbose,v", "verbose output")
("ini,i", bpo::value<std::string>(), "morrowind.ini file")
("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
("game-files,g", "import esm and esp files")
("no-archives,A", "disable bsa archives import")
("encoding,e", bpo::value<std::string>()-> default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default")
;
p_desc.add("ini", 1).add("cfg", 1);
bpo::variables_map vm;
try
{
bpo::options_description desc("Syntax: openmw-iniimporter <options> inifile configfile\nAllowed options");
bpo::positional_options_description p_desc;
desc.add_options()
("help,h", "produce help message")
("verbose,v", "verbose output")
("ini,i", bpo::value<std::string>(), "morrowind.ini file")
("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
("game-files,g", "import esm and esp files")
("no-archives,A", "disable bsa archives import")
("encoding,e", bpo::value<std::string>()-> default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default")
;
p_desc.add("ini", 1).add("cfg", 1);
bpo::variables_map vm;
bpo::parsed_options parsed = bpo::command_line_parser(argc, argv)
.options(desc)
.positional(p_desc)
.run();
bpo::store(parsed, vm);
}
catch(boost::program_options::unknown_option & x)
{
std::cerr << "ERROR: " << x.what() << std::endl;
return false;
}
catch(boost::program_options::invalid_command_line_syntax & x)
{
std::cerr << "ERROR: " << x.what() << std::endl;
return false;
}
if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) {
std::cout << desc;
return 0;
}
bpo::notify(vm);
if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) {
std::cout << desc;
return 0;
}
std::string iniFile = vm["ini"].as<std::string>();
std::string cfgFile = vm["cfg"].as<std::string>();
bpo::notify(vm);
// if no output is given, write back to cfg file
std::string outputFile(vm["output"].as<std::string>());
if(vm["output"].defaulted()) {
outputFile = vm["cfg"].as<std::string>();
}
boost::filesystem::path iniFile(vm["ini"].as<std::string>());
boost::filesystem::path cfgFile(vm["cfg"].as<std::string>());
if(!boost::filesystem::exists(iniFile)) {
std::cerr << "ini file does not exist" << std::endl;
return -3;
}
if(!boost::filesystem::exists(cfgFile))
std::cerr << "cfg file does not exist" << std::endl;
// if no output is given, write back to cfg file
std::string outputFile(vm["output"].as<std::string>());
if(vm["output"].defaulted()) {
outputFile = vm["cfg"].as<std::string>();
}
MwIniImporter importer;
importer.setVerbose(vm.count("verbose"));
if(!boost::filesystem::exists(iniFile)) {
std::cerr << "ini file does not exist" << std::endl;
return -3;
}
if(!boost::filesystem::exists(cfgFile))
std::cerr << "cfg file does not exist" << std::endl;
// Font encoding settings
std::string encoding(vm["encoding"].as<std::string>());
importer.setInputEncoding(ToUTF8::calculateEncoding(encoding));
MwIniImporter importer;
importer.setVerbose(vm.count("verbose") != 0);
MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
// Font encoding settings
std::string encoding(vm["encoding"].as<std::string>());
importer.setInputEncoding(ToUTF8::calculateEncoding(encoding));
importer.merge(cfg, ini);
importer.mergeFallback(cfg, ini);
MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
if(vm.count("game-files")) {
importer.importGameFiles(cfg, ini);
}
importer.merge(cfg, ini);
importer.mergeFallback(cfg, ini);
if(!vm.count("no-archives")) {
importer.importArchives(cfg, ini);
}
if(vm.count("game-files")) {
importer.importGameFiles(cfg, ini, iniFile);
}
std::cout << "write to: " << outputFile << std::endl;
bfs::ofstream file((bfs::path(outputFile)));
importer.writeToFile(file, cfg);
if(!vm.count("no-archives")) {
importer.importArchives(cfg, ini);
}
std::cout << "write to: " << outputFile << std::endl;
bfs::ofstream file((bfs::path(outputFile)));
importer.writeToFile(file, cfg);
}
catch (std::exception& e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
}
return 0;
}

@ -9,7 +9,7 @@ add_executable(niftest
)
target_link_libraries(niftest
${Boost_LIBRARIES}
${Boost_FILESYSTEM_LIBRARY}
components
)

@ -0,0 +1,165 @@
///Program to test .nif files both on the FileSystem and in BSA archives.
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <components/nif/niffile.hpp>
#include <components/files/constrainedfilestream.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/bsaarchive.hpp>
#include <components/vfs/filesystemarchive.hpp>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
// Create local aliases for brevity
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
///See if the file has the named extension
bool hasExtension(std::string filename, std::string extensionToFind)
{
std::string extension = filename.substr(filename.find_last_of(".")+1);
//Convert strings to lower case for comparison
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower);
if(extension == extensionToFind)
return true;
else
return false;
}
///See if the file has the "nif" extension.
bool isNIF(std::string filename)
{
return hasExtension(filename,"nif");
}
///See if the file has the "bsa" extension.
bool isBSA(std::string filename)
{
return hasExtension(filename,"bsa");
}
/// Check all the nif files in a given VFS::Archive
/// \note Takes ownership!
/// \note Can not read a bsa file inside of a bsa file.
void readVFS(VFS::Archive* anArchive,std::string archivePath = "")
{
VFS::Manager myManager(true);
myManager.addArchive(anArchive);
myManager.buildIndex();
std::map<std::string, VFS::File*> files=myManager.getIndex();
for(std::map<std::string, VFS::File*>::const_iterator it=files.begin(); it!=files.end(); ++it)
{
std::string name = it->first;
try{
if(isNIF(name))
{
// std::cout << "Decoding: " << name << std::endl;
Nif::NIFFile temp_nif(myManager.get(name),archivePath+name);
}
else if(isBSA(name))
{
if(!archivePath.empty() && !isBSA(archivePath))
{
// std::cout << "Reading BSA File: " << name << std::endl;
readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/");
// std::cout << "Done with BSA File: " << name << std::endl;
}
}
}
catch (std::exception& e)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
}
}
}
std::vector<std::string> parseOptions (int argc, char** argv)
{
bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n"
"Usages:\n"
" niftool <nif files, BSA files, or directories>\n"
" Scan the file or directories for nif errors.\n\n"
"Allowed options");
desc.add_options()
("help,h", "print help message.")
("input-file", bpo::value< std::vector<std::string> >(), "input file")
;
//Default option if none provided
bpo::positional_options_description p;
p.add("input-file", -1);
bpo::variables_map variables;
try
{
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).
options(desc).positional(p).run();
bpo::store(valid_opts, variables);
}
catch(std::exception &e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
<< desc << std::endl;
exit(1);
}
bpo::notify(variables);
if (variables.count ("help"))
{
std::cout << desc << std::endl;
exit(1);
}
if (variables.count("input-file"))
{
return variables["input-file"].as< std::vector<std::string> >();
}
std::cout << "No input files or directories specified!" << std::endl;
std::cout << desc << std::endl;
exit(1);
}
int main(int argc, char **argv)
{
std::vector<std::string> files = parseOptions (argc, argv);
// std::cout << "Reading Files" << std::endl;
for(std::vector<std::string>::const_iterator it=files.begin(); it!=files.end(); ++it)
{
std::string name = *it;
try{
if(isNIF(name))
{
//std::cout << "Decoding: " << name << std::endl;
Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name);
}
else if(isBSA(name))
{
// std::cout << "Reading BSA File: " << name << std::endl;
readVFS(new VFS::BsaArchive(name));
}
else if(bfs::is_directory(bfs::path(name)))
{
// std::cout << "Reading All Files in: " << name << std::endl;
readVFS(new VFS::FileSystemArchive(name),name);
}
else
{
std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
}
}
return 0;
}

@ -4,10 +4,8 @@ set (OPENCS_SRC main.cpp
opencs_units (. editor)
set (CMAKE_BUILD_TYPE DEBUG)
opencs_units (model/doc
document operation saving documentmanager loader runner
document operation saving documentmanager loader runner operationholder
)
opencs_units_noqt (model/doc
@ -20,14 +18,15 @@ opencs_hdrs_noqt (model/doc
opencs_units (model/world
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel
)
opencs_units_noqt (model/world
universalid record commands columnbase scriptcontext cell refidcollection
universalid record commands columnbase columnimp scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
pathgrid landtexture land
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro
)
opencs_hdrs_noqt (model/world
@ -36,18 +35,24 @@ opencs_hdrs_noqt (model/world
opencs_units (model/tools
tools reportmodel
tools reportmodel mergeoperation
)
opencs_units_noqt (model/tools
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck referenceablecheck scriptcheck bodypartcheck
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages gmstcheck topicinfocheck journalcheck
)
opencs_hdrs_noqt (model/tools
mergestate
)
opencs_units (view/doc
viewmanager view operations operation subview startup filedialog newgame
filewidget adjusterwidget loader globaldebugprofilemenu runlogsubview
filewidget adjusterwidget loader globaldebugprofilemenu runlogsubview sizehint
)
@ -62,68 +67,56 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
)
opencs_units_noqt (view/world
subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
scripthighlighter idvalidator dialoguecreator physicssystem
scripthighlighter idvalidator dialoguecreator idcompletiondelegate
colordelegate dragdroputils
)
opencs_units (view/widget
scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton
scenetooltoggle2
scenetooltoggle2 completerpopup coloreditor colorpickerpopup droplineedit
)
opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode
previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode
)
opencs_units_noqt (view/render
navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight
lightingbright object cell terrainstorage textoverlay overlaymask overlaysystem mousestate
lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase
cellarrow cellmarker cellborder cameracontroller pathgrid
)
opencs_hdrs_noqt (view/render
elements
mask
)
opencs_units (view/tools
reportsubview reporttable
reportsubview reporttable searchsubview searchbox merge
)
opencs_units_noqt (view/tools
subviews
)
opencs_units (view/settings
settingwindow
dialog
page
view
booleanview
textview
listview
rangeview
resizeablestackedwidget
spinbox
)
opencs_units_noqt (view/settings
frame
opencs_units (view/prefs
dialogue pagebase page
)
opencs_units (model/settings
usersettings
setting
connector
opencs_units (model/prefs
state setting intsetting doublesetting boolsetting enumsetting coloursetting
)
opencs_hdrs_noqt (model/settings
support
opencs_units_noqt (model/prefs
category
)
opencs_units_noqt (model/filter
@ -146,37 +139,34 @@ set (OPENCS_UI
${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui
)
source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR})
source_group (openmw-cs FILES ${OPENCS_SRC} ${OPENCS_HDR})
if(WIN32)
set(QT_USE_QTMAIN TRUE)
endif(WIN32)
set(BOOST_COMPONENTS system filesystem program_options thread wave)
if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
endif(WIN32)
find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED)
include(${QT_USE_FILE})
qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
if (DESIRED_QT_VERSION MATCHES 4)
include(${QT_USE_FILE})
qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
else()
qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${BULLET_INCLUDE_DIRS})
# for compiled .ui files
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(APPLE)
set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns)
set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns)
else()
set (OPENCS_MAC_ICON "")
endif(APPLE)
add_executable(opencs
add_executable(openmw-cs
MACOSX_BUNDLE
${OENGINE_BULLET}
${OPENCS_SRC}
${OPENCS_UI_HDR}
${OPENCS_MOC_SRC}
@ -185,31 +175,55 @@ add_executable(opencs
)
if(APPLE)
set_target_properties(opencs PROPERTIES
set_target_properties(openmw-cs PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
OUTPUT_NAME "OpenCS"
MACOSX_BUNDLE_ICON_FILE "opencs.icns"
OUTPUT_NAME "OpenMW-CS"
MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns"
MACOSX_BUNDLE_BUNDLE_NAME "OpenCS"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs-Info.plist.in"
)
set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
endif(APPLE)
target_link_libraries(opencs
${OGRE_LIBRARIES}
${OGRE_Overlay_LIBRARIES}
${OGRE_STATIC_PLUGINS}
${SHINY_LIBRARIES}
${Boost_LIBRARIES}
${BULLET_LIBRARIES}
${QT_LIBRARIES}
target_link_libraries(openmw-cs
${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSGVIEWER_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGFX_LIBRARIES}
${EXTERN_OSGQT_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
)
if (DESIRED_QT_VERSION MATCHES 4)
target_link_libraries(openmw-cs
${QT_QTGUI_LIBRARY}
${QT_QTCORE_LIBRARY}
${QT_QTNETWORK_LIBRARY}
${QT_QTOPENGL_LIBRARY})
if (WIN32)
target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY})
endif()
else()
qt5_use_modules(openmw-cs Widgets Core Network OpenGL)
endif()
if (WIN32)
target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY})
endif()
if(APPLE)
INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE)
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE)
endif()

@ -1,52 +1,52 @@
#include "editor.hpp"
#include <openengine/bullet/BulletShapeLoader.h>
#include <QApplication>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMessageBox>
#include <OgreRoot.h>
#include <OgreRenderWindow.h>
#include <extern/shiny/Main/Factory.hpp>
#include <extern/shiny/Platforms/Ogre/OgrePlatform.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
#include <components/ogreinit/ogreinit.hpp>
#include <components/fallback/validate.hpp>
#include <components/bsa/resources.hpp>
#include <components/nifosg/nifloader.hpp>
#include "model/doc/document.hpp"
#include "model/world/data.hpp"
CS::Editor::Editor (OgreInit::OgreInit& ogreInit)
: mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr),
mViewManager (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL), mPid(""), mLock()
#ifdef _WIN32
#include <Windows.h>
#endif
using namespace Fallback;
CS::Editor::Editor ()
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
mViewManager (mDocumentManager), mPid(""),
mLock(), mMerge (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
{
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
setupDataFiles (config.first);
CSMSettings::UserSettings::instance().loadSettings ("opencs.ini");
mSettings.setModel (CSMSettings::UserSettings::instance());
ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string());
NifOsg::Loader::setShowMarkers(true);
mOverlaySystem.reset (new CSVRender::OverlaySystem);
mVFS.reset(new VFS::Manager(mFsStrict));
Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true,
mFsStrict);
VFS::registerArchives(mVFS.get(), Files::Collections(config.first, !mFsStrict), config.second, true);
mDocumentManager.listResources();
mDocumentManager.setVFS(mVFS.get());
mNewGame.setLocalData (mLocal);
mFileDialog.setLocalData (mLocal);
mMerge.setLocalData (mLocal);
connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)),
this, SLOT (documentAdded (CSMDoc::Document *)));
connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)),
this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *)));
connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()),
this, SLOT (lastDocumentDeleted()));
@ -54,6 +54,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit)
connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ()));
connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ()));
connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *)));
connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ()));
connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ()));
@ -65,9 +66,11 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit)
connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)),
this, SLOT(createNewFile (const boost::filesystem::path&)));
connect (&mFileDialog, SIGNAL (rejected()), this, SLOT (cancelFileDialog ()));
connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)),
this, SLOT (createNewGame (const boost::filesystem::path&)));
connect (&mNewGame, SIGNAL (cancelCreateGame()), this, SLOT (cancelCreateGame ()));
}
CS::Editor::~Editor ()
@ -75,10 +78,8 @@ CS::Editor::~Editor ()
mPidFile.close();
if(mServer && boost::filesystem::exists(mPid))
remove(mPid.string().c_str()); // ignore any error
// cleanup global resources used by OEngine
delete OEngine::Physic::BulletShapeManager::getSingletonPtr();
static_cast<void> ( // silence coverity warning
remove(mPid.string().c_str())); // ignore any error
}
void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs)
@ -90,10 +91,10 @@ void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs)
}
}
std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfig()
std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfig(bool quiet)
{
boost::program_options::variables_map variables;
boost::program_options::options_description desc("Syntax: opencs <options>\nAllowed options");
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing())
@ -103,6 +104,8 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
("resources", boost::program_options::value<std::string>()->default_value("resources"))
("fallback-archive", boost::program_options::value<std::vector<std::string> >()->
default_value(std::vector<std::string>(), "fallback-archive")->multitoken())
("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")
->multitoken()->composing(), "fallback values")
("script-blacklist", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "")
->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
@ -110,13 +113,15 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc);
mCfgMgr.readConfiguration(variables, desc, quiet);
mDocumentManager.setEncoding (
ToUTF8::calculateEncoding (variables["encoding"].as<std::string>()));
mDocumentManager.setResourceDir (mResources = variables["resources"].as<std::string>());
mDocumentManager.setFallbackMap (variables["fallback"].as<FallbackMap>().mMap);
if (variables["script-blacklist-use"].as<bool>())
mDocumentManager.setBlacklistedScripts (
variables["script-blacklist"].as<std::vector<std::string> >());
@ -173,15 +178,53 @@ void CS::Editor::createGame()
mNewGame.activateWindow();
}
void CS::Editor::cancelCreateGame()
{
if (!mDocumentManager.isEmpty())
return;
mNewGame.hide();
if (mStartup.isHidden())
mStartup.show();
mStartup.raise();
mStartup.activateWindow();
}
void CS::Editor::createAddon()
{
mStartup.hide();
mFileDialog.clearFiles();
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig(/*quiet*/true);
setupDataFiles (config.first);
mFileDialog.showDialog (CSVDoc::ContentAction_New);
}
void CS::Editor::cancelFileDialog()
{
if (!mDocumentManager.isEmpty())
return;
mFileDialog.hide();
if (mStartup.isHidden())
mStartup.show();
mStartup.raise();
mStartup.activateWindow();
}
void CS::Editor::loadDocument()
{
mStartup.hide();
mFileDialog.clearFiles();
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig(/*quiet*/true);
setupDataFiles (config.first);
mFileDialog.showDialog (CSVDoc::ContentAction_Edit);
}
@ -236,6 +279,7 @@ void CS::Editor::showSettings()
if (mSettings.isHidden())
mSettings.show();
mSettings.move (QCursor::pos());
mSettings.raise();
mSettings.activateWindow();
}
@ -245,7 +289,7 @@ bool CS::Editor::makeIPCServer()
try
{
mPid = boost::filesystem::temp_directory_path();
mPid /= "opencs.pid";
mPid /= "openmw-cs.pid";
bool pidExists = boost::filesystem::exists(mPid);
mPidFile.open(mPid);
@ -273,12 +317,12 @@ bool CS::Editor::makeIPCServer()
mServer->close();
fullPath.remove(QRegExp("dummy$"));
fullPath += mIpcServerName;
if(boost::filesystem::exists(fullPath.toStdString().c_str()))
if(boost::filesystem::exists(fullPath.toUtf8().constData()))
{
// TODO: compare pid of the current process with that in the file
std::cout << "Detected unclean shutdown." << std::endl;
// delete the stale file
if(remove(fullPath.toStdString().c_str()))
if(remove(fullPath.toUtf8().constData()))
std::cerr << "ERROR removing stale connection file" << std::endl;
}
}
@ -320,120 +364,26 @@ int CS::Editor::run()
return QApplication::exec();
}
std::auto_ptr<sh::Factory> CS::Editor::setupGraphics()
void CS::Editor::documentAdded (CSMDoc::Document *document)
{
std::string renderer =
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
"Direct3D9 Rendering Subsystem";
#else
"OpenGL Rendering Subsystem";
#endif
std::string renderSystem = mUserSettings.setting("Video/render system", renderer.c_str()).toStdString();
Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName(renderSystem));
// Initialise Ogre::OverlaySystem after Ogre::Root but before initialisation
mOverlaySystem.get();
Ogre::Root::getSingleton().initialise(false);
// Create a hidden background window to keep resources
Ogre::NameValuePairList params;
params.insert(std::make_pair("title", ""));
std::string antialiasing = mUserSettings.settingValue("Video/antialiasing").toStdString();
if(antialiasing == "MSAA 16") antialiasing = "16";
else if(antialiasing == "MSAA 8") antialiasing = "8";
else if(antialiasing == "MSAA 4") antialiasing = "4";
else if(antialiasing == "MSAA 2") antialiasing = "2";
else antialiasing = "0";
params.insert(std::make_pair("FSAA", antialiasing));
params.insert(std::make_pair("vsync", "false"));
params.insert(std::make_pair("hidden", "true"));
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
params.insert(std::make_pair("macAPI", "cocoa"));
#endif
// NOTE: fullscreen mode not supported (doesn't really make sense for opencs)
Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, &params);
hiddenWindow->setActive(false);
sh::OgrePlatform* platform =
new sh::OgrePlatform ("General", (mResources / "materials").string());
// for font used in overlays
Ogre::Root::getSingleton().addResourceLocation ((mResources / "mygui").string(),
"FileSystem", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true);
if (!boost::filesystem::exists (mCfgMgr.getCachePath()))
boost::filesystem::create_directories (mCfgMgr.getCachePath());
platform->setCacheFolder (mCfgMgr.getCachePath().string());
std::auto_ptr<sh::Factory> factory (new sh::Factory (platform));
QString shLang = mUserSettings.settingValue("General/shader mode");
QString rend = renderSystem.c_str();
bool openGL = rend.contains(QRegExp("^OpenGL", Qt::CaseInsensitive));
bool glES = rend.contains(QRegExp("^OpenGL ES", Qt::CaseInsensitive));
// force shader language based on render system
if(shLang == ""
|| (openGL && shLang == "hlsl")
|| (!openGL && shLang == "glsl")
|| (glES && shLang != "glsles"))
{
shLang = openGL ? (glES ? "glsles" : "glsl") : "hlsl";
//no group means "General" group in the "ini" file standard
mUserSettings.setDefinitions("shader mode", (QStringList() << shLang));
}
enum sh::Language lang;
if(shLang == "glsl") lang = sh::Language_GLSL;
else if(shLang == "glsles") lang = sh::Language_GLSLES;
else if(shLang == "hlsl") lang = sh::Language_HLSL;
else lang = sh::Language_CG;
factory->setCurrentLanguage (lang);
factory->setWriteSourceCache (true);
factory->setReadSourceCache (true);
factory->setReadMicrocodeCache (true);
factory->setWriteMicrocodeCache (true);
factory->loadAllFiles();
bool shaders = mUserSettings.setting("3d-render/shaders", QString("true")) == "true" ? true : false;
sh::Factory::getInstance ().setShadersEnabled (shaders);
std::string fog = mUserSettings.setting("Shader/fog", QString("true")).toStdString();
sh::Factory::getInstance().setGlobalSetting ("fog", fog);
std::string shadows = mUserSettings.setting("Shader/shadows", QString("false")).toStdString();
sh::Factory::getInstance().setGlobalSetting ("shadows", shadows);
std::string shadows_pssm = mUserSettings.setting("Shader/shadows_pssm", QString("false")).toStdString();
sh::Factory::getInstance().setGlobalSetting ("shadows_pssm", shadows_pssm);
std::string render_refraction = mUserSettings.setting("Shader/render_refraction", QString("false")).toStdString();
sh::Factory::getInstance ().setGlobalSetting ("render_refraction", render_refraction);
// internal setting - may be switched on or off by the use of shader configurations
sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false");
std::string num_lights = mUserSettings.setting("3d-render-adv/num_lights", QString("8")).toStdString();
sh::Factory::getInstance ().setGlobalSetting ("num_lights", num_lights);
/// \todo add more configurable shiny settings
return factory;
mViewManager.addView (document);
}
void CS::Editor::documentAdded (CSMDoc::Document *document)
void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document)
{
mViewManager.addView (document);
if (mMerge.getDocument()==document)
mMerge.cancel();
}
void CS::Editor::lastDocumentDeleted()
{
QApplication::quit();
}
void CS::Editor::mergeDocument (CSMDoc::Document *document)
{
mMerge.configure (document);
mMerge.show();
mMerge.raise();
mMerge.activateWindow();
}

@ -11,30 +11,33 @@
#include <QLocalServer>
#include <QLocalSocket>
#include <extern/shiny/Main/Factory.hpp>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
#include <components/files/multidircollection.hpp>
#include <components/nifcache/nifcache.hpp>
#include "model/settings/usersettings.hpp"
#include "model/doc/documentmanager.hpp"
#include "model/prefs/state.hpp"
#include "view/doc/viewmanager.hpp"
#include "view/doc/startup.hpp"
#include "view/doc/filedialog.hpp"
#include "view/doc/newgame.hpp"
#include "view/settings/dialog.hpp"
#include "view/render/overlaysystem.hpp"
#include "view/prefs/dialogue.hpp"
namespace OgreInit
#include "view/tools/merge.hpp"
namespace VFS
{
class OgreInit;
class Manager;
}
namespace CSMDoc
{
class Document;
}
namespace CS
@ -43,15 +46,16 @@ namespace CS
{
Q_OBJECT
Nif::Cache mNifCache;
// FIXME: should be moved to document, so we can have different resources for each opened project
std::auto_ptr<VFS::Manager> mVFS;
Files::ConfigurationManager mCfgMgr;
CSMSettings::UserSettings mUserSettings;
std::auto_ptr<CSVRender::OverlaySystem> mOverlaySystem;
CSMPrefs::State mSettingsState;
CSMDoc::DocumentManager mDocumentManager;
CSVDoc::ViewManager mViewManager;
CSVDoc::StartupDialogue mStartup;
CSVDoc::NewGameDialogue mNewGame;
CSVSettings::Dialog mSettings;
CSVPrefs::Dialogue mSettings;
CSVDoc::FileDialog mFileDialog;
boost::filesystem::path mLocal;
boost::filesystem::path mResources;
@ -59,10 +63,11 @@ namespace CS
boost::interprocess::file_lock mLock;
boost::filesystem::ofstream mPidFile;
bool mFsStrict;
CSVTools::Merge mMerge;
void setupDataFiles (const Files::PathContainer& dataDirs);
std::pair<Files::PathContainer, std::vector<std::string> > readConfig();
std::pair<Files::PathContainer, std::vector<std::string> > readConfig(bool quiet=false);
///< \return data paths
// not implemented
@ -71,7 +76,7 @@ namespace CS
public:
Editor (OgreInit::OgreInit& ogreInit);
Editor ();
~Editor ();
bool makeIPCServer();
@ -80,13 +85,12 @@ namespace CS
int run();
///< \return error status
std::auto_ptr<sh::Factory> setupGraphics();
///< The returned factory must persist at least as long as *this.
private slots:
void createGame();
void createAddon();
void cancelCreateGame();
void cancelFileDialog();
void loadDocument();
void openFiles (const boost::filesystem::path &path);
@ -99,8 +103,12 @@ namespace CS
void documentAdded (CSMDoc::Document *document);
void documentAboutToBeRemoved (CSMDoc::Document *document);
void lastDocumentDeleted();
void mergeDocument (CSMDoc::Document *document);
private:
QString mIpcServerName;

@ -1,4 +1,3 @@
#include "editor.hpp"
#include <exception>
@ -9,9 +8,7 @@
#include <QIcon>
#include <QMetaType>
#include <extern/shiny/Main/Factory.hpp>
#include <components/ogreinit/ogreinit.hpp>
#include "model/doc/messages.hpp"
#include "model/world/universalid.hpp"
@ -46,47 +43,48 @@ class Application : public QApplication
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE (resources);
qRegisterMetaType<std::string> ("std::string");
qRegisterMetaType<CSMWorld::UniversalId> ("CSMWorld::UniversalId");
OgreInit::OgreInit ogreInit;
#ifdef Q_OS_MAC
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif
std::auto_ptr<sh::Factory> shinyFactory;
try
{
// To allow background thread drawing in OSG
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
Application application (argc, argv);
Q_INIT_RESOURCE (resources);
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
if (dir.dirName() == "MacOS") {
dir.cdUp();
dir.cdUp();
dir.cdUp();
}
QDir::setCurrent(dir.absolutePath());
qRegisterMetaType<std::string> ("std::string");
qRegisterMetaType<CSMWorld::UniversalId> ("CSMWorld::UniversalId");
qRegisterMetaType<CSMDoc::Message> ("CSMDoc::Message");
// force Qt to load only LOCAL plugins, don't touch system Qt installation
QDir pluginsPath(QCoreApplication::applicationDirPath());
pluginsPath.cdUp();
pluginsPath.cd("Plugins");
Application application (argc, argv);
QStringList libraryPaths;
libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath();
application.setLibraryPaths(libraryPaths);
#endif
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
if (dir.dirName() == "MacOS") {
dir.cdUp();
dir.cdUp();
dir.cdUp();
}
QDir::setCurrent(dir.absolutePath());
#endif
application.setWindowIcon (QIcon (":./opencs.png"));
application.setWindowIcon (QIcon (":./openmw-cs.png"));
CS::Editor editor (ogreInit);
CS::Editor editor;
if(!editor.makeIPCServer())
if(!editor.makeIPCServer())
{
editor.connectToIPCServer();
return 0;
}
return editor.run();
}
catch (std::exception& e)
{
editor.connectToIPCServer();
std::cerr << "ERROR: " << e.what() << std::endl;
return 0;
}
shinyFactory = editor.setupGraphics();
return editor.run();
}

@ -1,4 +1,3 @@
#include "blacklist.hpp"
#include <algorithm>
@ -28,4 +27,4 @@ void CSMDoc::Blacklist::add (CSMWorld::UniversalId::Type type,
std::transform (ids.begin(), ids.end(), list.begin()+size, Misc::StringUtils::lowerCase);
std::sort (list.begin(), list.end());
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save