Merge
authorMedo <smaxein@googlemail.com>
Sat, 18 Aug 2012 00:48:09 +0200
changeset 7550 3c4b4cb40f40
parent 7508 763d3961400b (diff)
parent 7548 c7a21fc530de (current diff)
child 7552 1209e1c3c620
Merge
--- a/.hgignore	Fri Aug 17 10:39:23 2012 -0400
+++ b/.hgignore	Sat Aug 18 00:48:09 2012 +0200
@@ -40,7 +40,7 @@
 glob:*.rej
 glob:project_files/Android-build/SDL-android-project/jni/**
 glob:project_files/Android-build/SDL-android-project/obj
-glob:project_files/Android-build/SDL-android-project/libs
+glob:project_files/Android-build/SDL-android-project/libs/armeabi*
 glob:project_files/Android-build/SDL-android-project/bin
 glob:project_files/Android-build/SDL-android-project/gen
 glob:project_files/Android-build/SDL-android-project/local.properties
--- a/QTfrontend/CMakeLists.txt	Fri Aug 17 10:39:23 2012 -0400
+++ b/QTfrontend/CMakeLists.txt	Sat Aug 18 00:48:09 2012 +0200
@@ -130,7 +130,6 @@
     achievements.h
     binds.h
     ui_hwform.h
-    KB.h
     hwconsts.h
     sdlkeys.h
     )
--- a/QTfrontend/game.cpp	Fri Aug 17 10:39:23 2012 -0400
+++ b/QTfrontend/game.cpp	Sat Aug 18 00:48:09 2012 +0200
@@ -28,7 +28,6 @@
 #include "gameuiconfig.h"
 #include "gamecfgwidget.h"
 #include "teamselect.h"
-#include "KB.h"
 #include "proto.h"
 #include "ThemeModel.h"
 
@@ -225,20 +224,6 @@
             emit ErrorMessage(QString("Last two engine messages:\n") + QString().append(msg.mid(2)).left(size - 4));
             return;
         }
-        case 'K':
-        {
-            ulong kb = msg.mid(2).toULong();
-            if (kb==1)
-            {
-                qWarning("%s", KBMessages[kb - 1].toLocal8Bit().constData());
-                return;
-            }
-            if (kb && kb <= KBmsgsCount)
-            {
-                emit ErrorMessage(KBMessages[kb - 1]);
-            }
-            return;
-        }
         case 'i':
         {
             emit GameStats(msg.at(2), QString::fromUtf8(msg.mid(3)));
@@ -264,7 +249,6 @@
             int size = msg.size();
             QString msgbody = QString::fromUtf8(msg.mid(2).left(size - 4));
             emit SendChat(msgbody);
-            // FIXME: /me command doesn't work here
             QByteArray buf;
             HWProto::addStringToBuffer(buf, "s" + HWProto::formatChatMsg(config->netNick(), msgbody) + "\x20\x20");
             demo.append(buf);
@@ -283,8 +267,7 @@
             {
                 emit SendNet(msg);
             }
-            if (msg.at(1) != 's')
-                demo.append(msg);
+            demo.append(msg);
         }
     }
 }
--- a/QTfrontend/net/newnetclient.h	Fri Aug 17 10:39:23 2012 -0400
+++ b/QTfrontend/net/newnetclient.h	Sat Aug 18 00:48:09 2012 +0200
@@ -66,27 +66,6 @@
         bool m_game_connected;
         RoomsListModel * m_roomsListModel;
 
-        template <typename T>
-        void SendCfgStrNet(T a)
-        {
-            QByteArray strmsg;
-            strmsg.append(a);
-            quint8 sz = strmsg.size();
-            QByteArray enginemsg = QByteArray((char *)&sz, 1) + strmsg;
-            QString _msg = delimeter + QString(enginemsg.toBase64());
-            RawSendNet(_msg);
-        }
-
-        template <typename T>
-        void SendCfgStrLoc(T a)
-        {
-            QByteArray strmsg;
-            strmsg.append(QString(a).toUtf8());
-            quint8 sz = strmsg.size();
-            QByteArray enginemsg = QByteArray((char *)&sz, 1) + strmsg;
-            emit FromNet(enginemsg);
-        }
-
         QStringList cmdbuf;
 
         void RawSendNet(const QString & buf);
--- a/hedgewars/hwLibrary.pas	Fri Aug 17 10:39:23 2012 -0400
+++ b/hedgewars/hwLibrary.pas	Sat Aug 18 00:48:09 2012 +0200
@@ -99,14 +99,24 @@
     JNI_HW_versionInfoVersion := envderef^.NewStringUTF(env, PChar(cVersionString));
 end;
 
+procedure JNI_HW_GenLandPreview(env: PJNIEnv; c: JClass; port: JInt); cdecl;
+begin
+	GenLandPreview(port);
+end;
+
+procedure JNI_HW_Terminate(env: PJNIEnv; c: JClass); cdecl;
+begin
+	HW_terminate(false);
+end;
+
 exports
     JNI_HW_versionInfoNet name Java_Prefix+'HWversionInfoNetProto', 
     JNI_HW_versionInfoVersion name Java_Prefix+'HWversionInfoVersion', 
-    GenLandPreview name Java_Prefix + 'GenLandPreview',
+    JNI_HW_GenLandPreview name Java_Prefix + 'HWGenLandPreview',
     HW_getNumberOfweapons name Java_Prefix + 'HWgetNumberOfWeapons',
     HW_getMaxNumberOfHogs name Java_Prefix + 'HWgetMaxNumberOfHogs',
     HW_getMaxNumberOfTeams name Java_Prefix + 'HWgetMaxNumberOfTeams',
-    HW_terminate name Java_Prefix + 'HWterminate',
+    JNI_HW_Terminate name Java_Prefix + 'HWterminate',
     Game;
 {$ELSE}
 exports
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/Licenses/Android Support library/NOTICE.txt	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,195 @@
+Notice for all the files in this folder.
+------------------------------------------------------------
+
+
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/Licenses/ini4j/LICENSE.txt	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/Licenses/ini4j/NOTICE.txt	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,13 @@
+Copyright 2005,2009 Ivan SZKIBA
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -1,54 +1,106 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="org.hedgewars.hedgeroid"
-      android:versionCode="8"
-      android:installLocation="preferExternal" android:versionName="0.2">
-    <uses-sdk android:targetSdkVersion="14" android:minSdkVersion="7"></uses-sdk>
-    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
-    <application android:label="@string/app_name" android:icon="@drawable/icon">
-        <activity android:name=".MainActivity"
-                  android:label="@string/app_name"
- 		  		  android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+    package="org.hedgewars.hedgeroid"
+    android:installLocation="preferExternal"
+    android:versionCode="8"
+    android:versionName="0.2" >
+
+    <uses-sdk
+        android:minSdkVersion="7"
+        android:targetSdkVersion="14" >
+    </uses-sdk>
+
+    <uses-permission android:name="android.permission.INTERNET" >
+    </uses-permission>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
+    </uses-permission>
+
+    <application
+        android:icon="@drawable/icon"
+        android:label="@string/app_name" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:screenOrientation="landscape" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name=".SDLActivity"
-                  android:label="@string/app_name"
- 		  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-		  android:screenOrientation='landscape'>
+        <activity
+            android:name=".SDLActivity"
+            android:label="@string/app_name"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        </activity>
+        <activity
+            android:name=".Downloader.DownloadFragment"
+            android:label="@string/app_name"
+            android:theme="@android:style/Theme.Dialog" >
         </activity>
-        
-        <activity android:name=".Downloader.DownloadFragment"
-                  android:label="@string/app_name"
-				  android:theme="@android:style/Theme.Dialog">
+        <activity
+            android:name=".Downloader.DownloadListActivity"
+            android:label="@string/app_name"
+            android:launchMode="singleTop"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+
+        <service android:name=".Downloader.DownloadService" />
+		
+        <activity
+            android:name="StartGameActivity"
+            android:label="@string/app_name"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        </activity>
+        <activity
+            android:name="TeamSelectionActivity"
+            android:label="@string/app_name"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
         </activity>
-        
-        <activity android:name=".Downloader.DownloadListActivity"
-                  android:label="@string/app_name"
-				  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-				  android:screenOrientation='landscape'
-				  android:launchMode="singleTop"/>
-        
-        <service android:name=".Downloader.DownloadService"/>
-        
-        <activity android:name="StartGameActivity"
-                  android:label="@string/app_name"
-				  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-				  android:screenOrientation='landscape'>
+        <activity
+            android:name="TeamCreatorActivity"
+            android:label="@string/app_name"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+            android:windowSoftInputMode="stateUnchanged" >
+        </activity>
+        <activity
+            android:name=".LobbyActivity"
+            android:label="@string/title_activity_lobby"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
+        <activity
+            android:name=".RoomActivity"
+            android:label="@string/title_activity_room"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
         </activity>
-        <activity android:name="TeamSelectionActivity"
-                  android:label="@string/app_name"
-				  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-				  android:screenOrientation='landscape'>
+        <activity
+            android:name=".WeaponsetListActivity"
+            android:label="@string/title_activity_weaponset_list"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
+        <activity
+            android:name=".WeaponsetCreatorActivity"
+            android:label="@string/title_activity_weaponset_creator"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
         </activity>
-        <activity android:name="TeamCreatorActivity"
-                  android:label="@string/app_name"
-				  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-				  android:screenOrientation='landscape'
-				  android:windowSoftInputMode="stateUnchanged">
+        <activity
+            android:name=".SchemeListActivity"
+            android:label="@string/title_activity_scheme_list"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
+        <activity
+            android:name=".SchemeCreatorActivity"
+            android:label="@string/title_activity_scheme_creator"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
         </activity>
     </application>
-</manifest> 
+</manifest>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/assets/assetsversion.txt	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,1 @@
+7
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/jni/Android.mk	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/jni/Android.mk	Sat Aug 18 00:48:09 2012 +0200
@@ -6,5 +6,6 @@
 
 include $(CLEAR_VARS)
 include $(JNI_DIR)/../../../../misc/Android.mk
+include $(JNI_DIR)/../../../frontlib/Android.mk
 
 
Binary file project_files/Android-build/SDL-android-project/libs/android-support-v13.jar has changed
Binary file project_files/Android-build/SDL-android-project/libs/armeabi/libjnidispatch.so has changed
Binary file project_files/Android-build/SDL-android-project/libs/jna.jar has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-hdpi/button_local_play.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-hdpi/button_network_play.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-large-mdpi/button_local_play.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-large-mdpi/button_network_play.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/button.9.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,10 @@
+ <level-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:maxLevel="1" android:drawable="@drawable/hogcount1" />
+  <item android:maxLevel="2" android:drawable="@drawable/hogcount2" />
+  <item android:maxLevel="3" android:drawable="@drawable/hogcount3" />
+  <item android:maxLevel="4" android:drawable="@drawable/hogcount4" />
+  <item android:maxLevel="5" android:drawable="@drawable/hogcount5" />
+  <item android:maxLevel="6" android:drawable="@drawable/hogcount6" />
+  <item android:maxLevel="7" android:drawable="@drawable/hogcount7" />
+  <item android:maxLevel="8" android:drawable="@drawable/hogcount8" />
+ </level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount1.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount2.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount3.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount4.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount5.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount6.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount7.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount8.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/lightbulb_off.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/lightbulb_on.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/playerlist_player.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/roomlist_ingame.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/roomlist_preparing.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_local_by_level.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,8 @@
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:maxLevel="0" android:drawable="@drawable/human" />
+	<item android:maxLevel="1" android:drawable="@drawable/bot5" />
+	<item android:maxLevel="2" android:drawable="@drawable/bot4" />
+	<item android:maxLevel="3" android:drawable="@drawable/bot3" />
+	<item android:maxLevel="4" android:drawable="@drawable/bot2" />
+	<item android:maxLevel="5" android:drawable="@drawable/bot1" />
+</level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot1.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot2.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot3.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot4.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot5.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_by_level.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,8 @@
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:maxLevel="0" android:drawable="@drawable/team_net_human" />
+	<item android:maxLevel="1" android:drawable="@drawable/team_net_bot5" />
+	<item android:maxLevel="2" android:drawable="@drawable/team_net_bot4" />
+	<item android:maxLevel="3" android:drawable="@drawable/team_net_bot3" />
+	<item android:maxLevel="4" android:drawable="@drawable/team_net_bot2" />
+	<item android:maxLevel="5" android:drawable="@drawable/team_net_bot1" />
+</level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_human.png has changed
--- a/project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
- <level-list xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:maxLevel="0" android:drawable="@drawable/teams_number0" />
-  <item android:maxLevel="1" android:drawable="@drawable/teams_number1" />
-  <item android:maxLevel="2" android:drawable="@drawable/teams_number2" />
-  <item android:maxLevel="3" android:drawable="@drawable/teams_number3" />
-  <item android:maxLevel="4" android:drawable="@drawable/teams_number4" />
-  <item android:maxLevel="5" android:drawable="@drawable/teams_number5" />
-  <item android:maxLevel="6" android:drawable="@drawable/teams_number6" />
-  <item android:maxLevel="7" android:drawable="@drawable/teams_number7" />
-  <item android:maxLevel="8" android:drawable="@drawable/teams_number8" />
-  <item android:maxLevel="9" android:drawable="@drawable/teams_number9" />
- </level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount0.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount1.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount2.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount3.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount4.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount5.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount6.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount7.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount8.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount9.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/teams_number.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,12 @@
+ <level-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:maxLevel="0" android:drawable="@drawable/teams_number0" />
+  <item android:maxLevel="1" android:drawable="@drawable/teams_number1" />
+  <item android:maxLevel="2" android:drawable="@drawable/teams_number2" />
+  <item android:maxLevel="3" android:drawable="@drawable/teams_number3" />
+  <item android:maxLevel="4" android:drawable="@drawable/teams_number4" />
+  <item android:maxLevel="5" android:drawable="@drawable/teams_number5" />
+  <item android:maxLevel="6" android:drawable="@drawable/teams_number6" />
+  <item android:maxLevel="7" android:drawable="@drawable/teams_number7" />
+  <item android:maxLevel="8" android:drawable="@drawable/teams_number8" />
+  <item android:maxLevel="9" android:drawable="@drawable/teams_number9" />
+ </level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-small-hdpi/button_local_play.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-small-hdpi/button_network_play.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout-large/activity_lobby.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:tools="http://schemas.android.com/tools"
+  	android:layout_width="fill_parent"
+	android:layout_height="fill_parent">
+	<include layout="@layout/background"/>
+
+	<LinearLayout
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent"
+	    android:orientation="vertical"
+	    android:padding="5dp" >
+	
+	    <FrameLayout
+	        android:layout_width="fill_parent"
+	        android:layout_height="0dp"
+	        android:layout_marginBottom="10dp"
+	        android:layout_weight="0.4"
+	        android:background="@drawable/box" >
+	
+	        <fragment
+	            android:id="@+id/roomListFragment"
+	            android:layout_width="fill_parent"
+	            android:layout_height="fill_parent"
+	            class="org.hedgewars.hedgeroid.netplay.RoomlistFragment"
+	            tools:layout="@layout/lobby_rooms_fragment" />
+	    </FrameLayout>
+	
+	    <RelativeLayout
+	        android:layout_width="fill_parent"
+	        android:layout_height="0dp"
+	        android:layout_weight="0.6"
+	        android:baselineAligned="false"
+	        android:orientation="horizontal" >
+	
+	        <FrameLayout
+	            android:id="@+id/playerFrame"
+	            android:layout_width="250dp"
+	            android:layout_height="fill_parent"
+	            android:layout_alignParentRight="true"
+	            android:background="@drawable/box" >
+	
+	            <fragment
+	                android:id="@+id/playerListFragment"
+	                android:layout_width="fill_parent"
+	                android:layout_height="fill_parent"
+	                class="org.hedgewars.hedgeroid.netplay.LobbyPlayerlistFragment"
+	                tools:layout="@layout/fragment_playerlist" />
+	        </FrameLayout>
+	        
+	        <FrameLayout
+	            android:layout_width="0dp"
+	            android:layout_height="fill_parent"
+	            android:layout_alignParentLeft="true"
+	            android:layout_toLeftOf="@id/playerFrame"
+	            android:layout_marginRight="10dp"
+	            android:background="@drawable/box" >
+	
+	            <fragment
+	                android:id="@+id/chatFragment"
+	                android:layout_width="fill_parent"
+	                android:layout_height="fill_parent"
+	                class="org.hedgewars.hedgeroid.netplay.ChatFragment"
+	                tools:layout="@layout/fragment_chat" />
+	        </FrameLayout>
+	    </RelativeLayout>
+	
+	</LinearLayout>
+</FrameLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout-large/listview_room.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+	android:layout_width="fill_parent"
+	android:layout_height="wrap_content" 
+	android:paddingTop="4dp"
+	android:paddingBottom="4dp">
+	
+<TextView
+    android:id="@+id/roomname"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:layout_weight="1.5"
+    android:padding="3dp"
+    android:drawablePadding="5dp"
+    android:gravity="left|center_vertical"
+    android:singleLine="true"
+    android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+<include layout="@layout/roomlist_player_team_count" />
+
+<TextView
+    android:id="@+id/owner"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="left"
+	android:layout_gravity="center_vertical"
+	android:singleLine="true"/>
+
+<TextView
+    android:id="@+id/map"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="left"
+	android:layout_gravity="center_vertical"
+	android:singleLine="true"/>
+
+<TextView
+    android:id="@+id/scheme"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="left"
+	android:layout_gravity="center_vertical"
+	android:singleLine="true"/>
+
+<TextView
+    android:id="@+id/weapons"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="left"
+	android:layout_gravity="center_vertical"
+	android:singleLine="true"/>
+
+</LinearLayout>
+    
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout-large/lobby_rooms_fragment.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <FrameLayout
+        android:id="@+id/listHeader"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+	    <include layout="@layout/listview_room_header" />
+    </FrameLayout>
+
+    <ListView
+        android:id="@id/android:list"
+        android:layout_width="fill_parent"
+        android:layout_height="0dp"
+        android:layout_alignParentBottom="true"
+        android:layout_below="@+id/listHeader"
+        android:cacheColorHint="@android:color/transparent"
+        android:drawSelectorOnTop="false"
+        tools:listitem="@layout/listview_room" />
+
+    <TextView
+        android:id="@id/android:empty"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:text="@string/no_rooms_in_list" />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout-xlarge/roomlist_player_team_count.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+    
+	<TextView
+	    android:id="@+id/playercount"
+		android:layout_width="20dp"
+		android:layout_height="wrap_content"
+		android:padding="3dp"
+		android:gravity="center" 
+		android:layout_gravity="center_vertical"
+		android:singleLine="true"/>
+	
+	<TextView
+	    android:id="@+id/teamcount"
+		android:layout_width="20dp"
+		android:layout_height="wrap_content"
+		android:padding="3dp"
+		android:gravity="center"
+		android:layout_gravity="center_vertical"
+		android:singleLine="true"/>
+</merge>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout-xlarge/roomlist_player_team_count_header.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+    
+	<TextView
+	    android:id="@+id/playercount"
+		android:layout_width="20dp"
+		android:layout_height="wrap_content"
+		android:padding="3dp"
+		android:gravity="center" 
+		android:singleLine="true"
+		android:textAppearance="?android:attr/textAppearanceMedium"
+		android:text="@string/roomlist_header_clients"/>
+	
+	<TextView
+	    android:id="@+id/teamcount"
+		android:layout_width="20dp"
+		android:layout_height="wrap_content"
+		android:padding="3dp"
+		android:gravity="center"
+		android:singleLine="true"
+		android:textAppearance="?android:attr/textAppearanceMedium"
+		android:text="@string/roomlist_header_teams" />
+</merge>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_lobby.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+
+    <include layout="@layout/background" />
+
+    <TabHost
+        android:id="@android:id/tabhost"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal" >
+
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_weight="0" />
+
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="0dip"
+                android:layout_height="match_parent"
+                android:layout_weight="1" >
+
+                <fragment
+                    android:id="@+id/roomListFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.RoomlistFragment"
+                    tools:layout="@layout/lobby_rooms_fragment" />
+
+                <fragment
+                    android:id="@+id/chatFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.ChatFragment"
+                    tools:layout="@layout/fragment_chat" />
+
+                <fragment
+                    android:id="@+id/playerListFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.LobbyPlayerlistFragment"
+                    tools:layout="@layout/fragment_playerlist" />
+            </FrameLayout>
+        </LinearLayout>
+    </TabHost>
+
+</FrameLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_netroom.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+
+    <include layout="@layout/background" />
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:padding="5dp" >
+
+        <LinearLayout
+            android:id="@+id/upperFrame"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_marginBottom="10dp"
+            android:baselineAligned="false"
+            android:minHeight="200dp" >
+            
+            <FrameLayout
+                android:id="@+id/mapFrame"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="10dp"
+                android:layout_weight="1"
+                android:background="@drawable/box" >
+
+                <fragment
+                    android:id="@+id/mapFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.MapFragment"
+                    tools:layout="@layout/fragment_map" />
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/settingsFrame"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="10dp"
+                android:layout_weight="1"
+                android:background="@drawable/box" >
+
+                <fragment
+                    android:id="@+id/settingsFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.SettingsFragment"
+                    tools:layout="@layout/fragment_settings" />
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/teamsFrame"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:layout_weight="1"
+                android:background="@drawable/box" >
+
+                <fragment
+                    android:id="@+id/teamsFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.TeamlistFragment"
+                    tools:layout="@layout/fragment_teamlist" />
+            </FrameLayout>
+        </LinearLayout>
+
+        <FrameLayout
+            android:id="@+id/playerFrame"
+            android:layout_width="200dp"
+            android:layout_height="fill_parent"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/upperFrame"
+            android:background="@drawable/box" >
+
+            <fragment
+                android:id="@+id/playerListFragment"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                class="org.hedgewars.hedgeroid.netplay.RoomPlayerlistFragment"
+                tools:layout="@layout/fragment_playerlist" />
+        </FrameLayout>
+
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentLeft="true"
+            android:layout_below="@id/upperFrame"
+            android:layout_marginRight="10dp"
+            android:layout_toLeftOf="@id/playerFrame"
+            android:background="@drawable/box" >
+
+            <fragment
+                android:id="@+id/chatFragment"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                class="org.hedgewars.hedgeroid.netplay.ChatFragment"
+                tools:layout="@layout/fragment_chat" />
+        </FrameLayout>
+    </RelativeLayout>
+
+</FrameLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_schemelist.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <ListView 
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" 
+        android:cacheColorHint="@android:color/transparent" />
+
+	<Button
+        android:id="@+id/addButton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/schemelist_add_button_text"
+        android:background="@drawable/button" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_weaponsetlist.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/weaponsetlist_empty" />
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:cacheColorHint="@android:color/transparent" />
+
+    <Button
+        android:id="@+id/addButton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/button"
+        android:text="@string/weaponsetlist_add_button_text" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/fragment_chat.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <ListView 
+        android:id="@+id/chatConsole"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" 
+        android:clickable="false"
+        android:cacheColorHint="@android:color/transparent"
+        android:transcriptMode="normal"
+        android:focusableInTouchMode="false"
+        android:focusable="false"
+        android:longClickable="false"
+        android:stackFromBottom="true"
+        />
+
+	<EditText
+        android:id="@+id/chatInput"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/chat_hint"
+        android:imeOptions="actionSend"
+        android:inputType="text" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/fragment_map.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingBottom="3dp"
+    android:paddingLeft="5dp"
+    android:paddingRight="3dp"
+    android:paddingTop="3dp" >
+
+    <ImageView
+        android:id="@+id/mapPreview"
+        android:layout_width="256dip"
+        android:layout_height="128dip"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:layout_margin="5dip"
+        android:background="@drawable/box"
+        android:scaleType="fitCenter"
+        android:src="@drawable/roomlist_preparing" />
+
+    <TableLayout
+        android:id="@+id/gameOptions"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/mapPreview"
+        android:stretchColumns="1" >
+
+        <TableRow>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_gen" />
+
+            <Spinner
+                android:id="@+id/spinMapType"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+        </TableRow>
+
+        <TableRow android:id="@+id/rowMapName">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/map_name" />
+
+            <Spinner
+                android:id="@+id/spinMapName"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+        </TableRow>
+		<TableRow android:id="@+id/rowTemplateFilter">
+		    <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/map_template" />
+		    
+            <Spinner
+                android:id="@+id/spinTemplateFilter"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+		</TableRow>
+		<TableRow android:id="@+id/rowMazeSize">
+		    <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/map_maze_size" />
+		    
+            <Spinner
+                android:id="@+id/spinMazeSize"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+		</TableRow>
+    </TableLayout>
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/fragment_playerlist.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <ListView
+        android:id="@id/android:list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:drawSelectorOnTop="false"
+        android:cacheColorHint="@android:color/transparent"
+        tools:listitem="@layout/listview_player" />
+
+    <TextView
+        android:id="@id/android:empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/no_players_in_list" />
+</FrameLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/fragment_settings.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingBottom="3dp"
+    android:paddingLeft="5dp"
+    android:paddingRight="3dp"
+    android:paddingTop="3dp" >
+
+    <TableLayout
+        android:id="@+id/gameOptions"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:stretchColumns="1" >
+
+        <TableRow>
+
+            <TextView
+                android:id="@+id/txtGameplay"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/start_gameplay" />
+
+            <Spinner
+                android:id="@+id/spinGameplay"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+        </TableRow>
+
+        <TableRow>
+
+            <TextView
+                android:id="@+id/txtGamescheme"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/start_gamescheme" />
+
+            <Spinner
+                android:id="@+id/spinGamescheme"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+        </TableRow>
+
+        <TableRow>
+
+            <TextView
+                android:id="@+id/txtweapons"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/start_weapons" />
+
+            <Spinner
+                android:id="@+id/spinweapons"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+        </TableRow>
+    </TableLayout>
+
+    <ImageView
+        android:id="@+id/imgTheme"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@+id/spinTheme"
+        android:layout_alignLeft="@id/gameOptions"
+        android:layout_alignTop="@id/spinTheme"
+        android:adjustViewBounds="true" />
+
+    <Spinner
+        android:id="@id/spinTheme"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_below="@id/gameOptions"
+        android:layout_toRightOf="@+id/imgTheme"
+        android:background="@drawable/dropdown" />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/fragment_teamlist.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <ListView 
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" 
+        android:cacheColorHint="@android:color/transparent"
+        />
+
+	<Button
+        android:id="@+id/addTeamButton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/teamlist_addteam"
+        android:background="@drawable/button" />
+
+</LinearLayout>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/listview_item.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_item.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
 	android:layout_width="fill_parent"
 	android:layout_height="wrap_content"
 	android:textSize="10dip"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_player.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="4dp"
+	android:paddingBottom="4dp"
+    android:drawablePadding="5dp"
+    android:drawableLeft="@drawable/playerlist_player"
+    android:gravity="center_vertical|left"
+    android:singleLine="true"
+    android:textAppearance="?android:attr/textAppearanceMedium" />
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_room.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:mode="twoLine"
+    android:paddingTop="4dp"
+	android:paddingBottom="4dp">
+
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:drawablePadding="5dp"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <TextView
+        android:id="@android:id/text2"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignLeft="@android:id/text1"
+        android:layout_below="@android:id/text1"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</TwoLineListItem>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_room_header.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+	android:layout_width="fill_parent"
+	android:layout_height="wrap_content" >
+	
+<TextView
+    android:id="@+id/roomname"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:layout_weight="1.5"
+    android:padding="3dp"
+    android:gravity="center"
+    android:singleLine="true"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:text="@string/roomlist_header_roomname" />
+
+<include layout="@layout/roomlist_player_team_count_header" />
+
+<TextView
+    android:id="@+id/owner"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="center"
+	android:singleLine="true"
+	android:textAppearance="?android:attr/textAppearanceMedium"
+	android:text="@string/roomlist_header_owner" />
+
+<TextView
+    android:id="@+id/map"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="center"
+	android:singleLine="true"
+	android:textAppearance="?android:attr/textAppearanceMedium"
+	android:text="@string/roomlist_header_map" />
+
+<TextView
+    android:id="@+id/scheme"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="center"
+	android:singleLine="true"
+	android:textAppearance="?android:attr/textAppearanceMedium"
+	android:text="@string/roomlist_header_scheme" />
+
+<TextView
+    android:id="@+id/weapons"
+	android:layout_width="0dp"
+	android:layout_height="wrap_content"
+	android:layout_weight="0.5"
+	android:padding="3dp"
+	android:gravity="center"
+	android:singleLine="true"
+	android:textAppearance="?android:attr/textAppearanceMedium"
+	android:text="@string/roomlist_header_weapons" />
+
+</LinearLayout>
+    
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_team.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="4dp"
+	android:paddingBottom="4dp" >
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical|left"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:drawablePadding="5dp"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <ImageButton
+        android:id="@+id/colorButton"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_gravity="center_vertical|right"
+        android:src="#fff"
+        android:padding="8dp"
+        android:contentDescription="@string/teamlist_color_button_description" />
+    
+    <ImageButton
+        android:id="@+id/hogCountButton"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_gravity="center_vertical|right"
+        android:src="@drawable/hogcount"
+        android:scaleType="centerCrop"
+        android:padding="0dp"
+        android:contentDescription="@string/teamlist_hogcount_button_description" />
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/lobby_rooms_fragment.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingLeft="8dp"
+    android:paddingRight="8dp" >
+
+    <ListView
+        android:id="@id/android:list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:drawSelectorOnTop="false"
+        android:cacheColorHint="@android:color/transparent"
+        tools:listitem="@layout/listview_room" />
+
+    <TextView
+        android:id="@id/android:empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="@string/no_rooms_in_list" />
+
+</LinearLayout>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/main.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/layout/main.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -1,25 +1,54 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
-	xmlns:android="http://schemas.android.com/apk/res/android"
-  	android:layout_width="fill_parent"
-	android:layout_height="fill_parent">
-	<include layout="@layout/background"/>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    >    
-    <Button
-    	android:id="@+id/downloader"
-    	android:layout_width="wrap_content"
-    	android:layout_height="wrap_content"
-    	android:text="downloader"/>
-    	
-    <Button
-    	android:id="@+id/startGame"
-    	android:layout_width="wrap_content"
-    	android:layout_height="wrap_content"
-    	android:text="startgame"/>
-    	
-</LinearLayout>
+    android:layout_height="fill_parent" >
+
+    <include layout="@layout/background" />
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" >
+
+        <View
+            android:id="@+id/placeholder"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_centerInParent="true" />
+
+        <FrameLayout
+            android:id="@+id/frameLayout1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentTop="true"
+            android:layout_toLeftOf="@id/placeholder" >
+
+            <Button
+                android:id="@+id/startGame"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:drawableTop="@drawable/button_local_play"
+                android:text="@string/main_button_localplay" />
+        </FrameLayout>
+
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/placeholder" >
+
+            <Button
+                android:id="@+id/joinLobby"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:drawableTop="@drawable/button_network_play"
+                android:text="@string/main_button_netplay" />
+        </FrameLayout>
+    </RelativeLayout>
+
 </FrameLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/roomlist_player_team_count.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+</merge>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/roomlist_player_team_count_header.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+</merge>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/starting_game.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/layout/starting_game.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -147,7 +147,7 @@
     	android:adjustViewBounds="true"
     	android:scaleType="centerInside"
     	android:background="@android:color/transparent"
-    	android:src="@drawable/teamcount"/>
+    	android:src="@drawable/teams_number"/>
         
     </LinearLayout>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/tab_indicator.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,23 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="0dp"
+    android:layout_weight="1"
+    android:orientation="vertical"
+    android:background="@drawable/box">
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+    />
+
+    <TextView android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:scrollHorizontally="false"
+        android:padding="4dp"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        style="?android:attr/tabWidgetStyle"
+    />
+</RelativeLayout>
--- a/project_files/Android-build/SDL-android-project/res/layout/team_creation.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/layout/team_creation.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -15,8 +15,6 @@
 	  	android:layout_width="fill_parent"
 	  	android:layout_height="fill_parent"
 	  	android:layout_weight="1">
-	  	<include layout="@layout/backbutton"/>
-	  	<include layout="@layout/savebutton"/>
 	  	<ScrollView
 		  	android:layout_width="fill_parent"
 		  	android:layout_height="fill_parent"
--- a/project_files/Android-build/SDL-android-project/res/layout/team_selection_entry.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/layout/team_selection_entry.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -31,7 +31,7 @@
   	android:layout_alignBottom="@id/imgDifficulty"
   	android:adjustViewBounds="true"
   	android:scaleType="centerInside"
-  	android:src="@drawable/teamcount7"/>
+  	android:src="@drawable/hogcount"/>
   <TextView
   	android:id="@+id/txtName"
   	android:layout_height="fill_parent"
--- a/project_files/Android-build/SDL-android-project/res/layout/team_selector.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/layout/team_selector.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -5,8 +5,6 @@
   android:layout_height="fill_parent">
   
   <include layout="@layout/background"/>
- 
-  <include layout="@layout/backbutton"/>
 
   <ImageButton
    	android:id="@+id/btnAdd"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/lobby_options.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/room_create"
+        android:title="@string/lobby_roomlistmenu_create"
+        android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/disconnect"
+        android:title="@string/lobby_menu_disconnect"
+        android:showAsAction="ifRoom" />
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/lobby_playerlist_context.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/player_info"
+        android:title="@string/lobby_playerlist_contextmenu_info">
+    </item>
+    <item
+        android:id="@+id/player_follow"
+        android:title="@string/lobby_playerlist_contextmenu_follow">
+    </item>
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/main_options.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,16 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/download"
+        android:title="@string/main_menu_downloader"
+        android:icon="@android:drawable/ic_menu_save"
+        android:showAsAction="ifRoom|withText" />
+    <item
+        android:id="@+id/preferences"
+        android:title="@string/main_menu_preferences"
+        android:icon="@android:drawable/ic_menu_preferences"
+        android:showAsAction="ifRoom|withText" />
+    <item
+        android:id="@+id/edit_weaponsets"
+        android:title="@string/edit_weaponsets_menu"
+        android:showAsAction="ifRoom|withText" />
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/room_playerlist_chief_context.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,6 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/player_kick"
+        android:title="@string/playerlist_contextmenu_kick">
+    </item>
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/room_playerlist_context.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,6 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/player_info"
+        android:title="@string/lobby_playerlist_contextmenu_info">
+    </item>
+</menu>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/basicflags.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,390 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<basicflags>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$damagepct</string>
-        </command>
-        <default>
-            <integer>100</integer>
-        </default>
-        <image>
-            <string>Damage</string>
-        </image>
-        <max>
-            <integer>300</integer>
-        </max>
-        <min>
-            <integer>10</integer>
-        </min>
-        <title>
-            <string>Damage Modifier</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>true</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>true</boolean>
-        </times1000>
-        <command>
-            <string>e$turntime</string>
-        </command>
-        <default>
-            <integer>45</integer>
-        </default>
-        <image>
-            <string>Time</string>
-        </image>
-        <max>
-            <integer>100</integer>
-        </max>
-        <min>
-            <integer>1</integer>
-        </min>
-        <title>
-            <string>Turn Time</string>
-        </title>
-    </flag>
-    <flag>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>inithealth</string>
-        </command>
-        <default>
-            <integer>200</integer>
-        </default>
-        <image>
-            <string>Health</string>
-        </image>
-        <max>
-            <integer>200</integer>
-        </max>
-        <min>
-            <integer>50</integer>
-        </min>
-        <title>
-            <string>Initial Health</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>true</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$sd_turns</string>
-        </command>
-        <default>
-            <integer>15</integer>
-        </default>
-        <image>
-            <string>SuddenDeath</string>
-        </image>
-        <max>
-            <integer>50</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Sudden Death Timeout</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$casefreq</string>
-        </command>
-        <default>
-            <integer>5</integer>
-        </default>
-        <image>
-            <string>Box</string>
-        </image>
-        <max>
-            <integer>9</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Crate Drop Turns</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>true</boolean>
-        </times1000>
-        <command>
-            <string>e$minestime</string>
-        </command>
-        <default>
-            <integer>3</integer>
-        </default>
-        <image>
-            <string>Time</string>
-        </image>
-        <max>
-            <integer>5</integer>
-        </max>
-        <min>
-            <integer>-1</integer>
-        </min>
-        <title>
-            <string>Mines Time</string>
-        </title>
-    </flag>
-   <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$minesnum</string>
-        </command>
-        <default>
-            <integer>4</integer>
-        </default>
-        <image>
-            <string>Mine</string>
-        </image>
-        <max>
-            <integer>80</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Mines Number</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$minedudpct</string>
-        </command>
-        <default>
-            <integer>0</integer>
-        </default>
-        <image>
-            <string>Dud</string>
-        </image>
-        <max>
-            <integer>100</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Dud Mines Probability (%)</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$explosives</string>
-        </command>
-        <default>
-            <integer>2</integer>
-        </default>
-        <image>
-            <string>Damage</string>
-        </image>
-        <max>
-            <integer>40</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Explosives</string>
-        </title>
-    </flag>
-        <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$healthprob</string>
-        </command>
-        <default>
-            <integer>35</integer>
-        </default>
-        <image>
-            <string>Health</string>
-        </image>
-        <max>
-            <integer>100</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Health Kit Probability (%)</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$hcaseamount</string>
-        </command>
-        <default>
-            <integer>25</integer>
-        </default>
-        <image>
-            <string>Health</string>
-        </image>
-        <max>
-            <integer>200</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Health Amount in Kit</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$waterrise</string>
-        </command>
-        <default>
-            <integer>47</integer>
-        </default>
-        <image>
-            <string>SuddenDeath</string>
-        </image>
-        <max>
-            <integer>100</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Water Rise Amount</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$healthdec</string>
-        </command>
-        <default>
-            <integer>5</integer>
-        </default>
-        <image>
-            <string>SuddenDeath</string>
-        </image>
-        <max>
-            <integer>100</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Health Decrease</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$ropepct</string>
-        </command>
-        <default>
-            <integer>100</integer>
-        </default>
-        <image>
-            <string>Rope</string>
-        </image>
-        <max>
-            <integer>999</integer>
-        </max>
-        <min>
-            <integer>25</integer>
-        </min>
-        <title>
-            <string>Rope Length (%)</string>
-        </title>
-    </flag>
-    <flag>
-        <checkOverMax>
-            <boolean>false</boolean>
-        </checkOverMax>
-        <times1000>
-            <boolean>false</boolean>
-        </times1000>
-        <command>
-            <string>e$getawaytime</string>
-        </command>
-        <default>
-            <integer>100</integer>
-        </default>
-        <image>
-            <string>Time</string>
-        </image>
-        <max>
-            <integer>999</integer>
-        </max>
-        <min>
-            <integer>0</integer>
-        </min>
-        <title>
-            <string>Get Away Time (%)</string>
-        </title>
-    </flag>
-</basicflags>
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_barrelmayhem.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Barrel Mayhem</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>30</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>80</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_cleanslate.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Clean Slate</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>45</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>5</integer>
-		<integer>3</integer>
-		<integer>4</integer>
-		<integer>0</integer>
-		<integer>2</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_default_scheme.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Default</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>45</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>5</integer>
-		<integer>3</integer>
-		<integer>4</integer>
-		<integer>0</integer>
-		<integer>2</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_fortmode.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Fort Mode</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>45</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>5</integer>
-		<integer>3</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_kingmode.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>King Mode</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>45</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>5</integer>
-		<integer>3</integer>
-		<integer>4</integer>
-		<integer>0</integer>
-		<integer>2</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_minefield.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Minefield</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>30</integer>
-		<integer>50</integer>
-		<integer>15</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>80</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-        <integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_promode.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Pro Mode</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>0</integer>
-		<integer>3</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>2</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_shoppa.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Shoppa</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>30</integer>
-		<integer>100</integer>
-		<integer>50</integer>
-		<integer>1</integer>
-		<integer>3</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_thinkingwithportals.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Thinking with Portals</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>45</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>2</integer>
-		<integer>3</integer>
-		<integer>5</integer>
-		<integer>0</integer>
-		<integer>5</integer>
-		<integer>25</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_timeless.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Timeless</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>9999</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>5</integer>
-		<integer>3</integer>
-		<integer>5</integer>
-		<integer>10</integer>
-		<integer>2</integer>
-		<integer>35</integer>
-		<integer>30</integer>
-		<integer>0</integer>
-		<integer>0</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/scheme_tunnelhogs.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scheme>
-	<name>Tunnelhogs</name>
-	<basicflags>
-		<integer>100</integer>
-		<integer>30</integer>
-		<integer>100</integer>
-		<integer>15</integer>
-		<integer>5</integer>
-		<integer>3</integer>
-		<integer>10</integer>
-		<integer>10</integer>
-		<integer>10</integer>
-		<integer>35</integer>
-		<integer>25</integer>
-		<integer>47</integer>
-		<integer>5</integer>
-		<integer>100</integer>
-		<integer>100</integer>
-	</basicflags>
-	<gamemod>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<true/>
-		<false/>
-		<false/>
-		<true/>
-		<true/>
-		<true/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-		<false/>
-	</gamemod>
-</scheme>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/raw/schemes_builtin.ini	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,456 @@
+
+[schemes]
+size                           = 11
+1\name                         = Default
+1\fortsmode                    = false
+1\divteams                     = false
+1\solidland                    = false
+1\border                       = false
+1\lowgrav                      = false
+1\laser                        = false
+1\invulnerability              = false
+1\resethealth                  = false
+1\vampiric                     = false
+1\karma                        = false
+1\artillery                    = false
+1\randomorder                  = false
+1\king                         = false
+1\placehog                     = false
+1\sharedammo                   = false
+1\disablegirders               = false
+1\disablelandobjects           = false
+1\aisurvival                   = false
+1\infattack                    = false
+1\resetweps                    = false
+1\perhogammo                   = false
+1\disablewind                  = false
+1\morewind                     = false
+1\tagteam                      = false
+1\bottomborder                 = false
+1\damagefactor                 = 100
+1\turntime                     = 45
+1\health                       = 100
+1\suddendeath                  = 15
+1\caseprobability              = 5
+1\minestime                    = 3
+1\minesnum                     = 4
+1\minedudpct                   = 0
+1\explosives                   = 2
+1\healthprobability            = 35
+1\healthcaseamount             = 25
+1\waterrise                    = 47
+1\healthdecrease               = 5
+1\ropepct                      = 100
+1\getawaytime                  = 100
+2\name                         = Pro Mode
+2\fortsmode                    = false
+2\divteams                     = false
+2\solidland                    = false
+2\border                       = false
+2\lowgrav                      = false
+2\laser                        = false
+2\invulnerability              = false
+2\resethealth                  = false
+2\vampiric                     = false
+2\karma                        = false
+2\artillery                    = false
+2\randomorder                  = true
+2\king                         = false
+2\placehog                     = false
+2\sharedammo                   = false
+2\disablegirders               = false
+2\disablelandobjects           = false
+2\aisurvival                   = false
+2\infattack                    = false
+2\resetweps                    = false
+2\perhogammo                   = false
+2\disablewind                  = false
+2\morewind                     = false
+2\tagteam                      = false
+2\bottomborder                 = false
+2\damagefactor                 = 100
+2\turntime                     = 45
+2\health                       = 100
+2\suddendeath                  = 15
+2\caseprobability              = 5
+2\minestime                    = 3
+2\minesnum                     = 4
+2\minedudpct                   = 0
+2\explosives                   = 2
+2\healthprobability            = 35
+2\healthcaseamount             = 25
+2\waterrise                    = 47
+2\healthdecrease               = 5
+2\ropepct                      = 100
+2\getawaytime                  = 100
+3\name                         = Shoppa
+3\fortsmode                    = false
+3\divteams                     = false
+3\solidland                    = false
+3\border                       = false
+3\lowgrav                      = false
+3\laser                        = false
+3\invulnerability              = false
+3\resethealth                  = false
+3\vampiric                     = false
+3\karma                        = false
+3\artillery                    = false
+3\randomorder                  = true
+3\king                         = false
+3\placehog                     = false
+3\sharedammo                   = true
+3\disablegirders               = false
+3\disablelandobjects           = false
+3\aisurvival                   = false
+3\infattack                    = false
+3\resetweps                    = false
+3\perhogammo                   = false
+3\disablewind                  = false
+3\morewind                     = false
+3\tagteam                      = false
+3\bottomborder                 = false
+3\damagefactor                 = 100
+3\turntime                     = 15
+3\health                       = 100
+3\suddendeath                  = 15
+3\caseprobability              = 0
+3\minestime                    = 3
+3\minesnum                     = 0
+3\minedudpct                   = 0
+3\explosives                   = 2
+3\healthprobability            = 35
+3\healthcaseamount             = 25
+3\waterrise                    = 47
+3\healthdecrease               = 5
+3\ropepct                      = 100
+3\getawaytime                  = 100
+4\name                         = Clean Slate
+4\fortsmode                    = false
+4\divteams                     = false
+4\solidland                    = true
+4\border                       = true
+4\lowgrav                      = false
+4\laser                        = false
+4\invulnerability              = false
+4\resethealth                  = false
+4\vampiric                     = false
+4\karma                        = false
+4\artillery                    = false
+4\randomorder                  = true
+4\king                         = false
+4\placehog                     = false
+4\sharedammo                   = true
+4\disablegirders               = true
+4\disablelandobjects           = false
+4\aisurvival                   = false
+4\infattack                    = false
+4\resetweps                    = true
+4\perhogammo                   = false
+4\disablewind                  = false
+4\morewind                     = false
+4\tagteam                      = false
+4\bottomborder                 = false
+4\damagefactor                 = 100
+4\turntime                     = 30
+4\health                       = 100
+4\suddendeath                  = 50
+4\caseprobability              = 1
+4\minestime                    = 3
+4\minesnum                     = 0
+4\minedudpct                   = 0
+4\explosives                   = 0
+4\healthprobability            = 0
+4\healthcaseamount             = 25
+4\waterrise                    = 47
+4\healthdecrease               = 5
+4\ropepct                      = 100
+4\getawaytime                  = 100
+5\name                         = Minefield
+5\fortsmode                    = false
+5\divteams                     = false
+5\solidland                    = false
+5\border                       = false
+5\lowgrav                      = false
+5\laser                        = false
+5\invulnerability              = false
+5\resethealth                  = true
+5\vampiric                     = false
+5\karma                        = false
+5\artillery                    = false
+5\randomorder                  = true
+5\king                         = false
+5\placehog                     = false
+5\sharedammo                   = false
+5\disablegirders               = false
+5\disablelandobjects           = false
+5\aisurvival                   = false
+5\infattack                    = true
+5\resetweps                    = true
+5\perhogammo                   = false
+5\disablewind                  = false
+5\morewind                     = false
+5\tagteam                      = false
+5\bottomborder                 = false
+5\damagefactor                 = 100
+5\turntime                     = 45
+5\health                       = 100
+5\suddendeath                  = 15
+5\caseprobability              = 5
+5\minestime                    = 3
+5\minesnum                     = 4
+5\minedudpct                   = 0
+5\explosives                   = 2
+5\healthprobability            = 35
+5\healthcaseamount             = 25
+5\waterrise                    = 47
+5\healthdecrease               = 5
+5\ropepct                      = 100
+5\getawaytime                  = 100
+6\name                         = Barrel Mayhem
+6\fortsmode                    = false
+6\divteams                     = false
+6\solidland                    = false
+6\border                       = false
+6\lowgrav                      = false
+6\laser                        = false
+6\invulnerability              = false
+6\resethealth                  = false
+6\vampiric                     = false
+6\karma                        = false
+6\artillery                    = false
+6\randomorder                  = true
+6\king                         = false
+6\placehog                     = false
+6\sharedammo                   = true
+6\disablegirders               = true
+6\disablelandobjects           = false
+6\aisurvival                   = false
+6\infattack                    = false
+6\resetweps                    = false
+6\perhogammo                   = false
+6\disablewind                  = false
+6\morewind                     = false
+6\tagteam                      = false
+6\bottomborder                 = false
+6\damagefactor                 = 100
+6\turntime                     = 30
+6\health                       = 50
+6\suddendeath                  = 15
+6\caseprobability              = 0
+6\minestime                    = 0
+6\minesnum                     = 80
+6\minedudpct                   = 0
+6\explosives                   = 0
+6\healthprobability            = 35
+6\healthcaseamount             = 25
+6\waterrise                    = 47
+6\healthdecrease               = 5
+6\ropepct                      = 100
+6\getawaytime                  = 100
+7\name                         = Tunnel Hogs
+7\fortsmode                    = false
+7\divteams                     = false
+7\solidland                    = false
+7\border                       = false
+7\lowgrav                      = false
+7\laser                        = false
+7\invulnerability              = false
+7\resethealth                  = false
+7\vampiric                     = false
+7\karma                        = false
+7\artillery                    = false
+7\randomorder                  = true
+7\king                         = false
+7\placehog                     = false
+7\sharedammo                   = true
+7\disablegirders               = false
+7\disablelandobjects           = false
+7\aisurvival                   = false
+7\infattack                    = false
+7\resetweps                    = false
+7\perhogammo                   = false
+7\disablewind                  = false
+7\morewind                     = false
+7\tagteam                      = false
+7\bottomborder                 = false
+7\damagefactor                 = 100
+7\turntime                     = 30
+7\health                       = 100
+7\suddendeath                  = 15
+7\caseprobability              = 0
+7\minestime                    = 0
+7\minesnum                     = 0
+7\minedudpct                   = 0
+7\explosives                   = 80
+7\healthprobability            = 35
+7\healthcaseamount             = 25
+7\waterrise                    = 47
+7\healthdecrease               = 5
+7\ropepct                      = 100
+7\getawaytime                  = 100
+8\name                         = Fort Mode
+8\fortsmode                    = false
+8\divteams                     = false
+8\solidland                    = false
+8\border                       = true
+8\lowgrav                      = false
+8\laser                        = false
+8\invulnerability              = false
+8\resethealth                  = false
+8\vampiric                     = false
+8\karma                        = false
+8\artillery                    = false
+8\randomorder                  = true
+8\king                         = false
+8\placehog                     = false
+8\sharedammo                   = true
+8\disablegirders               = true
+8\disablelandobjects           = true
+8\aisurvival                   = false
+8\infattack                    = false
+8\resetweps                    = false
+8\perhogammo                   = false
+8\disablewind                  = false
+8\morewind                     = false
+8\tagteam                      = false
+8\bottomborder                 = false
+8\damagefactor                 = 100
+8\turntime                     = 30
+8\health                       = 100
+8\suddendeath                  = 15
+8\caseprobability              = 5
+8\minestime                    = 3
+8\minesnum                     = 10
+8\minedudpct                   = 10
+8\explosives                   = 10
+8\healthprobability            = 35
+8\healthcaseamount             = 25
+8\waterrise                    = 47
+8\healthdecrease               = 5
+8\ropepct                      = 100
+8\getawaytime                  = 100
+9\name                         = Timeless
+9\fortsmode                    = true
+9\divteams                     = true
+9\solidland                    = false
+9\border                       = false
+9\lowgrav                      = true
+9\laser                        = false
+9\invulnerability              = false
+9\resethealth                  = false
+9\vampiric                     = false
+9\karma                        = false
+9\artillery                    = false
+9\randomorder                  = true
+9\king                         = false
+9\placehog                     = false
+9\sharedammo                   = false
+9\disablegirders               = false
+9\disablelandobjects           = false
+9\aisurvival                   = false
+9\infattack                    = false
+9\resetweps                    = false
+9\perhogammo                   = false
+9\disablewind                  = false
+9\morewind                     = false
+9\tagteam                      = false
+9\bottomborder                 = false
+9\damagefactor                 = 100
+9\turntime                     = 45
+9\health                       = 100
+9\suddendeath                  = 15
+9\caseprobability              = 5
+9\minestime                    = 3
+9\minesnum                     = 0
+9\minedudpct                   = 0
+9\explosives                   = 0
+9\healthprobability            = 35
+9\healthcaseamount             = 25
+9\waterrise                    = 47
+9\healthdecrease               = 5
+9\ropepct                      = 100
+9\getawaytime                  = 100
+10\name                        = Thinking with Portals
+10\fortsmode                   = false
+10\divteams                    = false
+10\solidland                   = false
+10\border                      = false
+10\lowgrav                     = false
+10\laser                       = false
+10\invulnerability             = false
+10\resethealth                 = false
+10\vampiric                    = false
+10\karma                       = false
+10\artillery                   = false
+10\randomorder                 = true
+10\king                        = false
+10\placehog                    = false
+10\sharedammo                  = false
+10\disablegirders              = false
+10\disablelandobjects          = false
+10\aisurvival                  = false
+10\infattack                   = false
+10\resetweps                   = false
+10\perhogammo                  = true
+10\disablewind                 = false
+10\morewind                    = false
+10\tagteam                     = false
+10\bottomborder                = false
+10\damagefactor                = 100
+10\turntime                    = 9999
+10\health                      = 100
+10\suddendeath                 = 15
+10\caseprobability             = 5
+10\minestime                   = 3
+10\minesnum                    = 5
+10\minedudpct                  = 10
+10\explosives                  = 2
+10\healthprobability           = 35
+10\healthcaseamount            = 30
+10\waterrise                   = 0
+10\healthdecrease              = 0
+10\ropepct                     = 100
+10\getawaytime                 = 100
+11\name                        = King Mode
+11\fortsmode                   = false
+11\divteams                    = false
+11\solidland                   = false
+11\border                      = false
+11\lowgrav                     = false
+11\laser                       = false
+11\invulnerability             = false
+11\resethealth                 = false
+11\vampiric                    = false
+11\karma                       = false
+11\artillery                   = true
+11\randomorder                 = true
+11\king                        = false
+11\placehog                    = false
+11\sharedammo                  = false
+11\disablegirders              = false
+11\disablelandobjects          = false
+11\aisurvival                  = false
+11\infattack                   = false
+11\resetweps                   = false
+11\perhogammo                  = false
+11\disablewind                 = false
+11\morewind                    = false
+11\tagteam                     = false
+11\bottomborder                = false
+11\damagefactor                = 100
+11\turntime                    = 45
+11\health                      = 100
+11\suddendeath                 = 15
+11\caseprobability             = 2
+11\minestime                   = 3
+11\minesnum                    = 5
+11\minedudpct                  = 0
+11\explosives                  = 5
+11\healthprobability           = 25
+11\healthcaseamount            = 25
+11\waterrise                   = 47
+11\healthdecrease              = 5
+11\ropepct                     = 100
+11\getawaytime                 = 100
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/raw/team_one.hwt	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,74 @@
+[Team]
+Name=Team 1
+Grave=Bone
+Fort=Lego
+Voicepack=Classic
+Flag=hedgewars
+Difficulty=0
+Rounds=0
+Wins=0
+CampaignProgress=0
+
+[Hedgehog0]
+Name=Leonidas
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog1]
+Name=Pipo
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog2]
+Name=Sonic
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog3]
+Name=Xin
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog4]
+Name=Arnold
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog5]
+Name=Jack
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog6]
+Name=Tom
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog7]
+Name=Goldie
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
--- a/project_files/Android-build/SDL-android-project/res/raw/team_one.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
-<team>
-  <name>Team 1</name>
-  <flag>hedgewars</flag>
-  <fort>Lego</fort>
-  <grave>Bone</grave>
-  <voice>Classic</voice>
-  <hash>0</hash>
-  <hog>
-    <name>Leonidas</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Pipo</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Sonic</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Xin</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Arnold</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Jack</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Tom</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-  <hog>
-    <name>Goldie</name>
-    <hat>NoHat</hat>
-    <level>0</level>
-  </hog>
-</team>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/raw/team_two.hwt	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,74 @@
+[Team]
+Name=Team 2
+Grave=Bone
+Fort=Lego
+Voicepack=Classic
+Flag=cm_binary
+Difficulty=2
+Rounds=0
+Wins=0
+CampaignProgress=0
+
+[Hedgehog0]
+Name=Paris
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog1]
+Name=Knut
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog2]
+Name=Ash
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog3]
+Name=Woad
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog4]
+Name=Bob
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog5]
+Name=Corky
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog6]
+Name=Bea
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
+
+[Hedgehog7]
+Name=Silvia
+Hat=NoHat
+Rounds=0
+Kills=0
+Deaths=0
+Suicides=0
--- a/project_files/Android-build/SDL-android-project/res/raw/team_two.xml	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
-<team>
-  <name>Team 2</name>
-  <flag>cm_binary</flag>
-  <fort>Lego</fort>
-  <grave>Bone</grave>
-  <voice>Classic</voice>
-  <hash>0</hash>
-  <hog>
-    <name>Paris</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Knut</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Ash</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Woad</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Bob</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Corky</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Bea</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-  <hog>
-    <name>Silvia</name>
-    <hat>NoHat</hat>
-    <level>2</level>
-  </hog>
-</team>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_clean	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Clean</name>
-    <QT>
-    101000900001000001100000000000000000000000000000100000
-    </QT>
-    <probability>
-    040504054160065554655446477657666666615551010111541111
-    </probability>
-    <delay>
-    000000000000000000000000000000000000000000000000000000
-    </delay>
-    <crate>
-    131111031211111112311411111111111111121111110111111111
-    </crate>
-</weapon>
-
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_crazy	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Crazy</name>
-    <QT>
-    999999999999999999299999999999999929999999990999999229
-    </QT>
-    <probability>
-    111111011111111111111111111111111111111111110111111111
-    </probability>
-    <delay>
-    000000000000000000000000000000000000000000000000000000
-    </delay>
-    <crate>
-    131111031211111112311411111111111111121111010111111111
-    </crate>
-</weapon>
-
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_default	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Default</name>
-    <QT>
-    939192942219912103223511100120100000021111010101111991
-    </QT>
-    <probability>
-    040504054160065554655446477657666666615551010111541111
-    </probability>
-    <delay>
-    000000000000020550000004000700400000000022000000060000
-    </delay>
-    <crate>
-    131111031211111112311411111111111111121111110111111111
-    </crate>
-</weapon>
-
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_mines	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Mines</name>
-    <QT>
-    000000990009000000030000000000000000000000000000000000
-    </QT>
-    <probability>
-    000000000000000000000000000000000000000000000000000000
-    </probability>
-    <delay>
-    000000000000020550000004000700400000000020000000060000
-    </delay>
-    <crate>
-    111111111111111111111111111111111111111111110111111111
-    </crate>
-</weapon>
-
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_portals	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Portals</name>
-    <QT>
-    900000900200000000210000000000000011000009000000000000
-    </QT>
-    <probability>
-    040504054160065554655446477657666666615551010111541111
-    </probability>
-    <delay>
-    000000000000020550000004000700400000000020000000060000
-    </delay>
-    <crate>
-    131111031211111112311411111111111111121111110111111111
-    </crate>
-</weapon>
-
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_promode	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Pro Mode</name>
-    <QT>
-    909000900000000000000900000000000000000000000000000000
-    </QT>
-    <probability>
-    000000000000000000000000000000000000000000000000000000
-    </probability>
-    <delay>
-    000000000000020550000004000700400000000020000000000000
-    </delay>
-    <crate>
-    111111111111111111111111111111111111111110010111111111
-    </crate>
-</weapon>
-
--- a/project_files/Android-build/SDL-android-project/res/raw/weapon_shoppa	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<weapon>
-    <name>Shoppa</name>
-    <QT>
-    000000990000000000000000000000000000000000000000000000
-    </QT>
-    <probability>
-    444441004424440221011212122242200000000200040001001111
-    </probability>
-    <delay>
-    000000000000000000000000000000000000000000000000000000
-    </delay>
-    <crate>
-    111111111111111111111111111111111111111110110111111111
-    </crate>
-</weapon>
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/raw/weapons_builtin.ini	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,8 @@
+[General]
+%44efault=9391929422199121032235111001201000000211110101011111011040504054160065554655446477657666666615551010111541101100000000000002055000000400070040000000002200000006000001311110312111111123114111111111111111211111101111111010
+%43razy=9999999999999999992999999999999999299999999909999992099111111011111111111111111111111111111111111110111111101100000000000000000000000000000000000000000000000000000001311110312111111123114111111111111111211110101111111011
+%50ro%20%4dode=9090009000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002055000000400070040000000002000000000000001111111111111111111111111111111111111111100101111111011
+%53hoppa=0000009900000000000000000000000000000000000000000000000444441004424440221011212122242200000000200040001001100000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111101101111111001
+%43lean%20%53late=1010009000010000011000000000000000000000000000001000000040504054160065554655446477657666666615551010111541101100000000000000000000000000000000000000000000000000000001311110312111111123114111111111111111211111101111111011
+%4dinefield=0000009900090000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002055000000400070040000000002000000006000001111111111111111111111111111111111111111111101111111011
+%54hinking%20with%20%50ortals=9000009002000000002100000000000000110000090000000000000040504054160065554655446477657666666615551010111541101100000000000002055000000400070040000000002000000006000001311110312111111123114111111111111111211111101111111011
--- a/project_files/Android-build/SDL-android-project/res/values/frontend_data_pointers.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/values/frontend_data_pointers.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -1,34 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-
-<array name="schemes">
-	<item>@raw/basicflags</item>
-	<item>@raw/scheme_default_scheme</item>
-	<item>@raw/scheme_barrelmayhem</item>
-	<item>@raw/scheme_cleanslate</item>
-	<item>@raw/scheme_fortmode</item>
-	<item>@raw/scheme_kingmode</item>
-	<item>@raw/scheme_minefield</item>
-	<item>@raw/scheme_promode</item>
-	<item>@raw/scheme_shoppa</item>
-	<item>@raw/scheme_thinkingwithportals</item>
-	<item>@raw/scheme_timeless</item>
-	<item>@raw/scheme_tunnelhogs</item>
-</array>
-
-<array name="weapons">
-    <item>@raw/weapon_default</item>
-    <item>@raw/weapon_clean</item>
-    <item>@raw/weapon_crazy</item>
-    <item>@raw/weapon_mines</item>
-    <item>@raw/weapon_portals</item>
-    <item>@raw/weapon_promode</item>
-    <item>@raw/weapon_shoppa</item>
-</array>
-
 <array name="teams">
 	<item>@raw/team_one</item>
 	<item>@raw/team_two</item>
-
+</array>
+<array name="teamFilenames">
+	<item>Team 1.hwt</item>
+	<item>Team 2.hwt</item>
 </array>
 </resources>
--- a/project_files/Android-build/SDL-android-project/res/values/strings.xml	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/res/values/strings.xml	Sat Aug 18 00:48:09 2012 +0200
@@ -1,20 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+
     <string name="app_name">Hedgewars</string>
-    
     <string name="select">Select</string>
     <string name="edit">Edit</string>
     <string name="delete">Delete</string>
     <string name="saved">Saved succesfully</string>
-    
+
     <!-- SDCARD -->
     <string name="sdcard_not_mounted_title">Sorry! Could not find the SDCard</string>
     <string name="sdcard_not_mounted">There\'s been an error when accessing the SDcard. Please check if there is an SDcard present in the device (internal or external) and if the SDcard is not mounted (via usb to your computer for example). Hedgewars for Android will now quit</string>
-        
+
     <!-- Notification -->
-    <string name="notification_title">Downloading hedgewars files...</string>
+    <string name="notification_title">Downloading hedgewars files&#8230;</string>
     <string name="notification_done">Successfully downloaded: </string>
-    
+
     <!-- Download Activity -->
     <string name="download_background">Continue in background</string>
     <string name="download_cancel">Cancel</string>
@@ -22,25 +22,25 @@
     <string name="download_back">Back to main menu</string>
     <string name="download_tryagain">Try again</string>
     <string name="download_failed">The download has failed because of: </string>
-    <string name="download_userexplain">Before starting the game we must download some extra files...</string>
-    
+    <string name="download_userexplain">Before starting the game we must download some extra files&#8230;</string>
     <string name="download_areyousure">Are you sure you want to download this package?</string>
     <string name="download_alreadydownloaded">You\'ve already downloaded this package, are you sure you want to download it again?</string>
     <string name="download_downloadnow">Download now!</string>
-    
     <string name="download_queued">This download has been queued</string>
+
+    <!-- main activity -->
+    <string name="main_button_localplay">Local Game</string>
+    <string name="main_button_netplay">Network Game</string>
+    <string name="main_menu_downloader">Downloader</string>
+    <string name="main_menu_preferences">Preferences</string>
     
     <!-- start game -->
-    
     <string name="start_gameplay">Style</string>
     <string name="start_gamescheme">Game scheme</string>
     <string name="start_weapons">Weapons</string>
     <string name="start_map">Map</string>
     <string name="start_filter">Filter</string>
-    <string name="start_themes">Themes</string>
-    
-    
-    
+
     <!-- Teams -->
     <string name="not_enough_teams">Not enough teams</string>
     <string name="teams_info_template">Selected teams = %d</string>
@@ -52,7 +52,7 @@
     <string name="flag">Flag</string>
     <string name="voice">Voice</string>
     <string name="fort">Fort</string>
-    
+
     <!-- Difficulty levels -->
     <string name="human">Human</string>
     <string name="bot5">Level 5</string>
@@ -60,5 +60,121 @@
     <string name="bot3">Level 3</string>
     <string name="bot2">Level 2</string>
     <string name="bot1">Level 1</string>
+
+    <string name="title_activity_lobby">Hedgewars Server Lobby</string>
+    <string name="title_activity_room">Room</string>
+    <string name="title_activity_weaponset_list">User-defined Weaponsets</string>
+    <string name="title_activity_weaponset_creator">Weaponset Editor</string>
+    <string name="title_activity_scheme_list">User-defined Schemes</string>
+    <string name="title_activity_scheme_creator">Scheme Editor</string>
     
+    <string name="chat_hint">Type here to chat</string>
+
+    <!-- Map settings -->
+    <string name="map_gen">Map</string>
+    <string name="map_name">Name</string>
+    <string name="map_template">Type</string>
+    <string name="map_maze_size">Type</string>
+    <string name="map_mission_prefix">Mission: </string>
+    <string-array name="map_types">
+        <item>Generated map</item>
+        <item>Generated maze</item>
+        <item>Hand-drawn map</item>
+        <item>Map file</item>
+    </string-array>
+    <string-array name="map_templates">
+        <item>Random</item>
+        <item>Small</item>
+        <item>Medium</item>
+        <item>Large</item>
+        <item>Cavern</item>
+        <item>Wacky</item>
+    </string-array>
+    <string-array name="map_maze_sizes">
+        <item>Small tunnels</item>
+        <item>Medium tunnels</item>
+        <item>Large tunnels</item>
+        <item>Small floating islands</item>
+        <item>Medium floating islands</item>
+        <item>Large floating islands</item>
+    </string-array>
+    
+    <!-- Player list -->
+    <string name="no_players_in_list">No players</string>
+    
+    <!-- Teamlist -->
+    <string name="teamlist_addteam">Add team</string>
+    <string name="teamlist_color_button_description">Team color</string>
+    <string name="teamlist_hogcount_button_description">Hog count</string>
+    
+    <!-- Roomlist -->
+    <string name="roomlist_header_roomname">Room Name</string>
+    <string name="roomlist_header_clients">C</string>
+    <string name="roomlist_header_teams">T</string>
+    <string name="roomlist_header_owner">Owner</string>
+    <string name="roomlist_header_map">Map</string>
+    <string name="roomlist_header_scheme">Rules</string>
+    <string name="roomlist_header_weapons">Weapons</string>
+    <string name="no_rooms_in_list">No rooms</string>
+    
+    <string name="roomlist_owner">by %1$s</string>
+    <string name="roomlist_map">Map: %1$s</string>
+    <string name="roomlist_scheme">Scheme: %1$s</string>
+    <string name="map_regular">Random map</string>
+    <string name="map_maze">Random maze</string>
+    <string name="map_drawn">Drawn map</string>
+    
+    <!-- Chatlog messages -->
+    <string name="log_player_join">%1$s has joined.</string>
+    <string name="log_player_leave">%1$s has left.</string>
+    <string name="log_player_leave_with_msg">%1$s has left (%2$s).</string>
+    
+    <!-- Start netgame dialog -->
+    <string name="start_netgame_dialog_title">Connect</string>
+    <string name="start_netgame_dialog_message">Please select a username.</string>
+    <string name="start_netgame_dialog_playername_hint">Username</string>
+    
+    <string name="playerlist_contextmenu_kick">Kick</string>
+    <string name="lobby_playerlist_contextmenu_info">Info (shown in chat)</string>
+    <string name="lobby_playerlist_contextmenu_follow">Follow</string>
+    <string name="lobby_roomlistmenu_create">Create room</string>
+    <string name="lobby_roomlistmenu_refresh">Refresh</string>
+    <string name="lobby_menu_disconnect">Disconnect</string>
+    <string name="lobby_tab_rooms">Rooms</string>
+    <string name="lobby_tab_chat">Chat</string>
+    <string name="lobby_tab_players">Users</string>
+    
+    <string name="not_implemented_yet">Sorry, not implemented yet. :(</string>
+    
+    <!-- Errors -->
+    <string name="error_connection_failed">Unable to connect to the server.</string>
+    <string name="error_unexpected">An unexpected error has occurred: %1$s</string>
+    <string name="error_server_too_old">The server you tried to connect to is using an incompatible protocol.</string>
+    <string name="error_auth_failed">Unable to authenticate for your username.</string>
+    <string name="error_connection_lost">The connection to the server was lost.</string>
+    <string name="error_save_failed">Saving has failed.</string>
+    <string name="error_missing_sdcard_or_files">Error: Either the sdcard is not available, or files are missing from the Data directory.</string>
+    <string name="error_team_attribute_not_found">Error: This team uses assets which we do not have. Try downloading the big data package from the main menu.</string>
+    
+    <!-- Dialogs -->
+    <string name="dialog_connecting_title">Please wait</string>
+    <string name="dialog_connecting_message">Connecting to the server&#8230;</string>
+    <string name="dialog_password_title">Password required</string>
+    <string name="dialog_password_message">The server has requested a password to connect as "%1$s".</string>
+    <string name="dialog_password_hint">Password</string>
+    <string name="dialog_password_remember">remember password</string>
+    <string name="dialog_create_room_hint">Room name</string>
+    <string name="dialog_create_room_title">Create new room</string>
+    <string name="dialog_addteam_title">Add team</string>
+    
+    <string name="toast_disconnected">Disconnected: %1$s</string>
+    <string name="toast_room_abandoned">The room was closed because the owner left.</string>
+    <string name="toast_kicked">You were kicked from the room.</string>
+    
+    <!-- Weaponset editor -->
+    <string name="weaponsetlist_add_button_text">New Weaponset</string>
+    <string name="weaponsetlist_empty">No weaponsets</string>
+    <string name="edit_weaponsets_menu">Edit Weaponsets</string>
+    
+    <string name="schemelist_add_button_text">New Scheme</string>
 </resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ChatFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,78 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.netplay.MessageLog;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class ChatFragment extends Fragment {
+	public static final String ARGUMENT_INROOM = "inRoom";
+	
+	private ChatlogAdapter adapter;
+	private Netplay netconn;
+	private MessageLog messageLog;
+	private boolean inRoom;
+	
+	public void setInRoom(boolean inRoom) {
+		this.inRoom = inRoom;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netconn = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new ChatlogAdapter(getActivity());
+	}
+	
+	@Override
+	public void onStart() {
+		super.onStart();
+		messageLog = inRoom ? netconn.roomChatlog : netconn.lobbyChatlog;
+    	adapter.setLog(messageLog.getLog());
+    	messageLog.registerObserver(adapter);
+	}
+	
+	@Override
+	public void onStop() {
+		super.onStop();
+		messageLog.unregisterObserver(adapter);
+	}
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View view = inflater.inflate(R.layout.fragment_chat, container, false);
+		
+		ListView listView = (ListView) view.findViewById(R.id.chatConsole);
+		listView.setAdapter(adapter);
+		listView.setDivider(null);
+		listView.setDividerHeight(0);
+		listView.setVerticalFadingEdgeEnabled(true);
+		
+		EditText editText = (EditText) view.findViewById(R.id.chatInput);
+        editText.setOnEditorActionListener(new ChatSendListener());
+        
+		return view;
+	}
+	
+	private final class ChatSendListener implements OnEditorActionListener {
+		public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+			String text = v.getText().toString();
+			if(text.length()>0) {
+				v.setText("");
+				netconn.sendChat(text);
+			}
+			return true;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ChatlogAdapter.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,95 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.netplay.MessageLog.Observer;
+
+import android.content.Context;
+import android.text.method.LinkMovementMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+/**
+ * Optimization: ListView is smart enough to try re-using the same view for an item
+ * with the same ID, but it still calls getView for those items when the list changes.
+ * Since lines with a given ID never change in our chatlog, we can avoid the effort
+ * of TextView.setText in many cases by checking if the view is already set up for the
+ * line with the right ID - but to do that, the view needs to remember the ID it's
+ * holding the text for. That's what the LoglineView does. 
+ */
+class LoglineView extends TextView {
+	long chatlogId = -1;
+	
+	public LoglineView(Context context) {
+		super(context);
+	}
+}
+
+public class ChatlogAdapter extends BaseAdapter implements Observer {
+	long idOffset = 0;
+	private List<CharSequence> log = new ArrayList<CharSequence>();
+	private Context context;
+	
+	public ChatlogAdapter(Context context) {
+		this.context = context;
+	}
+	
+	public int getCount() {
+		return log.size();
+	}
+
+	public Object getItem(int position) {
+		return log.get(position);
+	}
+
+	public long getItemId(int position) {
+		return position+idOffset;
+	}
+
+	public boolean hasStableIds() {
+		return true;
+	}
+
+	public void clear() {
+		idOffset += log.size();
+		log.clear();
+		notifyDataSetChanged();
+	}
+	
+	public void lineAdded(CharSequence text) {
+		log.add(text);
+		notifyDataSetChanged();
+	}
+	
+	public void lineRemoved() {
+		log.remove(0);
+		idOffset += 1;
+		notifyDataSetChanged();
+	}
+	
+	public void setLog(Collection<CharSequence> log) {
+		idOffset += log.size();
+		this.log = new ArrayList<CharSequence>(log);
+		notifyDataSetChanged();
+	}
+	
+	public View getView(int position, View convertView, ViewGroup parent) {
+		LoglineView v = (LoglineView)convertView;
+		if (v == null) {
+			v = new LoglineView(context);
+			v.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
+			v.setMovementMethod(LinkMovementMethod.getInstance());
+		}
+		long id = getItemId(position);
+		if(id != v.chatlogId) {
+			v.setText(log.get(position));
+			v.chatlogId = id;
+		}
+		return v;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ConnectingDialog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,59 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class ConnectingDialog extends ConnectionDependendDialogFragment {
+	@Override
+	public void onStart() {
+		super.onStart();
+		LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).registerReceiver(connectedReceiver, new IntentFilter(Netplay.ACTION_CONNECTED));
+
+		if(Netplay.getAppInstance(getActivity().getApplicationContext()).getState() != State.CONNECTING) {
+			dismiss();
+		}
+	}
+	
+	@Override
+	public void onStop() {
+		super.onStop();
+		LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(connectedReceiver);
+	}
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		ProgressDialog dialog = new ProgressDialog(getActivity());
+		dialog.setIndeterminate(true);
+		dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+		dialog.setTitle(R.string.dialog_connecting_title);
+		dialog.setMessage(getString(R.string.dialog_connecting_message));
+		return dialog;
+	}
+	
+	private BroadcastReceiver connectedReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			Dialog dialog = getDialog();
+			if(dialog != null) {
+				dialog.dismiss();
+			} else {
+				dismiss();
+			}
+		}
+	};
+	
+	public void onCancel(DialogInterface dialog) {
+		super.onCancel(dialog);
+		Netplay.getAppInstance(getActivity().getApplicationContext()).disconnect();
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ConnectionDependendDialogFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,41 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class ConnectionDependendDialogFragment extends DialogFragment {
+	@Override
+	public void onStart() {
+		super.onStart();
+		LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).registerReceiver(dismissReceiver, new IntentFilter(Netplay.ACTION_DISCONNECTED));
+		if(Netplay.getAppInstance(getActivity().getApplicationContext()).getState() == State.NOT_CONNECTED) {
+			dismiss();
+		}
+	}
+	
+	@Override
+	public void onStop() {
+		super.onStop();
+		LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(dismissReceiver);
+	}
+	
+	private BroadcastReceiver dismissReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			Dialog dialog = getDialog();
+			if(dialog != null) {
+				dialog.dismiss();
+			} else {
+				dismiss();
+			}
+		}
+	};
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/FrontendDataUtils.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/FrontendDataUtils.java	Sat Aug 18 00:48:09 2012 +0200
@@ -20,80 +20,78 @@
 package org.hedgewars.hedgeroid.Datastructures;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
 import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Utils;
-import org.hedgewars.hedgeroid.Datastructures.Map.MapType;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import java.nio.ByteBuffer;
 
 public class FrontendDataUtils {
 
-
-	public static ArrayList<Map> getMaps(Context c){
-		File[] files = Utils.getFilesFromRelativeDir(c,"Maps");
-		ArrayList<Map> ret = new ArrayList<Map>();
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Maps directory doesn't exist
+	 */
+	public static ArrayList<MapFile> getMaps(Context c) throws FileNotFoundException {
+		File[] files = FileUtils.getFilesFromRelativeDir(c,"Maps");
+		ArrayList<MapFile> ret = new ArrayList<MapFile>();
 
-		for(File f : files){
-			if(Utils.hasFileWithSuffix(f, ".lua")){
-				ret.add(new Map(f,MapType.TYPE_MISSION, c));
-			}else{
-				ret.add(new Map(f, MapType.TYPE_DEFAULT,c));
-			}
+		for(File f : files) {
+			boolean isMission = FileUtils.hasFileWithSuffix(f, ".lua");
+			ret.add(new MapFile(f.getName(), isMission));
 		}
-		Collections.sort(ret);
 
 		return ret;
 	}
 
-	public static List<String> getGameplay(Context c){
-		String[] files = Utils.getFileNamesFromRelativeDir(c, "Scripts/Multiplayer");
+	/**
+	 * Returns a list of all multiplayer scripts (game styles)
+	 * @throws FileNotFoundException if the sdcard isn't available or the Scripts/Multiplayer directory doesn't exist
+	 */
+	public static List<String> getGameStyles(Context c) throws FileNotFoundException {
+		File[] files = FileUtils.getFilesFromRelativeDir(c, "Scripts/Multiplayer");
 		ArrayList<String> ret = new ArrayList<String>();
-		
-		for(int i = 0; i < files.length; i++){
-			if(files[i].endsWith(".lua")){
-				ret.add(files[i].replace('_', ' ').substring(0, files[i].length()-4)); //replace _ by a space and removed the last four characters (.lua)
+		/*
+		 * Caution: It is important that the "empty" style has this exact name, because
+		 * it will be interpreted as "don't load a script" by the frontlib, and also by
+		 * the QtFrontend in a netgame. This should probably be improved some time
+		 * (maybe TODO add a dummy script called "Normal" to the MP scripts?) 
+		 */
+		ret.add("Normal");
+		for(int i = 0; i < files.length; i++) {
+			String name = files[i].getName();
+			if(name.endsWith(".lua")){
+				//replace _ by a space and removed the last four characters (.lua)
+				ret.add(name.replace('_', ' ').substring(0, name.length()-4));
 			}
 		}
-		ret.add(0,"None");
-		Collections.sort(ret);
-		return ret;	
+		return ret;
 	}
 
-	public static List<String> getThemes(Context c){
-		List<String> list = Utils.getDirsWithFileSuffix(c, "Themes", "icon.png");
-		Collections.sort(list);
-		return list;
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Themes directory doesn't exist
+	 */
+	public static List<String> getThemes(Context c) throws FileNotFoundException {
+		return FileUtils.getDirsWithFileSuffix(c, "Themes", "icon.png");
 	}
 
-	public static List<Scheme> getSchemes(Context c){
-		List<Scheme> list = Scheme.getSchemes(c);
-		Collections.sort(list);
-		return list;
-	}
-
-	public static List<Weapon> getWeapons(Context c){
-		List<Weapon> list = Weapon.getWeapons(c);
-		Collections.sort(list);
-		return list;
-	}
-
-	public static ArrayList<HashMap<String, ?>> getGraves(Context c){
-		String pathPrefix = Utils.getDataPath(c) + "Graphics/Graves/";
-		ArrayList<String> names = Utils.getFilesFromDirWithSuffix(c,"Graphics/Graves", ".png", true);
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Graphics/Graves directory doesn't exist
+	 */
+	public static ArrayList<HashMap<String, ?>> getGraves(Context c) throws FileNotFoundException {
+		File gravePath = new File(new File(FileUtils.getDataPathFile(c), "Graphics"), "Graves");
+		ArrayList<String> names = FileUtils.getFileNamesFromDirWithSuffix(c,"Graphics/Graves", ".png", true);
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(names.size());
 
 		for(String s : names){
 			HashMap<String, Object> map = new HashMap<String, Object>();
 			map.put("txt", s);
-			Bitmap b = BitmapFactory.decodeFile(pathPrefix + s + ".png");//create a full path - decode to to a bitmap
+			Bitmap b = BitmapFactory.decodeFile(new File(gravePath, s + ".png").getAbsolutePath());
 			int width = b.getWidth();
 			if(b.getHeight() > width){//some pictures contain more 'frames' underneath each other, if so we only use the first frame
                                 Bitmap tmp = Bitmap.createBitmap(width, width, b.getConfig());
@@ -109,23 +107,29 @@
 		return data;
 	}
 
-	public static ArrayList<HashMap<String, ?>> getFlags(Context c){
-		String pathPrefix = Utils.getDataPath(c) + "Graphics/Flags/";
-		ArrayList<String> names = Utils.getFilesFromDirWithSuffix(c, "Graphics/Flags", ".png", true);
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Graphics/Graves directory doesn't exist
+	 */
+	public static ArrayList<HashMap<String, ?>> getFlags(Context c) throws FileNotFoundException {
+		File flagsPath = new File(new File(FileUtils.getDataPathFile(c), "Graphics"), "Flags");
+		ArrayList<String> names = FileUtils.getFileNamesFromDirWithSuffix(c, "Graphics/Flags", ".png", true);
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(names.size());
 
 		for(String s : names){
 			HashMap<String, Object> map = new HashMap<String, Object>();
 			map.put("txt", s);
-			Bitmap b = BitmapFactory.decodeFile(pathPrefix + s + ".png");//create a full path - decode to to a bitmap
+			Bitmap b = BitmapFactory.decodeFile(new File(flagsPath, s + ".png").getAbsolutePath());
 			map.put("img", b);
 			data.add(map);
 		}
 		return data;
 	}
 
-	public static ArrayList<String> getVoices(Context c){
-		File[] files = Utils.getFilesFromRelativeDir(c, "Sounds/voices");
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Sounds/voices directory doesn't exist
+	 */
+	public static ArrayList<String> getVoices(Context c) throws FileNotFoundException {
+		File[] files = FileUtils.getFilesFromRelativeDir(c, "Sounds/voices");
 		ArrayList<String> ret = new ArrayList<String>();
 
 		for(File f : files){
@@ -134,9 +138,13 @@
 		return ret;
 	}
 
-	public static ArrayList<String> getForts(Context c){
-		return Utils.getFilesFromDirWithSuffix(c,"Forts", "L.png", true);
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Forts directory doesn't exist
+	 */
+	public static ArrayList<String> getForts(Context c) throws FileNotFoundException {
+		return FileUtils.getFileNamesFromDirWithSuffix(c,"Forts", "L.png", true);
 	}
+	
 	public static ArrayList<HashMap<String, ?>> getTypes(Context c){
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(6);
 		String[] levels = {c.getString(R.string.human), c.getString(R.string.bot5), c.getString(R.string.bot4), c.getString(R.string.bot3), c.getString(R.string.bot2), c.getString(R.string.bot1)};
@@ -146,15 +154,20 @@
 			HashMap<String, Object> map = new HashMap<String, Object>();
 			map.put("txt", levels[i]);
 			map.put("img", images[i]);
+			map.put("level", i);
+			
 			data.add(map);
 		}
 
 		return data;
 	}
 
-	public static ArrayList<HashMap<String, ?>> getHats(Context c){
-		ArrayList<String> files = Utils.getFilesFromDirWithSuffix(c,"Graphics/Hats", ".png", true);
-		String pathPrefix = Utils.getDataPath(c) + "Graphics/Hats/";
+	/**
+	 * @throws FileNotFoundException if the sdcard isn't available or the Graphics/Hats directory doesn't exist
+	 */
+	public static ArrayList<HashMap<String, ?>> getHats(Context c) throws FileNotFoundException {
+		ArrayList<String> files = FileUtils.getFileNamesFromDirWithSuffix(c,"Graphics/Hats", ".png", true);
+		File hatsPath = new File(new File(FileUtils.getDataPathFile(c), "Graphics"), "Hats");
 		int size = files.size();
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(size);
 
@@ -162,7 +175,7 @@
 		for(String s : files){
 			hashmap = new HashMap<String, Object>();
 			hashmap.put("txt", s);
-			Bitmap b = BitmapFactory.decodeFile(pathPrefix + s + ".png");//create a full path - decode to to a bitmap
+			Bitmap b = BitmapFactory.decodeFile(new File(hatsPath, s + ".png").getAbsolutePath());
 			b = Bitmap.createBitmap(b, 0,0,b.getWidth()/2, b.getWidth()/2);
 			hashmap.put("img", b);
 			data.add(hashmap);
@@ -171,50 +184,21 @@
 		return data;
 	}
 
-	public static List<HashMap<String, Object>> getTeams(Context c){
-		List<HashMap<String, Object>> ret = new ArrayList<HashMap<String, Object>>();
-
-		File teamsDir = new File(c.getFilesDir().getAbsolutePath() + '/' + Team.DIRECTORY_TEAMS);
+	public static List<Team> getTeams(Context c) {
+		List<Team> ret = new ArrayList<Team>();
+		
+		File teamsDir = new File(c.getFilesDir(), Team.DIRECTORY_TEAMS);
 		File[] teamFileNames = teamsDir.listFiles();
 		if(teamFileNames != null){
-			for(File s : teamFileNames){
-				Team t = Team.getTeamFromXml(s.getAbsolutePath());
-				if(t != null){
-					t.file = s.getName();
-					ret.add(teamToMap(t));
+			for(File file : teamFileNames){
+				if(file.getName().endsWith(".hwt")) {
+					Team team = Team.load(file);
+					if(team != null){
+						ret.add(team);
+					}
 				}
 			}
 		}
 		return ret;
 	}
-
-	public static HashMap<String, Object> teamToMap(Team t){
-		HashMap<String, Object> hashmap = new HashMap<String, Object>();
-		hashmap.put("team", t);
-		hashmap.put("txt", t.name);
-		hashmap.put("color", t.color);
-		hashmap.put("count", t.hogCount);
-		switch(t.levels[0]){
-		case 0:
-			hashmap.put("img", R.drawable.human);
-			break;
-		case 1:
-			hashmap.put("img", R.drawable.bot5);
-			break;
-		case 2:
-			hashmap.put("img", R.drawable.bot4);
-			break;
-		case 3:
-			hashmap.put("img", R.drawable.bot3);
-			break;
-		case 4:
-			hashmap.put("img", R.drawable.bot2);
-			break;
-		default:
-		case 5:
-			hashmap.put("img", R.drawable.bot1);
-			break;
-		}
-		return hashmap;
-	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/GameConfig.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,61 @@
+/*
+ * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
+ * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Game configuration from the point of view of the UI. This differs slightly from the
+ * frontlib view, because the engine allows setting a separate initial health and weapon set
+ * for each hog, while the Android UI currently only supports both attributes on a per-game
+ * basis (initial health is contained in the scheme).
+ * 
+ * This difference means that creating a GameConfig object from a frontlib object can, in
+ * theory, lose information. This does not cause problems at the moment because for now
+ * weaponset and initial health are always per-game, but that might change in the future.
+ */
+public final class GameConfig {
+	public static final String DEFAULT_STYLE = "Normal";
+	public static final String DEFAULT_SCHEME = "Default";
+	public static final String DEFAULT_WEAPONSET = "Default";
+	public static final String DEFAULT_THEME = "Bamboo";
+	
+	public final String style;
+	public final Scheme scheme;
+	public final MapRecipe map;
+	public final List<TeamInGame> teams;
+	public final Weaponset weaponset;
+	
+	public GameConfig(String style, Scheme scheme, MapRecipe map, List<TeamInGame> teams, Weaponset weaponset) {
+		this.style = style;
+		this.scheme = scheme;
+		this.map = map;
+		this.teams = Collections.unmodifiableList(new ArrayList<TeamInGame>(teams));
+		this.weaponset = weaponset;
+	}
+
+	@Override
+	public String toString() {
+		return "GameConfig [style=" + style + ", scheme=" + scheme + ", map="
+				+ map + ", teams=" + teams + ", weaponset=" + weaponset + "]";
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/GameMode.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/GameMode.java	Sat Aug 18 00:48:09 2012 +0200
@@ -20,5 +20,5 @@
 package org.hedgewars.hedgeroid.Datastructures;
 
 public enum GameMode {
-		MODE_LOCAL, MODE_DEMO, MODE_NET, MODE_SAVE
+	MODE_LOCAL, MODE_DEMO, MODE_NET, MODE_SAVE
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Grave.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Grave.java	Sat Aug 18 00:48:09 2012 +0200
@@ -19,18 +19,17 @@
 
 package org.hedgewars.hedgeroid.Datastructures;
 
-public class Grave{
-
+public final class Grave{
 	public final String name;
 	public final String path;
 	
-	public Grave(String _name, String _path) {
-		name = _name;
-		path = _path;
+	public Grave(String name, String path) {
+		this.name = name;
+		this.path = path;
 	}
 
-	public String toString(){
-		return name;
+	@Override
+	public String toString() {
+		return "Grave [name=" + name + ", path=" + path + "]";
 	}
-	
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Hog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,17 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+public final class Hog {
+	public final String name, hat;
+	public final int level;
+	
+	public Hog(String name, String hat, int level) {
+		this.name = name;
+		this.hat = hat;
+		this.level = level;
+	}
+
+	@Override
+	public String toString() {
+		return "Hog [name=" + name + ", hat=" + hat + ", level=" + level + "]";
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Map.java	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/*
- * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
- * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
-package org.hedgewars.hedgeroid.Datastructures;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork;
-
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class Map implements Comparable<Map>, Parcelable{
-
-	private static final String MISSION_PREFIX = "Mission: ";
-
-	private String name;
-	private String path;
-	private String previewPath;
-	private MapType type;
-
-	public Map(File mapDir, MapType _type, Context c){
-		type = _type;
-
-		name = mapDir.getName();
-		path = mapDir.getAbsolutePath();
-		previewPath = path + "/preview.png";
-		
-		/*switch(type){
-		case TYPE_DEFAULT:
-			
-			break;
-		case TYPE_GENERATED:
-			//TODO
-			break;
-		case TYPE_MISSION:
-			name = MISSION_PREFIX + mapDir.getName();
-			path = mapDir.getAbsolutePath();
-			break;
-		}*/
-
-		
-	}
-	
-	public Map(Parcel in){
-		readFromParcel(in);
-	}
-
-	public String toString(){
-		switch(type){
-		default:
-		case TYPE_DEFAULT:
-			return name;
-		case TYPE_GENERATED:
-			return "bla";
-		case TYPE_MISSION:
-			return MISSION_PREFIX + name;
-		}
-	}
-	
-	public void sendToEngine(EngineProtocolNetwork epn) throws IOException{
-		epn.sendToEngine(String.format("emap %s",name));
-	}
-	
-	public MapType getType(){
-		return type;
-	}
-
-	public Drawable getDrawable(){
-		switch(type){
-		case TYPE_MISSION:
-		case TYPE_DEFAULT:
-			return Drawable.createFromPath(previewPath);
-		case TYPE_GENERATED:
-
-		default:
-			return null;
-		}
-	}
-
-	public int compareTo(Map another) {
-		switch(type){
-		case TYPE_GENERATED:
-			switch(another.getType()){
-			case TYPE_GENERATED:
-				return name.compareTo(another.name);
-			case TYPE_MISSION:
-				return -1;
-			case TYPE_DEFAULT:
-				return -1;
-			}
-		case TYPE_MISSION:
-			switch(another.getType()){
-			case TYPE_GENERATED:
-				return 1;
-			case TYPE_MISSION:
-				return name.compareTo(another.name);
-			case TYPE_DEFAULT:
-				return -1;
-			}
-		case TYPE_DEFAULT:
-			switch(another.getType()){
-			case TYPE_GENERATED:
-				return 1;
-			case TYPE_MISSION:
-				return 1;
-			case TYPE_DEFAULT:
-				return name.compareTo(another.name);
-			}
-		}
-		return 0;//default case this should never happen
-	}
-
-	public enum MapType{
-		TYPE_DEFAULT, TYPE_MISSION, TYPE_GENERATED
-	}
-
-	public int describeContents() {
-		return 0;
-	}
-	
-	public void writeToParcel(Parcel dest, int flags) {
-		dest.writeString(name);
-		dest.writeString(path);
-		dest.writeString(previewPath);
-		dest.writeString(type.name());
-	}
-	
-	private void readFromParcel(Parcel src){
-		name = src.readString();
-		path = src.readString();
-		previewPath = src.readString();
-		type = MapType.valueOf(src.readString());
-	}
-	public static final Parcelable.Creator<Map> CREATOR = new Parcelable.Creator<Map>() {
-		public Map createFromParcel(Parcel source) {
-			return new Map(source);
-		}
-		public Map[] newArray(int size) {
-			return new Map[size];
-		}
-		
-	};
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapFile.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,67 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.util.FileUtils;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+/**
+ * Represents a map from the data directory
+ */
+public final class MapFile {
+	public static final String MAP_DIRECTORY = "Maps";
+	
+	public final String name;
+	public final boolean isMission;
+	
+	public MapFile(String name, boolean isMission) {
+		this.name = name;
+		this.isMission = isMission;
+	}
+	
+	/**
+	 * @throws FileNotFoundException if the sdcard is not available. Does NOT throw if the requested map file does not exist.
+	 */
+	public static File getFileForMapname(Context ctx, String mapname) throws FileNotFoundException {
+		return new File(new File(FileUtils.getDataPathFile(ctx), MAP_DIRECTORY), mapname);
+	}
+	
+	public static final Comparator<MapFile> MISSIONS_FIRST_NAME_ORDER = new Comparator<MapFile>() {
+		public int compare(MapFile lhs, MapFile rhs) {
+			if(lhs.isMission != rhs.isMission) {
+				return lhs.isMission && !rhs.isMission ? -1 : 1;
+			} else {
+				return lhs.name.compareToIgnoreCase(rhs.name);
+			}
+		}
+	};
+	
+	@Override
+	public String toString() {
+		return "MapFile [name=" + name + ", isMission=" + isMission + "]";
+	}
+
+	public File getPreviewFile(Context c) throws FileNotFoundException {
+		return getPreviewFile(c, name);
+	}
+	
+	public static File getPreviewFile(Context c, String mapName) throws FileNotFoundException {
+		return new File(FileUtils.getDataPathFile(c), MAP_DIRECTORY + "/" + mapName + "/" + "preview.png");
+	}
+	
+	public static List<String> toDisplayNameList(List<MapFile> mapFiles, Resources res) {
+		String missionPrefix = res.getString(R.string.map_mission_prefix);
+		List<String> result = new ArrayList<String>();
+		for(MapFile mapFile : mapFiles) {
+			result.add((mapFile.isMission ? missionPrefix : "") + mapFile.name);
+		}
+		return result;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapRecipe.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,202 @@
+/*
+ * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
+ * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+
+import android.content.res.Resources;
+
+public final class MapRecipe {
+	public static final String MAPNAME_REGULAR = "+rnd+";
+	public static final String MAPNAME_MAZE = "+maze+";
+	public static final String MAPNAME_DRAWN = "+drawn+";
+
+	public final int mapgen;			// Frontlib.MAPGEN_xxx
+	public final int templateFilter;	// Frontlib.TEMPLATEFILTER_xxx, only used when mapgen==MAPGEN_REGULAR
+	public final int mazeSize;			// Frontlib.MAZE_SIZE_xxx, only used when mapgen==MAPGEN_MAZE
+	public final String name, seed, theme;
+	
+	private final byte[] drawData;		// For drawn maps, can be null.
+
+	public MapRecipe(int mapgen, int templateFilter, int mazeSize, String name, String seed, String theme, byte[] drawData) {
+		this.mapgen = mapgen;
+		this.templateFilter = templateFilter;
+		this.mazeSize = mazeSize;
+		this.name = name;
+		this.seed = seed;
+		this.theme = theme;
+		this.drawData = drawData==null ? null : drawData.clone();
+	}
+	
+	public static MapRecipe makeMap(String name, String seed, String theme) {
+		return new MapRecipe(Frontlib.MAPGEN_NAMED, 0, 0, name, seed, theme, null);
+	}
+	
+	public static MapRecipe makeRandomMap(int templateFilter, String seed, String theme) {
+		return new MapRecipe(Frontlib.MAPGEN_REGULAR, templateFilter, 0, MAPNAME_REGULAR, seed, theme, null);
+	}
+	
+	public static MapRecipe makeRandomMaze(int mazeSize, String seed, String theme) {
+		return new MapRecipe(Frontlib.MAPGEN_MAZE, 0, mazeSize, MAPNAME_MAZE, seed, theme, null);
+	}
+	
+	public static MapRecipe makeDrawnMap(String seed, String theme, byte[] drawData) {
+		return new MapRecipe(Frontlib.MAPGEN_DRAWN, 0, 0, MAPNAME_DRAWN, seed, theme, drawData);
+	}
+	
+	public byte[] getDrawData() {
+		return drawData==null ? null : drawData.clone();
+	}
+	
+	public MapRecipe withMapgen(int mapgen) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withTemplateFilter(int templateFilter) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withMazeSize(int mazeSize) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withName(String name) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withSeed(String seed) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withTheme(String theme) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withDrawData(byte[] drawData) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public static String formatMapName(Resources res, String map) {
+		if(map.charAt(0)=='+') {
+			if(map.equals(MAPNAME_REGULAR)) {
+				return res.getString(R.string.map_regular);
+			} else if(map.equals(MAPNAME_MAZE)) {
+				return res.getString(R.string.map_maze);
+			} else if(map.equals(MAPNAME_DRAWN)) {
+				return res.getString(R.string.map_drawn);
+			}
+		}
+		return map;
+	}
+
+	/**
+	 * Returns the mapname corresponding to the map generator (e.g. "+rnd+" for regular maps)
+	 * If the mapgen does not have a unique name (MAPGEN_NAMED) or is not known, the def
+	 * value is returned.
+	 */
+	public static String mapnameForGenerator(int mapgen, String def) {
+		switch(mapgen) {
+		case Frontlib.MAPGEN_REGULAR: return MAPNAME_REGULAR;
+		case Frontlib.MAPGEN_MAZE: return MAPNAME_MAZE;
+		case Frontlib.MAPGEN_DRAWN: return MAPNAME_DRAWN;
+		default: return def;
+		}
+	}
+	
+	/**
+	 * In a sense this is the inverse of mapnameForGenerator. Returns the mapgen that uses
+	 * mapName as special identifier, or MAPGEN_NAMED if there is none.
+	 */
+	public static int generatorForMapname(String mapName) {
+		if(MapRecipe.MAPNAME_REGULAR.equals(mapName)) {
+			return Frontlib.MAPGEN_REGULAR;
+		} else if(MapRecipe.MAPNAME_MAZE.equals(mapName)) {
+			return Frontlib.MAPGEN_MAZE;
+		} else if(MapRecipe.MAPNAME_DRAWN.equals(mapName)) {
+			return Frontlib.MAPGEN_DRAWN;
+		} else {
+			return Frontlib.MAPGEN_NAMED;
+		}
+	}
+	
+	@Override
+	public String toString() {
+		return "MapRecipe [mapgen=" + mapgen + ", templateFilter="
+				+ templateFilter + ", mazeSize=" + mazeSize + ", name=" + name
+				+ ", seed=" + seed + ", theme=" + theme + ", drawData="
+				+ Arrays.toString(drawData) + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + Arrays.hashCode(drawData);
+		result = prime * result + mapgen;
+		result = prime * result + mazeSize;
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result + ((seed == null) ? 0 : seed.hashCode());
+		result = prime * result + templateFilter;
+		result = prime * result + ((theme == null) ? 0 : theme.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		MapRecipe other = (MapRecipe) obj;
+		if (!Arrays.equals(drawData, other.drawData))
+			return false;
+		if (mapgen != other.mapgen)
+			return false;
+		if (mazeSize != other.mazeSize)
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (seed == null) {
+			if (other.seed != null)
+				return false;
+		} else if (!seed.equals(other.seed))
+			return false;
+		if (templateFilter != other.templateFilter)
+			return false;
+		if (theme == null) {
+			if (other.theme != null)
+				return false;
+		} else if (!theme.equals(other.theme))
+			return false;
+		return true;
+	}
+
+	public static String makeRandomSeed() {
+		return "{"+UUID.randomUUID().toString()+"}";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MetaScheme.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,63 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.frontlib.Flib;
+
+public final class MetaScheme {
+	public static final MetaScheme INSTANCE = Flib.INSTANCE.flib_get_metascheme().deref();
+	
+	public static final class Mod {
+		public final String name;
+		public final int bitmaskIndex;
+		
+		public Mod(String name, int bitmaskIndex) {
+			this.name = name;
+			this.bitmaskIndex = bitmaskIndex;
+		}
+
+		@Override
+		public String toString() {
+			return "MetaScheme$Mod [name=" + name + ", bitmaskIndex=" + bitmaskIndex + "]";
+		}
+	}
+	
+	public static final class Setting {
+		public final String name, engineCommand;
+		public final boolean maxMeansInfinity, times1000;
+		public final int min, max, def;
+		
+		public Setting(String name, String engineCommand, boolean maxMeansInfinity, boolean times1000, int min, int max, int def) {
+			this.name = name;
+			this.engineCommand = engineCommand;
+			this.maxMeansInfinity = maxMeansInfinity;
+			this.times1000 = times1000;
+			this.min = min;
+			this.max = max;
+			this.def = def;
+		}
+
+		@Override
+		public String toString() {
+			return "MetaScheme$Setting [name=" + name + ", engineCommand=" + engineCommand
+					+ ", maxMeansInfinite=" + maxMeansInfinity + ", times1000="
+					+ times1000 + ", min=" + min + ", max=" + max + ", def="
+					+ def + "]";
+		}
+	}
+	
+	public final List<Mod> mods;
+	public final List<Setting> settings;
+	
+	public MetaScheme(List<Mod> mods, List<Setting> settings) {
+		this.mods = Collections.unmodifiableList(new ArrayList<Mod>(mods));
+		this.settings = Collections.unmodifiableList(new ArrayList<Setting>(settings));
+	}
+
+	@Override
+	public String toString() {
+		return "MetaScheme [\nmods=" + mods + ", \nsettings=" + settings + "\n]";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Player.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,29 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.Comparator;
+
+/**
+ * Basic information about a player on a server.
+ */
+public final class Player {
+	public final String name;
+	public final boolean registered, admin;
+	
+	public Player(String name, boolean registered, boolean admin) {
+		this.name = name;
+		this.registered = registered;
+		this.admin = admin;
+	}
+
+	@Override
+	public String toString() {
+		return "Player [name=" + name + ", registered=" + registered
+				+ ", admin=" + admin + "]";
+	}
+
+	public static Comparator<Player> NAME_ORDER = new Comparator<Player>() {
+		public int compare(Player lhs, Player rhs) {
+			return lhs.name.compareToIgnoreCase(rhs.name);
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/PlayerInRoom.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,16 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+public final class PlayerInRoom {
+	public final Player player;
+	public final boolean ready;
+	
+	public PlayerInRoom(Player player, boolean ready) {
+		this.player = player;
+		this.ready = ready;
+	}
+
+	@Override
+	public String toString() {
+		return "PlayerInRoom [player=" + player + ", ready=" + ready + "]";
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Room.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,36 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import android.content.res.Resources;
+
+/**
+ * A room as presented in the roomlist in a server lobby.
+ */
+public final class Room {
+	public final String name, map, scheme, weapons, owner;
+	public final int playerCount, teamCount;
+	public final boolean inProgress;
+	
+	public Room(String name, String map, String scheme, String weapons,
+			String owner, int playerCount, int teamCount, boolean inProgress) {
+		this.name = name;
+		this.map = map;
+		this.scheme = scheme;
+		this.weapons = weapons;
+		this.owner = owner;
+		this.playerCount = playerCount;
+		this.teamCount = teamCount;
+		this.inProgress = inProgress;
+	}
+	
+	public String formatMapName(Resources res) {
+		return MapRecipe.formatMapName(res, map);
+	}
+
+	@Override
+	public String toString() {
+		return "RoomlistRoom [name=" + name + ", map=" + map + ", scheme="
+				+ scheme + ", weapons=" + weapons + ", owner=" + owner
+				+ ", playerCount=" + playerCount + ", teamCount=" + teamCount
+				+ ", inProgress=" + inProgress + "]";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/RoomWithId.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,24 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.Comparator;
+
+public final class RoomWithId {
+	public final Room room;
+	public final long id;
+	
+	public RoomWithId(Room room, long id) {
+		this.room = room;
+		this.id = id;
+	}
+
+	@Override
+	public String toString() {
+		return "RoomWithId [room=" + room + ", id=" + id + "]";
+	}
+	
+	public static final Comparator<RoomWithId> NEWEST_FIRST_ORDER = new Comparator<RoomWithId>() {
+		public int compare(RoomWithId lhs, RoomWithId rhs) {
+			return rhs.id<lhs.id ? -1 : rhs.id>lhs.id ? 1 : 0;
+		}
+	};
+}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Scheme.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Scheme.java	Sat Aug 18 00:48:09 2012 +0200
@@ -1,7 +1,8 @@
 /*
  * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
  * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
+ * Copyright (c) 2012 Simeon Maxein <smaxein@googlemail.com>
+ * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; version 2 of the License
@@ -18,351 +19,88 @@
 
 package org.hedgewars.hedgeroid.Datastructures;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-
-import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
 
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class Scheme implements Parcelable, Comparable<Scheme>{
-
-	public static final String DIRECTORY_SCHEME = "schemes";
-
-	private String name;
-	//private ArrayList<Integer> basic;
-	private Integer gamemod;
-	private ArrayList<Integer> basic;;
-	private static ArrayList<LinkedHashMap<String, ?>> basicflags = new ArrayList<LinkedHashMap<String, ?>>();//TODO why is it static?
-	public int health;
-	
-	public Scheme(String _name, ArrayList<Integer> _basic, int _gamemod){
-		name = _name;
-		gamemod = _gamemod;
-		basic = _basic;
+public final class Scheme {
+	public final String name;
+	public final Map<String, Integer> settings;
+	public final Map<String, Boolean> mods;
+		
+	public Scheme(String name, Map<String, Integer> settings, Map<String, Boolean> mods) {
+		this.name = name;
+		this.settings = Collections.unmodifiableMap(new HashMap<String, Integer>(settings));
+		this.mods = Collections.unmodifiableMap(new HashMap<String, Boolean>(mods));
 	}
 	
-	public Scheme(Parcel in){
-		readFromParcel(in);
-	}
-
-	public void sendToEngine(EngineProtocolNetwork epn)throws IOException{ 
-		epn.sendToEngine(String.format("e$gmflags %d", gamemod));
-
-		for(int pos = 0; pos < basic.size(); pos++){
-			LinkedHashMap<String, ?> basicflag = basicflags.get(pos);
-			
-			String command = (String)basicflag.get("command");
-			Integer value = basic.get(pos);
-			
-			if(command.equals("inithealth")){//Health is a special case, it doesn't need to be send 				                             
-				health = value;              //to the engine yet, we'll do that with the other HH info
-				continue;
-			}
-			
-			Boolean checkOverMax = (Boolean) basicflag.get("checkOverMax");
-			Boolean times1000 = (Boolean) basicflag.get("times1000");
-			Integer max = (Integer) basicflag.get("max");
-			
-			if(checkOverMax && value >= max) value = max;
-			if(times1000) value *= 1000;
-			
-			epn.sendToEngine(String.format("%s %d", command, value));
-		}
-	}
-	public String toString(){
-		return name;
+	public int getHealth() {
+		Integer health = settings.get("health");
+		return health==null ? 100 : health.intValue();
 	}
 
-
-	public static final int STATE_START = 0;
-	public static final int STATE_ROOT = 1;
-	public static final int STATE_NAME = 2;
-	public static final int STATE_BASICFLAGS = 3;
-	public static final int STATE_GAMEMOD = 4;
-	public static final int STATE_BASICFLAG_INTEGER = 5;
-	public static final int STATE_GAMEMOD_TRUE = 6;
-	public static final int STATE_GAMEMOD_FALSE = 7;
-
-	public static ArrayList<Scheme> getSchemes(Context c) throws IllegalArgumentException{
-		String dir = c.getFilesDir().getAbsolutePath() + '/' + DIRECTORY_SCHEME + '/';
-		String[] files = new File(dir).list(fnf);
-		if(files == null) files = new String[]{};
-		Arrays.sort(files);
-		ArrayList<Scheme> schemes = new ArrayList<Scheme>();
-
-		try {
-			XmlPullParserFactory xmlPullFactory = XmlPullParserFactory.newInstance();
-			XmlPullParser xmlPuller = xmlPullFactory.newPullParser();
-
-			for(String file : files){
-				BufferedReader br = new BufferedReader(new FileReader(dir + file), 1024);
-				xmlPuller.setInput(br);
-				String name = null;
-				ArrayList<Integer> basic = new ArrayList<Integer>();
-				Integer gamemod = 0;
-				int health = 0;
-				int mask = 0x000000004;
-
-				int eventType = xmlPuller.getEventType();
-				int state = STATE_START;
-				while(eventType != XmlPullParser.END_DOCUMENT){
-					switch(state){
-					case STATE_START:
-						if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().equals("scheme")) state = STATE_ROOT;
-						else if(eventType != XmlPullParser.START_DOCUMENT) throwException(file, eventType);
-						break;
-					case STATE_ROOT:
-						if(eventType == XmlPullParser.START_TAG){
-							if(xmlPuller.getName().equals("basicflags")) state = STATE_BASICFLAGS;
-							else if(xmlPuller.getName().toLowerCase().equals("gamemod")) state = STATE_GAMEMOD;
-							else if(xmlPuller.getName().toLowerCase().equals("name")) state = STATE_NAME;
-							else throwException(file, eventType);
-						}else if(eventType == XmlPullParser.END_TAG) state = STATE_START;
-						else throwException(xmlPuller.getText(), eventType);
-						break;
-					case STATE_BASICFLAGS:
-						if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().toLowerCase().equals("integer")) state = STATE_BASICFLAG_INTEGER;
-						else if(eventType == XmlPullParser.END_TAG)	state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_GAMEMOD:
-						if(eventType == XmlPullParser.START_TAG){
-							if(xmlPuller.getName().toLowerCase().equals("true")) state = STATE_GAMEMOD_TRUE;
-							else if(xmlPuller.getName().toLowerCase().equals("false")) state = STATE_GAMEMOD_FALSE;
-							else throwException(file, eventType);
-						}else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_NAME:
-						if(eventType == XmlPullParser.TEXT) name = xmlPuller.getText().trim();
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_BASICFLAG_INTEGER:
-						if(eventType == XmlPullParser.TEXT) basic.add(Integer.parseInt(xmlPuller.getText().trim()));
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_BASICFLAGS;
-						else throwException(file, eventType);
-						break;
-					case STATE_GAMEMOD_FALSE:
-						if(eventType == XmlPullParser.TEXT) gamemod <<= 1;
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_GAMEMOD;
-						else throwException(file, eventType);
-						break;
-					case STATE_GAMEMOD_TRUE:
-						if(eventType == XmlPullParser.TEXT){
-							gamemod |= mask;
-							gamemod <<= 1;
-						}else if(eventType == XmlPullParser.END_TAG) state = STATE_GAMEMOD;
-						else throwException(file, eventType);
-						break;
-					}
-					eventType = getEventType(xmlPuller);
-				}//end while(eventtype != END_DOCUMENT
-				schemes.add(new Scheme(name, basic, gamemod));
-			}//end for(string file : files
-			return schemes;
-		} catch (XmlPullParserException e) {
-			e.printStackTrace();
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
+	public static Scheme createDefaultScheme(MetaScheme meta) {
+		String name = GameConfig.DEFAULT_SCHEME;
+		Map<String, Integer> settings = new TreeMap<String, Integer>();
+		Map<String, Boolean> mods = new TreeMap<String, Boolean>();
+		for(MetaScheme.Setting setting : meta.settings) {
+			settings.put(setting.name, setting.def);
 		}
-		return new ArrayList<Scheme>();//TODO handle correctly
+		for(MetaScheme.Mod mod : meta.mods) {
+			mods.put(mod.name, Boolean.FALSE);
+		}
+		return new Scheme(name, settings, mods);
+	}
+	
+	@Override
+	public String toString() {
+		return "Scheme [name=" + name + ", settings=" + settings + ", mods="
+				+ mods + "]";
 	}
 	
-	private static FilenameFilter fnf = new FilenameFilter(){
-		public boolean accept(File dir, String filename) {
-			return filename.toLowerCase().startsWith("scheme_");
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result + ((mods == null) ? 0 : mods.hashCode());
+		result = prime * result
+				+ ((settings == null) ? 0 : settings.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Scheme other = (Scheme) obj;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (mods == null) {
+			if (other.mods != null)
+				return false;
+		} else if (!mods.equals(other.mods))
+			return false;
+		if (settings == null) {
+			if (other.settings != null)
+				return false;
+		} else if (!settings.equals(other.settings))
+			return false;
+		return true;
+	}
+
+	public static final Comparator<Scheme> NAME_ORDER = new Comparator<Scheme>() {
+		public int compare(Scheme lhs, Scheme rhs) {
+			return String.CASE_INSENSITIVE_ORDER.compare(lhs.name, rhs.name);
 		}
 	};
-
-	/**
-	 * This method will parse the basic flags from a prespecified xml file.
-	 * I use a raw xml file rather than one parsed by aatp at compile time
-	 * to keep it generic with other frontends, ie in the future we could 
-	 * use one provided by the Data folder.
-	 */
-	public static void parseBasicFlags(Context c){
-		String filename = String.format("%s/%s/basicflags", c.getFilesDir().getAbsolutePath(), DIRECTORY_SCHEME);
-
-		XmlPullParser xmlPuller = null;
-		BufferedReader br = null;
-		try {
-			XmlPullParserFactory xmlPullFactory = XmlPullParserFactory.newInstance();
-			xmlPuller = xmlPullFactory.newPullParser();
-			br = new BufferedReader(new FileReader(filename), 1024);
-			xmlPuller.setInput(br);
-
-			int eventType = getEventType(xmlPuller);
-			boolean continueParsing = true;
-			do{
-				switch(eventType){
-				
-				case XmlPullParser.START_TAG:
-					if(xmlPuller.getName().toLowerCase().equals("flag")){
-						basicflags.add(parseFlag(xmlPuller));
-					}else if(xmlPuller.getName().toLowerCase().equals("basicflags")){
-						eventType = getEventType(xmlPuller);
-					}else{
-						skipCurrentTag(xmlPuller);
-						eventType = getEventType(xmlPuller);
-					}
-					break;
-				case XmlPullParser.START_DOCUMENT://ignore all tags not being "flag"
-				case XmlPullParser.END_TAG:
-				case XmlPullParser.TEXT:
-				default:
-					continueParsing = true;
-				case XmlPullParser.END_DOCUMENT:
-					continueParsing = false;
-				}
-			}while(continueParsing);
-
-		}catch(IOException e){
-			e.printStackTrace();
-		}catch (XmlPullParserException e) {
-			e.printStackTrace();
-		}finally{
-			if(br != null)
-				try {
-					br.close();
-				} catch (IOException e) {}
-		}
-
-	}
-
-	/*
-	 * * Parses a Tag structure from xml as example we use
-	 *<flag>
-	 *   <checkOverMax>
-	 *       <boolean>false</boolean>
-	 *   </checkOverMax>
-	 *</flag>
-	 *
-	 * It returns a LinkedHashMap with key/value pairs
-	 */
-	private static LinkedHashMap<String, Object> parseFlag(XmlPullParser xmlPuller)throws XmlPullParserException, IOException{
-		LinkedHashMap<String, Object> hash = new LinkedHashMap<String, Object>();
-
-		int eventType = xmlPuller.getEventType();//Get the event type which triggered this method
-		if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().toLowerCase().equals("flag")){//valid start of flag tag
-			String lcKey = null;
-			String lcType = null;
-			String value = null;
-
-			eventType = getEventType(xmlPuller);//<checkOverMax>
-			while(eventType == XmlPullParser.START_TAG){
-				lcKey = xmlPuller.getName();//checkOverMax
-				if(getEventType(xmlPuller) == XmlPullParser.START_TAG){//<boolean>
-					lcType = xmlPuller.getName().toLowerCase();
-					if(getEventType(xmlPuller) == XmlPullParser.TEXT){
-						value = xmlPuller.getText();
-						if(getEventType(xmlPuller) == XmlPullParser.END_TAG && //</boolean> 
-								getEventType(xmlPuller) == XmlPullParser.END_TAG){//</checkOverMax>
-							if(lcType.equals("boolean")) hash.put(lcKey, new Boolean(value));
-							else if(lcType.equals("string"))hash.put(lcKey, value);							
-							else if(lcType.equals("integer")){
-								try{
-									hash.put(lcKey, new Integer(value));
-								}catch (NumberFormatException e){
-									throw new XmlPullParserException("Wrong integer value in xml file");
-								}
-							}else{
-								throwException("basicflags", eventType);
-							}
-						}//</boolean> / </checkOverMax>
-					}//if TEXT
-				}//if boolean
-				eventType = getEventType(xmlPuller);//start new loop
-			}
-			eventType = getEventType(xmlPuller);//</flag>
-		}
-
-		return hash;
-	}
-
-	private static void skipCurrentTag(XmlPullParser xmlPuller) throws XmlPullParserException, IOException{
-		int eventType = xmlPuller.getEventType();
-		if(eventType != XmlPullParser.START_TAG)return;
-		String tag = xmlPuller.getName().toLowerCase();
-
-		while(true){
-			eventType = getEventType(xmlPuller);//getNext()
-			switch(eventType){
-			case XmlPullParser.START_DOCUMENT://we're inside of a start tag so START_ or END_DOCUMENT is just wrong
-			case XmlPullParser.END_DOCUMENT:
-				throw new XmlPullParserException("invalid xml file");
-			case XmlPullParser.START_TAG://if we get a new tag recursively handle it
-				skipCurrentTag(xmlPuller);
-				break;
-			case XmlPullParser.TEXT:
-				break;
-			case XmlPullParser.END_TAG:
-				if(!xmlPuller.getName().toLowerCase().equals(tag)){//if the end tag doesn't match the start tag
-					throw new XmlPullParserException("invalid xml file");
-				}else{
-					return;//skip completed	
-				}
-
-			}
-		}
-	}
-
-	/**
-	 * Skips whitespaces..
-	 */
-	private static int getEventType(XmlPullParser xmlPuller)throws XmlPullParserException, IOException{
-		int eventType = xmlPuller.next();
-		while(eventType == XmlPullParser.TEXT && xmlPuller.isWhitespace()){
-			eventType = xmlPuller.next();
-		}
-		return eventType;
-	}
-	private static void throwException(String file, int eventType){
-		throw new IllegalArgumentException(String.format("Xml file: %s malformed with error: %d.", file, eventType));
-	}
-
-	public int describeContents() {
-		return 0;
-	}
-
-	public void writeToParcel(Parcel dest, int flags) {
-		dest.writeString(name);
-		dest.writeInt(gamemod);
-		dest.writeList(basic);
-	}
-	
-	public void readFromParcel(Parcel src){
-		name = src.readString();
-		gamemod = src.readInt();
-		basic = src.readArrayList(ArrayList.class.getClassLoader());
-	}
-
-	public static final Parcelable.Creator<Scheme> CREATOR = new Parcelable.Creator<Scheme>() {
-		public Scheme createFromParcel(Parcel source) {
-			return new Scheme(source);
-		}
-		public Scheme[] newArray(int size) {
-			return new Scheme[size];
-		}
-		
-	};
-
-	public int compareTo(Scheme another) {
-		return name.compareTo(another.name);
-	}
-}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Schemes.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,75 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.SchemelistPtr;
+
+import android.content.Context;
+
+/**
+ * Functions for handling the persistent list of schemes.
+ * Schemes in that list are identified by name (case sensitive).
+ */
+public final class Schemes {
+	private Schemes() {
+		throw new AssertionError("This class is not meant to be instantiated");
+	}
+	
+	public static File getUserSchemesFile(Context c) {
+		return new File(c.getFilesDir(), "schemes_user.ini");
+	}
+	
+	public static File getBuiltinSchemesFile(Context c) {
+		return new File(c.getFilesDir(), "schemes_builtin.ini");
+	}
+	
+	public static List<Scheme> loadAllSchemes(Context c) throws IOException {
+		List<Scheme> result = loadBuiltinSchemes(c);
+		result.addAll(loadUserSchemes(c));
+		return result;
+	}
+	
+	public static List<Scheme> loadUserSchemes(Context c) throws IOException {
+		return loadSchemes(c, getUserSchemesFile(c));
+	}
+	
+	public static List<Scheme> loadBuiltinSchemes(Context c) throws IOException {
+		return loadSchemes(c, getBuiltinSchemesFile(c));
+	}
+	
+	public static List<Scheme> loadSchemes(Context c, File schemeFile) throws IOException {
+		if(!schemeFile.isFile()) {
+			// No schemes file == no schemes, no error
+			return new ArrayList<Scheme>();
+		}
+		SchemelistPtr schemeListPtr = null;
+		try {
+			schemeListPtr = Flib.INSTANCE.flib_schemelist_from_ini(schemeFile.getAbsolutePath());
+			if(schemeListPtr == null) {
+				throw new IOException("Unable to read schemelist");
+			}
+			return schemeListPtr.deref();
+		} finally {
+			if(schemeListPtr != null) {
+				Flib.INSTANCE.flib_schemelist_destroy(schemeListPtr);
+			}
+		}
+	}
+	
+	public static void saveUserSchemes(Context c, List<Scheme> schemes) throws IOException {
+		SchemelistPtr ptr = SchemelistPtr.createJavaOwned(schemes);
+		Flib.INSTANCE.flib_schemelist_to_ini(getUserSchemesFile(c).getAbsolutePath(), ptr);
+	}
+	
+	public static List<String> toNameList(List<Scheme> schemes) {
+		List<String> result = new ArrayList<String>();
+		for(Scheme scheme : schemes) {
+			result.add(scheme.name);
+		}
+		return result;
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Team.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Team.java	Sat Aug 18 00:48:09 2012 +0200
@@ -18,351 +18,73 @@
 
 package org.hedgewars.hedgeroid.Datastructures;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
 
-import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork;
 import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-import org.xmlpull.v1.XmlSerializer;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamPtr;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Xml;
 
-public class Team implements Parcelable{
-
+public final class Team {
 	public static final String DIRECTORY_TEAMS = "teams";
-	private static final Integer[] TEAM_COLORS = {
-		0xd12b42, /* red    */ 
-		0x4980c1, /* blue   */ 
-		0x6ab530, /* green  */ 
-		0xbc64c4, /* purple */ 
-		0xe76d14, /* orange */ 
-		0x3fb6e6, /* cyan   */ 
-		0xe3e90c, /* yellow */ 
-		0x61d4ac, /* mint   */ 
-		0xf1c3e1, /* pink   */ 
-		/* add new colors here */
-	};
+
+	public static final int HEDGEHOGS_PER_TEAM = Flib.INSTANCE.flib_get_hedgehogs_per_team();
+	public static final int maxNumberOfTeams = PascalExports.HWgetMaxNumberOfTeams();
+
+	public final String name, grave, flag, voice, fort;
+	public final List<Hog> hogs;
 
-//	private static final Integer[] TEAM_COLORS = {
-//		0xff0000, /* red    */ 
-//		0x00ff00, /* blue   */ 
-//		0x0000ff, /* green  */ 
-//	};
-
-	private static final int STATE_START = 0;
-	private static final int STATE_ROOT = 1;
-	private static final int STATE_HOG_ROOT = 2;
-
-	public String name, grave, flag, voice, fort, hash;
-	public String file = null;
-
-	public static int maxNumberOfHogs = 0;
-	public static int maxNumberOfTeams = 0;
-
-	static{
-		maxNumberOfHogs = PascalExports.HWgetMaxNumberOfHogs();
-		maxNumberOfTeams = PascalExports.HWgetMaxNumberOfTeams();
-	}
-	public String[] hats = new String[maxNumberOfHogs];
-	public String[] hogNames = new String[maxNumberOfHogs];
-	public int[] levels = new int[maxNumberOfHogs];
-
-	public int hogCount = 4;
-	public int color = TEAM_COLORS[0];
-
-	public Team(){
+	public Team(String name, String grave, String flag, String voice, String fort, List<Hog> hogs) {
+		if(hogs.size() != HEDGEHOGS_PER_TEAM) {
+			throw new IllegalArgumentException("A team must consist of "+HEDGEHOGS_PER_TEAM+" hogs.");
+		}
+		this.name = name;
+		this.grave = grave;
+		this.flag = flag;
+		this.voice = voice;
+		this.fort = fort;
+		this.hogs = Collections.unmodifiableList(new ArrayList<Hog>(hogs));
 	}
 
-	public Team(Parcel in){
-		readFromParcel(in);
-	}
-
-	public boolean equals(Object o){
-		if(super.equals(o)) return true;
-		else if(o instanceof Team){
-			Team t = (Team)o;
-			boolean ret = name.equals(t.name);
-			ret &= grave.equals(t.grave);
-			ret &= flag.equals(t.flag);
-			ret &= voice.equals(t.voice);
-			ret &= fort.equals(t.fort);
-			ret &= hash.equals(t.hash);
-			return ret;
-		}else{
-			return false;
+	public void save(File f) throws IOException {
+		TeamPtr teamPtr = TeamPtr.createJavaOwned(this);
+		if(Flib.INSTANCE.flib_team_to_ini(f.getAbsolutePath(), teamPtr) != 0) {
+			throw new IOException("Error saving team "+name);
 		}
 	}
 
-	public void setRandomColor(int[] illegalcolors){
-		Integer[] colorsToPickFrom = TEAM_COLORS;
-		if(illegalcolors != null){
-			ArrayList<Integer> colors = new ArrayList<Integer>();
-			for(int color : TEAM_COLORS){
-				boolean validColor = true;
-				for(int illegal : illegalcolors){
-					if(color == illegal) validColor = false;
-				}
-				if(validColor) colors.add(color);
-			}
-			if(colors.size() != 0) colorsToPickFrom = colors.toArray(new Integer[1]);
-		}
-		int index = (int)Math.round(Math.random()*(colorsToPickFrom.length-1));
-		color = colorsToPickFrom[index];
-	}
-
-
-	public void sendToEngine(EngineProtocolNetwork epn, int hogCount, int health) throws IOException{
-		epn.sendToEngine(String.format("eaddteam %s %d %s", hash, color, name));
-		epn.sendToEngine(String.format("egrave %s", grave));
-		epn.sendToEngine(String.format("efort %s", fort));
-		epn.sendToEngine(String.format("evoicepack %s", voice));
-		epn.sendToEngine(String.format("eflag %s", flag));
-
-		for(int i = 0; i < hogCount; i++){
-			epn.sendToEngine(String.format("eaddhh %d %d %s", levels[i], health, hogNames[i]));
-			epn.sendToEngine(String.format("ehat %s", hats[i]));
-		}
-	}
-
-	public void setFileName(Context c){
-		if(file == null){
-		  	file = validFileName(c, name);
-		}
-	}
-	private String validFileName(Context c, String fileName){
-		String absolutePath = String.format("%s/%s", c.getFilesDir(), fileName);
-		File f = new File(absolutePath);
-		if(f.exists()){
-			String newFileName = fileName + (int)(Math.random()*10);
-			return validFileName(c, newFileName);
-		}else{
-			return fileName;
+	public static Team load(File f) {
+		TeamPtr teamPtr = Flib.INSTANCE.flib_team_from_ini(f.getAbsolutePath());
+		if(teamPtr != null) {
+			Team team = teamPtr.deref().team;
+			Flib.INSTANCE.flib_team_destroy(teamPtr);
+			return team;
+		} else {
+			return null;
 		}
 	}
 	
-	/*
-	 * XML METHODS
-	 */
-
-	/**
-	 * Read the xml file path and convert it to a Team object
-	 * @param path absolute path to the xml file
-	 * @return
-	 */
-	public static Team getTeamFromXml(String path){
-		try {
-			XmlPullParserFactory xmlPullFactory = XmlPullParserFactory.newInstance();
-			XmlPullParser xmlPuller = xmlPullFactory.newPullParser();
-
-			BufferedReader br = new BufferedReader(new FileReader(path), 1024);
-			xmlPuller.setInput(br);
-			Team team = new Team();
-			int hogCounter = 0;
-
-			int eventType = xmlPuller.getEventType();
-			int state = STATE_START;
-			while(eventType != XmlPullParser.END_DOCUMENT){
-				switch(state){
-				case STATE_START:
-					if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().equals("team")) state = STATE_ROOT;
-					else if(eventType != XmlPullParser.START_DOCUMENT) throwException(path, eventType);
-					break;
-				case STATE_ROOT:
-					if(eventType == XmlPullParser.START_TAG){
-						if(xmlPuller.getName().toLowerCase().equals("name")){
-							team.name = getXmlText(xmlPuller, "name");
-						}else if(xmlPuller.getName().toLowerCase().equals("flag")){
-							team.flag= getXmlText(xmlPuller, "flag");
-						}else if(xmlPuller.getName().toLowerCase().equals("voice")){
-							team.voice = getXmlText(xmlPuller, "voice");
-						}else if(xmlPuller.getName().toLowerCase().equals("grave")){
-							team.grave = getXmlText(xmlPuller, "grave");
-						}else if(xmlPuller.getName().toLowerCase().equals("fort")){
-							team.fort = getXmlText(xmlPuller, "fort");
-						}else if(xmlPuller.getName().toLowerCase().equals("hash")){
-							team.hash = getXmlText(xmlPuller, "hash");
-						}else if(xmlPuller.getName().toLowerCase().equals("hog")){
-							state = STATE_HOG_ROOT;
-						}else throwException(xmlPuller.getName(), eventType);
-					}else if(eventType == XmlPullParser.END_TAG) state = STATE_START;
-					else throwException(xmlPuller.getText(), eventType);
-					break;
-				case STATE_HOG_ROOT:
-					if(eventType == XmlPullParser.START_TAG){
-						if(xmlPuller.getName().toLowerCase().equals("name")){
-							team.hogNames[hogCounter] = getXmlText(xmlPuller, "name");
-						}else if(xmlPuller.getName().toLowerCase().equals("hat")){
-							team.hats[hogCounter] = getXmlText(xmlPuller, "hat");
-						}else if(xmlPuller.getName().toLowerCase().equals("level")){
-							team.levels[hogCounter] = Integer.parseInt(getXmlText(xmlPuller, "level"));
-						}else throwException(xmlPuller.getText(), eventType);
-					}else if(eventType == XmlPullParser.END_TAG){
-						hogCounter++;
-						state = STATE_ROOT;
-					}else throwException(xmlPuller.getText(), eventType);
-					break;
-				}
-				eventType = getEventType(xmlPuller);
-			}//end while(eventtype != END_DOCUMENT
-			return team;
-		} catch (NumberFormatException e){
-			e.printStackTrace();
-		} catch (XmlPullParserException e) {
-			e.printStackTrace();
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
-
-	private static String getXmlText(XmlPullParser xmlPuller, String parentTag)throws XmlPullParserException, IOException{
-		if(getEventType(xmlPuller) == XmlPullParser.TEXT){
-			String txt = xmlPuller.getText();
-			if(getEventType(xmlPuller) == XmlPullParser.END_TAG && xmlPuller.getName().toLowerCase().equals(parentTag)){
-				return txt;
-			}
-		}
-		throw new XmlPullParserException("malformed xml file on string read from tag: " + parentTag);
-	}
-
-	/**
-	 * Skips whitespaces..
-	 */
-	private static int getEventType(XmlPullParser xmlPuller)throws XmlPullParserException, IOException{
-		int eventType = xmlPuller.next();
-		while(eventType == XmlPullParser.TEXT && xmlPuller.isWhitespace()){
-			eventType = xmlPuller.next();
-		}
-		return eventType;
-	}
-
-	private static void throwException(String file, int eventType){
-		throw new IllegalArgumentException(String.format("Xml file: %s malformed with error: %d.", file, eventType));
+	public static File getTeamfileByName(Context c, String teamName) {
+		return new File(new File(c.getFilesDir(), DIRECTORY_TEAMS), FileUtils.replaceBadChars(teamName)+".hwt");
 	}
-
-	public void writeToXml(OutputStream os){
-		XmlSerializer serializer = Xml.newSerializer();
-		try{
-			serializer.setOutput(os, "UTF-8");	
-			serializer.startDocument("UTF-8", true);
-			serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
-			serializer.startTag(null, "team");
-			serializer.startTag(null, "name");
-			serializer.text(name);
-			serializer.endTag(null, "name");
-			serializer.startTag(null, "flag");
-			serializer.text(flag);
-			serializer.endTag(null, "flag");
-			serializer.startTag(null, "fort");
-			serializer.text(fort);
-			serializer.endTag(null, "fort");
-			serializer.startTag(null, "grave");
-			serializer.text(grave);
-			serializer.endTag(null, "grave");
-			serializer.startTag(null, "voice");
-			serializer.text(voice);
-			serializer.endTag(null, "voice");
-			serializer.startTag(null, "hash");
-			serializer.text(hash);
-			serializer.endTag(null, "hash");
-
-			for(int i = 0; i < maxNumberOfHogs; i++){
-				serializer.startTag(null, "hog");
-				serializer.startTag(null, "name");
-				serializer.text(hogNames[i]);
-				serializer.endTag(null, "name");
-				serializer.startTag(null, "hat");
-				serializer.text(hats[i]);
-				serializer.endTag(null, "hat");
-				serializer.startTag(null, "level");
-				serializer.text(String.valueOf(levels[i]));
-				serializer.endTag(null, "level");
-
-				serializer.endTag(null, "hog");
-			}
-			serializer.endTag(null, "team");
-			serializer.endDocument();
-			serializer.flush();
-
-		} catch (IOException e) {
-			e.printStackTrace();
-		}finally{
-			try {
-				os.close();
-			} catch (IOException e) {}
-		}
+	
+	@Override
+	public String toString() {
+		return "Team [name=" + name + ", grave=" + grave + ", flag=" + flag
+				+ ", voice=" + voice + ", fort=" + fort + ", hogs=" + hogs
+				+ "]";
 	}
-	/*
-	 * END XML METHODS
-	 */
-
-
-
-	/*
-	 * PARCABLE METHODS
-	 */
-
-	public int describeContents() {
-		return 0;
-	}
-
-	public void writeToParcel(Parcel dest, int flags) {
-		dest.writeString(name);
-		dest.writeString(grave);
-		dest.writeString(flag);
-		dest.writeString(voice);
-		dest.writeString(fort);
-		dest.writeString(hash);
-		dest.writeStringArray(hats);
-		dest.writeStringArray(hogNames);
-		dest.writeIntArray(levels);
-		dest.writeInt(color);
-		dest.writeInt(hogCount);
-		dest.writeString(file);
-	}
-
-
-	public void readFromParcel(Parcel src){
-		name = src.readString();
-		grave = src.readString();
-		flag = src.readString();
-		voice = src.readString();
-		fort = src.readString();
-		hash = src.readString();
-		src.readStringArray(hats);
-		src.readStringArray(hogNames);
-		src.readIntArray(levels);
-		color = src.readInt();
-		hogCount = src.readInt();
-		file = src.readString();
-	}
-
-	public static final Parcelable.Creator<Team> CREATOR = new Parcelable.Creator<Team>() {
-		public Team createFromParcel(Parcel source) {
-			return new Team(source);
+	
+	public static Comparator<Team> NAME_ORDER = new Comparator<Team>() {
+		public int compare(Team lhs, Team rhs) {
+			return lhs.name.compareToIgnoreCase(rhs.name);
 		}
-		public Team[] newArray(int size) {
-			return new Team[size];
-		}
-
 	};
-
-	/*
-	 * END PARCABLE METHODS
-	 */
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamInGame.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,39 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.Collection;
+import java.util.Comparator;
+
+/**
+ * A team with per-game configuration. This is similar to the frontlib "team" structure,
+ * except that it does not include weaponset and initial health, which are handled on a
+ * per-game basis in the UI, but per-hog in the frontlib.
+ */
+public final class TeamInGame {
+	public final Team team;
+	public final TeamIngameAttributes ingameAttribs;
+	
+	public TeamInGame(Team team, TeamIngameAttributes ingameAttribs) {
+		this.team = team;
+		this.ingameAttribs = ingameAttribs;
+	}
+	
+	public TeamInGame withAttribs(TeamIngameAttributes attribs) {
+		return new TeamInGame(team, attribs);
+	}
+	
+	public static int getUnusedOrRandomColorIndex(Collection<TeamInGame> teams) {
+		int[] illegalColors = new int[teams.size()];
+		int i=0;
+		for(TeamInGame team : teams) {
+			illegalColors[i] = team.ingameAttribs.colorIndex;
+			i++;
+		}
+		return TeamIngameAttributes.randomColorIndex(illegalColors);
+	}
+	
+	public static Comparator<TeamInGame> NAME_ORDER = new Comparator<TeamInGame>() {
+		public int compare(TeamInGame lhs, TeamInGame rhs) {
+			return Team.NAME_ORDER.compare(lhs.team, rhs.team);
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamIngameAttributes.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,61 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.hedgewars.hedgeroid.frontlib.Flib;
+
+public final class TeamIngameAttributes {
+	public static final int DEFAULT_HOG_COUNT = 4;
+	public static final int[] TEAM_COLORS;
+	
+	static {
+		int[] teamColors = new int[Flib.INSTANCE.flib_get_teamcolor_count()];
+		for(int i=0; i<teamColors.length; i++) {
+			teamColors[i] = Flib.INSTANCE.flib_get_teamcolor(i);
+		}
+		TEAM_COLORS = teamColors;
+	}
+	
+	public final String ownerName;
+	public final int colorIndex, hogCount;
+	public final boolean remoteDriven;
+	
+	public TeamIngameAttributes(String ownerName, int colorIndex, int hogCount, boolean remoteDriven) {
+		this.ownerName = ownerName;
+		this.colorIndex = colorIndex;
+		this.hogCount = hogCount;
+		this.remoteDriven = remoteDriven;
+	}
+	
+	public static int randomColorIndex(int[] illegalColors) {
+		Random rnd = new Random();
+		ArrayList<Integer> legalcolors = new ArrayList<Integer>();
+		for(int i=0; i<TEAM_COLORS.length; i++) {
+			legalcolors.add(i);
+		}
+		for(int illegalColor : illegalColors) {
+			legalcolors.remove(Integer.valueOf(illegalColor));
+		}
+		if(legalcolors.isEmpty()) {
+			return rnd.nextInt(TEAM_COLORS.length);
+		} else {
+			return legalcolors.get(rnd.nextInt(legalcolors.size()));
+		}
+	}
+	
+	public TeamIngameAttributes withColorIndex(int colorIndex) {
+		return new TeamIngameAttributes(ownerName, colorIndex, hogCount, remoteDriven);
+	}
+	
+	public TeamIngameAttributes withHogCount(int hogCount) {
+		return new TeamIngameAttributes(ownerName, colorIndex, hogCount, remoteDriven);
+	}
+
+	@Override
+	public String toString() {
+		return "TeamIngameAttributes [ownerName=" + ownerName + ", colorIndex="
+				+ colorIndex + ", hogCount=" + hogCount + ", remoteDriven="
+				+ remoteDriven + "]";
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weapon.java	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-/*
- * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
- * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
-package org.hedgewars.hedgeroid.Datastructures;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-
-import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork;
-import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class Weapon implements Parcelable, Comparable<Weapon>{
-
-	public static final String DIRECTORY_WEAPON = "weapons";
-	
-	private String name;
-	private String QT;
-	private String prob;
-	private String delay;
-	private String crate;
-	private static int maxWeapons;
-	
-	static{
-		maxWeapons = PascalExports.HWgetNumberOfWeapons();
-	}
-	
-	public Weapon(String _name, String _QT, String _prob, String _delay, String _crate){
-		name = _name;
-		
-		//Incase there's a newer ammoStore which is bigger we append with zeros
-		StringBuffer sb = new StringBuffer();
-		while(_QT.length() + sb.length() < maxWeapons){
-			sb.append('0');
-		}
-		
-		QT = String.format("e%s %s%s", "ammloadt", _QT, sb);
-		prob = String.format("e%s %s%s", "ammprob", _prob, sb);
-		delay = String.format("e%s %s%s", "ammdelay", _delay, sb);
-		crate = String.format("e%s %s%s", "ammreinf", _crate, sb);
-	}
-	
-	public Weapon(Parcel in){
-		readFromParcel(in);
-	}
-	
-	public String toString(){
-		return name;
-	}
-	
-	public void sendToEngine(EngineProtocolNetwork epn, int teamsCount) throws IOException{
-		epn.sendToEngine(QT);//command prefix is already in string 
-		epn.sendToEngine(prob);
-		epn.sendToEngine(delay);
-		epn.sendToEngine(crate);
-		
-		for(int i = 0; i < teamsCount; i++){
-			epn.sendToEngine("eammstore");
-		}
-	}
-	
-	public static final int STATE_START = 0;
-	public static final int STATE_ROOT = 1;
-	public static final int STATE_NAME = 2;
-	public static final int STATE_QT = 3;
-	public static final int STATE_PROBABILITY = 4;
-	public static final int STATE_DELAY = 5;
-	public static final int STATE_CRATE = 6;
-	
-	public static ArrayList<Weapon> getWeapons(Context c) throws IllegalArgumentException{
-		String dir = c.getFilesDir().getAbsolutePath() + '/' + DIRECTORY_WEAPON + '/';
-		String[] files = new File(dir).list();
-		if(files == null) files = new String[]{};
-		
-		ArrayList<Weapon> weapons = new ArrayList<Weapon>();
-
-		try {
-			XmlPullParserFactory xmlPullFactory = XmlPullParserFactory.newInstance();
-			XmlPullParser xmlPuller = xmlPullFactory.newPullParser();
-			
-			for(String file : files){
-				BufferedReader br = new BufferedReader(new FileReader(dir + file), 1024);
-				xmlPuller.setInput(br);
-				String name = null;
-				String qt = null;
-				String prob = null;
-				String delay = null;
-				String crate = null;
-				
-				int eventType = xmlPuller.getEventType();
-				int state = STATE_START;
-				while(eventType != XmlPullParser.END_DOCUMENT){
-					switch(state){
-					case STATE_START:
-						if(eventType == XmlPullParser.START_TAG && xmlPuller.getName().equals("weapon")) state = STATE_ROOT;
-						else if(eventType != XmlPullParser.START_DOCUMENT) throwException(file, eventType);
-						break;
-					case STATE_ROOT:
-						if(eventType == XmlPullParser.START_TAG){
-							if(xmlPuller.getName().toLowerCase().equals("qt")) state = STATE_QT;
-							else if(xmlPuller.getName().toLowerCase().equals("name")) state = STATE_NAME;
-							else if(xmlPuller.getName().toLowerCase().equals("probability")) state = STATE_PROBABILITY;
-							else if(xmlPuller.getName().toLowerCase().equals("delay")) state = STATE_DELAY;
-							else if(xmlPuller.getName().toLowerCase().equals("crate")) state = STATE_CRATE;
-							else throwException(file, eventType);
-						}else if(eventType == XmlPullParser.END_TAG) state = STATE_START;
-						else throwException(xmlPuller.getText(), eventType);
-						break;
-					case STATE_NAME:
-						if(eventType == XmlPullParser.TEXT) name = xmlPuller.getText().trim();
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_QT:
-						if(eventType == XmlPullParser.TEXT) qt = xmlPuller.getText().trim();
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_PROBABILITY:
-						if(eventType == XmlPullParser.TEXT) prob = xmlPuller.getText().trim();
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_DELAY:
-						if(eventType == XmlPullParser.TEXT) delay = xmlPuller.getText().trim();
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					case STATE_CRATE:
-						if(eventType == XmlPullParser.TEXT) crate = xmlPuller.getText().trim();
-						else if(eventType == XmlPullParser.END_TAG) state = STATE_ROOT;
-						else throwException(file, eventType);
-						break;
-					}
-					eventType = xmlPuller.next();
-					while(eventType == XmlPullParser.TEXT && xmlPuller.isWhitespace()){//Skip whitespaces
-						eventType = xmlPuller.next();
-					}
-				}//end while(eventtype != END_DOCUMENT
-				weapons.add(new Weapon(name, qt, prob, delay, crate));
-			}//end for(string file : files
-			return weapons;
-			
-		} catch (XmlPullParserException e) {
-			e.printStackTrace();
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-		return new ArrayList<Weapon>();//TODO handle correctly
-	}
-	
-	private static void throwException(String file, int eventType){
-		throw new IllegalArgumentException(String.format("Xml file: %s malformed with eventType: %d.", file, eventType));
-	}
-
-	public int describeContents() {
-		return 0;
-	}
-
-	public void writeToParcel(Parcel dest, int flags) {
-		dest.writeString(name);
-		dest.writeString(QT);
-		dest.writeString(prob);
-		dest.writeString(delay);
-		dest.writeString(crate);
-	}
-	
-	private void readFromParcel(Parcel src){
-		name = src.readString();
-		QT = src.readString();
-		prob = src.readString();
-		delay = src.readString();
-		crate = src.readString();
-	}
-	
-	public static final Parcelable.Creator<Weapon> CREATOR = new Parcelable.Creator<Weapon>() {
-		public Weapon createFromParcel(Parcel source) {
-			return new Weapon(source);
-		}
-		public Weapon[] newArray(int size) {
-			return new Weapon[size];
-		}
-		
-	};
-
-	public int compareTo(Weapon another) {
-		return name.compareTo(another.name);
-	}
-	
-	
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponset.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,83 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.frontlib.Flib;
+
+public final class Weaponset {
+	public static final int WEAPONS_COUNT = Flib.INSTANCE.flib_get_weapons_count();
+	
+	public final String name, loadout, crateProb, crateAmmo, delay;
+	
+	public Weaponset(String name, String loadout, String crateProb, String crateAmmo, String delay) {
+		this.name = name;
+		this.loadout = loadout;
+		this.crateProb = crateProb;
+		this.crateAmmo = crateAmmo;
+		this.delay = delay;
+	}
+
+	@Override
+	public String toString() {
+		return "Weaponset [name=" + name + ", loadout=" + loadout
+				+ ", crateProb=" + crateProb + ", crateAmmo=" + crateAmmo
+				+ ", delay=" + delay + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((crateAmmo == null) ? 0 : crateAmmo.hashCode());
+		result = prime * result
+				+ ((crateProb == null) ? 0 : crateProb.hashCode());
+		result = prime * result + ((delay == null) ? 0 : delay.hashCode());
+		result = prime * result + ((loadout == null) ? 0 : loadout.hashCode());
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Weaponset other = (Weaponset) obj;
+		if (crateAmmo == null) {
+			if (other.crateAmmo != null)
+				return false;
+		} else if (!crateAmmo.equals(other.crateAmmo))
+			return false;
+		if (crateProb == null) {
+			if (other.crateProb != null)
+				return false;
+		} else if (!crateProb.equals(other.crateProb))
+			return false;
+		if (delay == null) {
+			if (other.delay != null)
+				return false;
+		} else if (!delay.equals(other.delay))
+			return false;
+		if (loadout == null) {
+			if (other.loadout != null)
+				return false;
+		} else if (!loadout.equals(other.loadout))
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		return true;
+	}
+
+	public static Comparator<Weaponset> NAME_ORDER = new Comparator<Weaponset>() {
+		public int compare(Weaponset lhs, Weaponset rhs) {
+			return String.CASE_INSENSITIVE_ORDER.compare(lhs.name, rhs.name);
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponsets.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,84 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.WeaponsetListPtr;
+
+import android.content.Context;
+
+public final class Weaponsets {
+	private Weaponsets() {
+		throw new AssertionError("This class is not meant to be instantiated");
+	}
+	
+	public static File getUserWeaponsetsFile(Context c) {
+		return new File(c.getFilesDir(), "weapons_user.ini");
+	}
+	
+	public static File getBuiltinWeaponsetsFile(Context c) {
+		return new File(c.getFilesDir(), "weapons_builtin.ini");
+	}
+	
+	public static List<Weaponset> loadAllWeaponsets(Context c) throws IOException {
+		List<Weaponset> result = loadBuiltinWeaponsets(c);
+		result.addAll(loadUserWeaponsets(c));
+		return result;
+	}
+	
+	public static List<Weaponset> loadUserWeaponsets(Context c) throws IOException {
+		return loadWeaponsets(c, getUserWeaponsetsFile(c));
+	}
+	
+	public static List<Weaponset> loadBuiltinWeaponsets(Context c) throws IOException {
+		return loadWeaponsets(c, getBuiltinWeaponsetsFile(c));
+	}
+	
+	public static List<Weaponset> loadWeaponsets(Context c, File weaponsetFile) throws IOException {
+		if(!weaponsetFile.isFile()) {
+			// No file == no weaponsets, no error
+			return new ArrayList<Weaponset>();
+		}
+		WeaponsetListPtr weaponsetListPtr = null;
+		try {
+			weaponsetListPtr = Flib.INSTANCE.flib_weaponsetlist_from_ini(weaponsetFile.getAbsolutePath());
+			if(weaponsetListPtr == null) {
+				throw new IOException("Unable to read weaponsets from "+weaponsetFile);
+			}
+			return weaponsetListPtr.deref();
+		} finally {
+			if(weaponsetListPtr != null) {
+				Flib.INSTANCE.flib_weaponsetlist_destroy(weaponsetListPtr);
+			}
+		}
+	}
+	
+	public static void saveUserWeaponsets(Context c, List<Weaponset> weaponsets) throws IOException {
+		WeaponsetListPtr ptr = WeaponsetListPtr.createJavaOwned(weaponsets);
+		Flib.INSTANCE.flib_weaponsetlist_to_ini(getUserWeaponsetsFile(c).getAbsolutePath(), ptr);
+	}
+	
+	public static void deleteUserWeaponset(Context c, String setToDelete) throws IOException {
+		List<Weaponset> userWeaponsets = loadUserWeaponsets(c);
+		for(Iterator<Weaponset> iter = userWeaponsets.iterator(); iter.hasNext();) {
+			Weaponset set = iter.next();
+			if(set.name.equals(setToDelete)) {
+				iter.remove();
+				break;
+			}
+		}
+		saveUserWeaponsets(c, userWeaponsets);
+	}
+	
+	public static List<String> toNameList(List<Weaponset> weaponsets) {
+		List<String> result = new ArrayList<String>();
+		for(Weaponset weaponset : weaponsets) {
+			result.add(weaponset.name);
+		}
+		return result;
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java	Sat Aug 18 00:48:09 2012 +0200
@@ -1,100 +1,63 @@
 package org.hedgewars.hedgeroid.Downloader;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 
 import org.hedgewars.hedgeroid.MainActivity;
 import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Utils;
-import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Schemes;
 import org.hedgewars.hedgeroid.Datastructures.Team;
-import org.hedgewars.hedgeroid.Datastructures.Weapon;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
-import android.content.Context;
 import android.content.res.AssetManager;
 import android.os.AsyncTask;
 import android.util.Log;
 
-public class DownloadAssets extends AsyncTask<Object, Long, Long>{
-	
-	private MainActivity act;
-	private static byte[] buffer = null;
-	
-	public DownloadAssets(MainActivity _act){
-		act = _act;
-	}
+public class DownloadAssets extends AsyncTask<Object, Long, Boolean> {
+	private static final String VERSION_FILENAME = "assetsversion.txt";
+	private final MainActivity act;
 	
-	public static Long copyFileOrDir(Context c, String path) {
-	    AssetManager assetManager = c.getAssets();
-	    String assets[] = null;
-	    try {
-	        assets = assetManager.list(path);
-	        if (assets.length == 0) {
-	            return DownloadAssets.copyFile(c, path);
-	        } else {
-	            String fullPath = Utils.getCachePath(c) + path;
-	            File dir = new File(fullPath);
-	            if (!dir.exists())
-	                dir.mkdir();
-	            for (int i = 0; i < assets.length; ++i) {
-	                Long result = DownloadAssets.copyFileOrDir(c, path + "/" + assets[i]);
-	                if(result > 0) return 1l;
-	            }
-	        }
-	    } catch (IOException ex) {
-	    	ex.printStackTrace();
-	        Log.e("tag", "I/O Exception", ex);
-	        return 1l;
-	    }
-	    return 0l;
+	public DownloadAssets(MainActivity act){
+		this.act = act;
 	}
 	
-	private static Long copyFile(Context c, String filename) {
-	    AssetManager assetManager = c.getAssets();
-
-	    InputStream in = null;
-	    OutputStream out = null;
-	    try {
-	        in = assetManager.open(filename);
-	        in = new BufferedInputStream(in, 8192);
-	        
-	        String newFileName = Utils.getCachePath(c) + filename;
-	        out = new FileOutputStream(newFileName);
-	        out = new BufferedOutputStream(out, 8192);
-
-	        int read;
-	        while ((read = in.read(buffer)) != -1) {
-	            out.write(buffer, 0, read);
-	        }
-	        in.close();
-	        in = null;
-	        out.flush();
-	        out.close();
-	        out = null;
-	    } catch (Exception e) {
-	    	e.printStackTrace();
-	        Log.e("tag", e.getMessage());
-	        return 1l;
-	    }
-	    return 0l;
-
-	}
-
-	protected Long doInBackground(Object... params) {
-		Utils.resRawToFilesDir(act,R.array.schemes, Scheme.DIRECTORY_SCHEME);
-		Utils.resRawToFilesDir(act, R.array.weapons, Weapon.DIRECTORY_WEAPON);
-		Utils.resRawToFilesDir(act, R.array.teams, Team.DIRECTORY_TEAMS);
-		buffer = new byte[8192];//allocate the buffer
-		return DownloadAssets.copyFileOrDir(act, "Data");
+	private void copyFileOrDir(AssetManager assetManager, File target, String assetPath) throws IOException {
+		try {
+			FileUtils.writeStreamToFile(assetManager.open(assetPath), target);
+		} catch(FileNotFoundException e) {
+			/*
+			 * I can't find a better way to figure out whether an asset entry is
+			 * a file or a directory. Checking if assetManager.list(assetPath)
+			 * is empty is a bit cleaner, but SLOW.
+			 */
+			if (!target.isDirectory() && !target.mkdir()) {
+				throw new IOException("Unable to create directory "+target);
+			}
+			for (String asset : assetManager.list(assetPath)) {
+				copyFileOrDir(assetManager, new File(target, asset), assetPath + "/" + asset);
+			}
+		}
 	}
 	
-	protected void onPostExecute(Long result){
-		act.onAssetsDownloaded(result == 0);
-		buffer = null;
+	@Override
+	protected Boolean doInBackground(Object... params) {
+		try {
+			FileUtils.writeStreamToFile(act.getResources().openRawResource(R.raw.schemes_builtin), Schemes.getBuiltinSchemesFile(act));
+			FileUtils.writeStreamToFile(act.getResources().openRawResource(R.raw.weapons_builtin), Weaponsets.getBuiltinWeaponsetsFile(act));
+			FileUtils.resRawToFilesDir(act, R.array.teams, R.array.teamFilenames, Team.DIRECTORY_TEAMS);
+			copyFileOrDir(act.getAssets(), FileUtils.getDataPathFile(act), "Data");
+			copyFileOrDir(act.getAssets(), new File(FileUtils.getCachePath(act), VERSION_FILENAME), VERSION_FILENAME);
+			return Boolean.TRUE;
+		} catch(IOException e) {
+			Log.e("DownloadAssets", e.getMessage(), e);
+			return Boolean.FALSE;
+		}
+	}
+	
+	@Override
+	protected void onPostExecute(Boolean result){
+		act.onAssetsDownloaded(result);
 	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAsyncTask.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAsyncTask.java	Sat Aug 18 00:48:09 2012 +0200
@@ -107,7 +107,7 @@
 				entry = input.getNextEntry();	
 			}catch(IOException e){
 				e.printStackTrace();
-				if(conn != null) conn.disconnect();
+				conn.disconnect();
 				return EXIT_CONNERROR;
 			}
 
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadListActivity.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadListActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -6,7 +6,6 @@
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.View.OnClickListener;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadPackage.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadPackage.java	Sat Aug 18 00:48:09 2012 +0200
@@ -20,7 +20,7 @@
 
 import java.io.IOException;
 
-import org.hedgewars.hedgeroid.Utils;
+import org.hedgewars.hedgeroid.util.FileUtils;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -29,7 +29,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.preference.PreferenceManager;
-import android.util.Log;
 
 public class DownloadPackage implements Parcelable{
 	private String url_without_suffix;
@@ -137,7 +136,7 @@
 							version = -1;
 						}
 					}else if(name.equals("path")){
-						path = Utils.getDataPath(c) + text;
+						path = FileUtils.getDataPath(c) + text;
 					}else if(name.equals("representation")){
 						representation = text;
 					}else if(name.equals("description")){
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/EngineProtocolNetwork.java	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-/*
- * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
- * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
-
-package org.hedgewars.hedgeroid.EngineProtocol;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
-public class EngineProtocolNetwork extends Thread{
-
-	public static final String GAMEMODE_LOCAL = "TL";
-	public static final String GAMEMODE_DEMO = "TD";
-	public static final String GAMEMODE_NET = "TN";
-	public static final String GAMEMODE_SAVE = "TS";
-	
-	public static final int BUFFER_SIZE = 255; //From iOS code which got it from the origional frontend
-	
-	public static final int MODE_GENLANDPREVIEW = 0;
-	public static final int MODE_GAME = 1;
-
-	private ServerSocket serverSocket;
-	private InputStream input;
-	private OutputStream output;
-	public int port;
-	private final GameConfig config;
-	private boolean clientQuit = false;
-
-	public EngineProtocolNetwork(GameConfig _config){
-		config = _config;
-		try {
-			serverSocket = new ServerSocket(0);
-			port = serverSocket.getLocalPort();
-			Thread ipcThread = new Thread(this, "IPC - Thread");			
-			ipcThread.start();
-		} catch (UnknownHostException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-	}
-	
-	public void run(){
-		//if(mode == MODE_GENLANDPREVIEW) genLandPreviewIPC();
-		/*else if (mode == MODE_GAME)*/ gameIPC();
-	}
-	
-	private void gameIPC(){
-		Socket sock = null;
-		try{
-			sock = serverSocket.accept();
-			input = sock.getInputStream();
-			output = sock.getOutputStream();
-			
-			int msgSize = 0;
-			byte[] buffer = new byte[BUFFER_SIZE];
-
-			while(!clientQuit){
-				msgSize = 0;
-
-				input.read(buffer, 0, 1);
-				msgSize = buffer[0];
-
-				input.read(buffer, 0, msgSize);
-				System.out.println("IPC" + (char)buffer[0] + " : " + new String(buffer, 1,msgSize-1, "US_ASCII"));
-				switch(buffer[0]){
-				case 'C'://game init
-					config.sendToEngine(this);
-					break;
-				case '?'://ping - pong
-					sendToEngine("!");
-					break;
-				case 'e'://Send protocol version
-					System.out.println(new String(buffer));
-					break;
-				case 'i'://game statistics
-					switch(buffer[1]){
-					case 'r'://winning team
-						break;
-					case 'D'://best shot
-						break;
-					case 'k'://best hedgehog
-						break;
-					case 'K'://# hogs killed
-						break;
-					case 'H'://team health graph
-						break;
-					case 'T':// local team stats
-						break;
-					case 'P'://teams ranking
-						break;
-					case 's'://self damage
-						break;
-					case 'S'://friendly fire
-						break;
-					case 'B'://turn skipped
-						break;
-					default:
-					}
-					break;
-				case 'E'://error - quits game
-					System.out.println(new String(buffer));
-					return;
-				case 'q'://game ended remove save file
-
-				    return;
-				case 'Q'://game ended but not finished
-
-					return;
-				}
-
-			}
-		}catch(IOException e){
-			e.printStackTrace();
-		}finally{
-			try {
-				if(sock != null) sock.close();
-			} catch (IOException e) {}
-			try{
-				if(serverSocket != null) serverSocket.close();
-			} catch (IOException e) {}
-		}
-	}
-
-	public void sendToEngine(String s){
-		int length = s.length();
-		
-		try {
-			output.write(length);
-			output.write(s.getBytes(), 0, length);
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-	}
-	
-	public void quitIPC(){
-		clientQuit = true;
-	}
-	
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/GameConfig.java	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/*
- * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
- * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
-package org.hedgewars.hedgeroid.EngineProtocol;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.UUID;
-
-import org.hedgewars.hedgeroid.Datastructures.GameMode;
-import org.hedgewars.hedgeroid.Datastructures.Map;
-import org.hedgewars.hedgeroid.Datastructures.Scheme;
-import org.hedgewars.hedgeroid.Datastructures.Team;
-import org.hedgewars.hedgeroid.Datastructures.Weapon;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-
-public class GameConfig implements Parcelable{
-	
-	public GameMode mode = GameMode.MODE_LOCAL;
-	public Map map = null;
-	public String theme = null;
-	public Scheme scheme = null;
-	public Weapon weapon = null;
-	
-	public String style = null;
-	public String training = null;
-	public String seed = null;
-	
-	public ArrayList<Team> teams = new ArrayList<Team>();
-	
-	public GameConfig(){
-		
-	}
-	
-	public GameConfig(Parcel in){
-		readFromParcel(in);	
-	}
-	
-
-	
-	public void sendToEngine(EngineProtocolNetwork epn) throws IOException{
-		Log.d("HW_Frontend", "Sending Gameconfig...");
-		int teamCount = 4;
-		epn.sendToEngine("TL"); //Write game mode
-		if(training != null) epn.sendToEngine(String.format("escript Scripts/Training/%s.lua", training));
-		else if(style != null) epn.sendToEngine(String.format("escript Scripts/Multiplayer/%s.lua", style));
-		
-		//seed info
-		epn.sendToEngine(String.format("eseed {%s}", UUID.randomUUID().toString()));
-		
-		map.sendToEngine(epn);
-		//dimensions of the map
-		//templatefilter_command
-		//mapgen_command
-		//mazesize_command
-		
-		epn.sendToEngine(String.format("etheme %s", theme));
-		
-		scheme.sendToEngine(epn);
-		
-		weapon.sendToEngine(epn, teamCount);
-		
-		for(Team t : teams){
-			if(t != null)t.sendToEngine(epn, teamCount, scheme.health);
-		}
-	}
-	
-	public int describeContents() {
-		return 0;
-	}
-
-	public void writeToParcel(Parcel dest, int flags) {
-		dest.writeString(mode.name());
-		dest.writeParcelable(map, flags);
-		dest.writeString(theme);
-		dest.writeParcelable(scheme, flags);
-		dest.writeParcelable(weapon, flags);
-		dest.writeString(style);
-		dest.writeString(training);
-		dest.writeString(seed);
-		dest.writeParcelableArray((Team[])teams.toArray(new Team[1]), 0);
-	}
-	
-	private void readFromParcel(Parcel src){
-		mode = GameMode.valueOf(src.readString());
-		map = src.readParcelable(Map.class.getClassLoader());
-		theme = src.readString();
-		scheme = src.readParcelable(Scheme.class.getClassLoader());
-		weapon = src.readParcelable(Weapon.class.getClassLoader());
-		style = src.readString();
-		training = src.readString();
-		seed = src.readString();
-		Parcelable[] parcelables = src.readParcelableArray(Team[].class.getClassLoader());
-		for(Parcelable team : parcelables){
-			teams.add((Team)team);
-		}
-		
-	}
-	
-	public static final Parcelable.Creator<GameConfig> CREATOR = new Parcelable.Creator<GameConfig>() {
-		public GameConfig createFromParcel(Parcel source) {
-			return new GameConfig(source);
-		}
-		public GameConfig[] newArray(int size) {
-			return new GameConfig[size];
-		}
-	};
-	
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/PascalExports.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/PascalExports.java	Sat Aug 18 00:48:09 2012 +0200
@@ -31,10 +31,9 @@
 		System.loadLibrary("hwengine");
 	}
 	
-	public static native int HWversionInfoNetProto();
-	public static native String HWversionInfoVersion();
-	public static native int HWgetNumberOfWeapons();
 	public static native int HWgetMaxNumberOfTeams();
-	public static native int HWgetMaxNumberOfHogs();
-        public static native int HWterminate(boolean b);	
+    public static native int HWterminate(boolean b);
+    public static native int HWGenLandPreview(int port);
+    
+    public static Object engineMutex = new Object();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/GameConnection.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,214 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.BytesCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.IntCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
+import org.hedgewars.hedgeroid.netplay.GameMessageListener;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.util.TickHandler;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+import com.sun.jna.Memory;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+
+public final class GameConnection {
+	private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+	
+	private final HandlerThread thread;
+	private final Handler handler;
+	private final TickHandler tickHandler;
+	private final Netplay netplay; // ==null if not a netgame
+	private GameconnPtr conn;
+
+	/**
+	 * The actual connection has to be set up on a separate thread because networking
+	 * is not allowed on the UI thread, so the port can't be queried immediately after
+	 * creating the GameConnection object. Instead, one of these interface methods is
+	 * called once we know which port we are listening on (or once we fail to set this up).
+	 * Methods will be called on the UI thread.
+	 */
+	public static interface Listener {
+		/**
+		 * We are listening for the engine at $port, go start the engine.
+		 */
+		void gameConnectionReady(int port);
+		
+		/**
+		 * The connection has stopped, either because the game has ended or was interrupted,
+		 * or maybe we failed to create the connection at all (in that case gameConnectionReady wasn't called).
+		 */
+		void gameConnectionDisconnected(int reason);
+	}
+	
+	private GameConnection(Netplay netplay) {
+		this.netplay = netplay;
+		thread = new HandlerThread("IPCThread");
+		thread.start();
+		handler = new Handler(thread.getLooper());
+		tickHandler = new TickHandler(thread.getLooper(), 50, new Runnable() {
+			public void run() {
+				if(conn != null) {
+					Flib.INSTANCE.flib_gameconn_tick(conn);
+				}
+			}
+		});
+		tickHandler.start();
+	}
+	
+	public static GameConnection forNetgame(final GameConfig config, Netplay netplay, final Listener listener) {
+		final GameConnection result = new GameConnection(netplay);
+		final String playerName = netplay.getPlayerName();
+		result.handler.post(new Runnable() {
+			public void run() {
+				GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create(playerName, GameSetupPtr.createJavaOwned(config), true);
+				result.setupConnection(conn, true, listener);
+			}
+		});
+		return result;
+	}
+	
+	public static GameConnection forLocalGame(final GameConfig config, final Listener listener) {
+		final GameConnection result = new GameConnection(null);
+		result.handler.post(new Runnable() {
+			public void run() {
+				GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create("Player", GameSetupPtr.createJavaOwned(config), false);
+				result.setupConnection(conn, false, listener);
+			}
+		});
+		return result;
+	}
+	
+	// runs on the IPCThread
+	private void setupConnection(GameconnPtr conn, final boolean netgame, final Listener listener) {
+		if(conn == null) {
+			mainHandler.post(new Runnable() {
+				public void run() { listener.gameConnectionDisconnected(Frontlib.GAME_END_ERROR); }
+			});
+			shutdown();
+		} else {
+			this.conn = conn;
+			final int port = Flib.INSTANCE.flib_gameconn_getport(conn);
+			mainHandler.post(new Runnable() {
+				public void run() { 
+					listener.gameConnectionReady(port);
+					if(netgame) {
+						netplay.registerGameMessageListener(gameMessageListener);
+					}
+				}
+			});
+			Flib.INSTANCE.flib_gameconn_onConnect(conn, connectCb, null);
+			Flib.INSTANCE.flib_gameconn_onDisconnect(conn, disconnectCb, null);
+			Flib.INSTANCE.flib_gameconn_onErrorMessage(conn, errorMessageCb, null);
+			if(netgame) {
+				Flib.INSTANCE.flib_gameconn_onChat(conn, chatCb, null);
+				Flib.INSTANCE.flib_gameconn_onEngineMessage(conn, engineMessageCb, null);
+			}
+		}
+	}
+	
+	// runs on the IPCThread
+	private void shutdown() {
+		tickHandler.stop();
+		thread.quit();
+		Flib.INSTANCE.flib_gameconn_destroy(conn);
+		if(netplay != null) {
+			mainHandler.post(new Runnable() {
+				public void run() {
+					netplay.unregisterGameMessageListener(gameMessageListener);
+				}
+			});
+		}
+	}
+	
+	// runs on the IPCThread
+	private final StrBoolCallback chatCb = new StrBoolCallback() {
+		public void callback(Pointer context, String message, boolean teamChat) {
+			if(teamChat) {
+				netplay.sendTeamChat(message);
+			} else {
+				netplay.sendChat(message);
+			}
+		}
+	};
+	
+	// runs on the IPCThread
+	private final VoidCallback connectCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			Log.i("GameConnection", "Connected");
+		}
+	};
+	
+	// runs on the IPCThread
+	private final IntCallback disconnectCb = new IntCallback() {
+		public void callback(Pointer context, int reason) {
+			if(netplay != null) {
+				netplay.sendRoundFinished(reason==Frontlib.GAME_END_FINISHED);
+			}
+			shutdown();
+		}
+	};
+	
+	// runs on the IPCThread
+	private final BytesCallback engineMessageCb = new BytesCallback() {
+		public void callback(Pointer context, Pointer buffer, NativeLong size) {
+			netplay.sendEngineMessage(buffer.getByteArray(0, size.intValue()));
+		}
+	};
+	
+	// runs on the IPCThread
+	private final StrCallback errorMessageCb = new StrCallback() {
+		public void callback(Pointer context, String message) {
+			Log.e("GameConnection", message);
+		}
+	};
+	
+	// runs on any thread
+	private final GameMessageListener gameMessageListener = new GameMessageListener() {
+		public void onNetDisconnected() {
+			handler.post(new Runnable() {
+				public void run() {
+					shutdown();
+				}
+			});
+		}
+		
+		public void onMessage(final int type, final String message) {
+			handler.post(new Runnable() {
+				public void run() {
+					Flib.INSTANCE.flib_gameconn_send_textmsg(conn, type, message);
+				}
+			});
+		}
+		
+		public void onEngineMessage(final byte[] em) {
+			handler.post(new Runnable() {
+				public void run() {
+					Memory mem = new Memory(em.length);
+					mem.write(0, em, 0, em.length);
+					Flib.INSTANCE.flib_gameconn_send_enginemsg(conn, mem, new NativeLong(em.length));
+				}
+			});
+			
+		}
+		
+		public void onChatMessage(final String nick, final String message) {
+			handler.post(new Runnable() {
+				public void run() {
+					Flib.INSTANCE.flib_gameconn_send_chatmsg(conn, nick, message);
+				}
+			});
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,140 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.NetplayStateFragment.NetplayStateListener;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+import org.hedgewars.hedgeroid.util.TextInputDialog;
+import org.hedgewars.hedgeroid.util.TextInputDialog.TextInputDialogListener;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TabHost;
+import android.widget.TextView;
+
+public class LobbyActivity extends FragmentActivity implements TextInputDialogListener, NetplayStateListener {
+	private static final int DIALOG_CREATE_ROOM = 0;
+	
+    private TabHost tabHost;
+    private Netplay netplay;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        setContentView(R.layout.activity_lobby);
+        ChatFragment chatFragment = (ChatFragment)getSupportFragmentManager().findFragmentById(R.id.chatFragment);
+        chatFragment.setInRoom(false);
+        
+        FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+        trans.add(new NetplayStateFragment(), "netplayFragment");
+        trans.commit();
+        
+        netplay = Netplay.getAppInstance(getApplicationContext());
+        
+        tabHost = (TabHost)findViewById(android.R.id.tabhost);
+        if(tabHost != null) {
+	        tabHost.setup();
+	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
+
+	        tabHost.addTab(tabHost.newTabSpec("rooms").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_rooms, getResources().getDrawable(R.drawable.roomlist_ingame))).setContent(R.id.roomListFragment));
+	        tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_chat, getResources().getDrawable(R.drawable.edit))).setContent(R.id.chatFragment));
+	        tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_players, getResources().getDrawable(R.drawable.human))).setContent(R.id.playerListFragment));
+	
+	        if (icicle != null) {
+	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
+	        }
+        }
+    }
+    
+    private View createIndicatorView(TabHost tabHost, int label, Drawable icon) {
+        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        View tabIndicator = inflater.inflate(R.layout.tab_indicator,
+                tabHost.getTabWidget(), // tab widget is the parent
+                false); // no inflate params
+
+        final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
+        tv.setText(label);
+        
+        if(icon != null) {
+	        final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
+	        iconView.setImageDrawable(icon);
+        }
+        
+        return tabIndicator;
+    }
+    
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		super.onCreateOptionsMenu(menu);
+		getMenuInflater().inflate(R.menu.lobby_options, menu);
+		return true;
+	}
+	
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch(item.getItemId()) {
+		case R.id.room_create:
+	        TextInputDialog dialog = new TextInputDialog(DIALOG_CREATE_ROOM, R.string.dialog_create_room_title, 0, R.string.dialog_create_room_hint);
+	        dialog.show(getSupportFragmentManager(), "create_room_dialog");
+			return true;
+		case R.id.disconnect:
+			netplay.disconnect();
+			return true;
+		default:
+			return super.onOptionsItemSelected(item);
+		}
+	}
+	
+	@Override
+	public void onBackPressed() {
+		super.onBackPressed();
+		netplay.disconnect();
+	}
+	
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        if(tabHost != null) {
+        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
+        }
+    }
+    
+    public void onTextInputDialogSubmitted(int dialogId, String text) {
+    	if(text != null && text.length()>0) {
+    		netplay.sendCreateRoom(text);
+    	}
+    }
+    
+    public void onTextInputDialogCancelled(int dialogId) {
+    }
+    
+    public void onNetplayStateChanged(State newState) {
+    	switch(newState) {
+    	case CONNECTING:
+    	case NOT_CONNECTED:
+    		finish();
+    		break;
+    	case ROOM:
+    	case INGAME:
+    		startActivity(new Intent(getApplicationContext(), RoomActivity.class));
+    		break;
+    	case LOBBY:
+    		// Do nothing
+    		break;
+		default:
+			throw new IllegalStateException("Unknown connection state: "+newState);
+    	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyPlayerlistAdapter.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,32 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class LobbyPlayerlistAdapter extends ObservableTreeMapAdapter<String, Player> {
+	@Override
+	protected Comparator<Player> getEntryOrder() {
+		return Player.NAME_ORDER;
+	}
+
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(parent.getContext());
+			v = vi.inflate(R.layout.listview_player, null);
+		}
+
+		String player = getItem(position).name;
+		TextView username = (TextView) v.findViewById(android.R.id.text1);
+		username.setText(player);
+		return v;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyPlayerlistFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,75 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class LobbyPlayerlistFragment extends ListFragment {
+	private Netplay netplay;
+	private LobbyPlayerlistAdapter adapter;
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new LobbyPlayerlistAdapter();
+		adapter.setSource(netplay.lobbyPlayerlist);
+		setListAdapter(adapter);
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+	}
+	
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		registerForContextMenu(getListView());
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v,
+			ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
+		MenuInflater inflater = getActivity().getMenuInflater();
+		inflater.inflate(R.menu.lobby_playerlist_context, menu);
+		menu.setHeaderIcon(R.drawable.human);
+		menu.setHeaderTitle(adapter.getItem(info.position).name);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
+		Player player = adapter.getItem(info.position);
+		switch(item.getItemId()) {
+		case R.id.player_info:
+			netplay.sendPlayerInfoQuery(player.name);
+			return true;
+		case R.id.player_follow:
+			netplay.sendFollowPlayer(player.name);
+			return true;
+		default:
+			return super.onContextItemSelected(item);
+		}
+	}
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		return inflater.inflate(R.layout.fragment_playerlist, container, false);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -18,61 +18,131 @@
 
 package org.hedgewars.hedgeroid;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
 import org.hedgewars.hedgeroid.Downloader.DownloadAssets;
 import org.hedgewars.hedgeroid.Downloader.DownloadListActivity;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.IntentFilter;
 import android.os.Bundle;
-import android.preference.PreferenceManager;
 import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.Toast;
 
 public class MainActivity extends FragmentActivity {
-
-	private Button downloader, startGame;
+	private static final int DIALOG_NO_SDCARD = 0;
+	
+	private LocalBroadcastManager broadcastManager;
 	private ProgressDialog assetsDialog;
 
 	public void onCreate(Bundle sis){
 		super.onCreate(sis);
 		setContentView(R.layout.main);
 
-		downloader = (Button)findViewById(R.id.downloader);
-		startGame = (Button)findViewById(R.id.startGame);
+		broadcastManager = LocalBroadcastManager.getInstance(getApplicationContext());
+		Button startLocalGame = (Button)findViewById(R.id.startGame);
+		Button startNetGame = (Button)findViewById(R.id.joinLobby);
 
-		downloader.setOnClickListener(downloadClicker);
-		startGame.setOnClickListener(startGameClicker);
-
+		startLocalGame.setOnClickListener(startGameListener);
+		startNetGame.setOnClickListener(startNetGameListener);
 
-		String cacheDir = Utils.getCachePath(this);
-		if(cacheDir == null){
-			showDialog(0);
-		}else{
-			int versionCode = 0;
+		if(!FileUtils.isDataPathAvailable()){
+			showDialog(DIALOG_NO_SDCARD);
+		} else {
+			String existingVersion = "";
 			try {
-				versionCode = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionCode;
-			} catch (NameNotFoundException e) {
-
+				File versionFile = new File(FileUtils.getCachePath(this), "assetsversion.txt");
+				existingVersion = FileUtils.readToString(new FileInputStream(versionFile));
+			} catch(IOException e) {
 			}
-			boolean assetsCopied = PreferenceManager.getDefaultSharedPreferences(this).getInt("latestAssets", 0) >= versionCode;
-
-			if(!assetsCopied){
+			
+			String newVersion = "";
+			try {
+				newVersion = FileUtils.readToString(getAssets().open("assetsversion.txt"));
+			} catch(IOException e) {
+			}
+			
+			if(!existingVersion.equals(newVersion)) {
 				DownloadAssets assetsAsyncTask = new DownloadAssets(this);
-				assetsDialog = ProgressDialog.show(this, "Please wait a moment", "Moving assets...");
-				assetsAsyncTask.execute((Object[])null);
+				assetsDialog = ProgressDialog.show(this, "Please wait a moment", "Moving assets to SD card...");
+				assetsAsyncTask.execute();
 			}
 		}
 	}
 
+	@Override
+	protected void onResume() {
+		super.onResume();
+		broadcastManager.registerReceiver(connectedReceiver, new IntentFilter(Netplay.ACTION_CONNECTED));
+		broadcastManager.registerReceiver(connectionFailedReceiver, new IntentFilter(Netplay.ACTION_DISCONNECTED));
+		broadcastManager.registerReceiver(passwordRequestedReceiver, new IntentFilter(Netplay.ACTION_PASSWORD_REQUESTED));
+	}
+	
+	@Override
+	protected void onPause() {
+		super.onPause();
+		broadcastManager.unregisterReceiver(connectedReceiver);
+		broadcastManager.unregisterReceiver(connectionFailedReceiver);
+		broadcastManager.unregisterReceiver(passwordRequestedReceiver);
+		Netplay netplay = Netplay.getAppInstance(getApplicationContext());
+		if(netplay.getState() == State.CONNECTING) {
+			netplay.disconnect();
+		}
+	}
+	
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		super.onCreateOptionsMenu(menu);
+		getMenuInflater().inflate(R.menu.main_options, menu);
+		return true;
+	}
+	
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch(item.getItemId()) {
+		case R.id.download:
+			startActivityForResult(new Intent(getApplicationContext(), DownloadListActivity.class), 0);
+			return true;
+		case R.id.preferences:
+			Toast.makeText(this, R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+			return true;
+		case R.id.edit_weaponsets:
+			startActivity(new Intent(getApplicationContext(), WeaponsetListActivity.class));
+			return true;
+		default:
+			return super.onOptionsItemSelected(item);
+		}
+	}
+	
 	public Dialog onCreateDialog(int id, Bundle args){
+		switch(id) {
+		case DIALOG_NO_SDCARD:
+			return createNoSdcardDialog();
+		default:
+			throw new IndexOutOfBoundsException();
+		}
+	}
+
+	private Dialog createNoSdcardDialog() {
 		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 		builder.setTitle(R.string.sdcard_not_mounted_title);
 		builder.setMessage(R.string.sdcard_not_mounted);
@@ -84,30 +154,67 @@
 
 		return builder.create();
 	}
-
+	
 	public void onAssetsDownloaded(boolean result){
-		if(result){
-			try {
-				int versionCode = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionCode;
-				PreferenceManager.getDefaultSharedPreferences(this).edit().putInt("latestAssets", versionCode).commit();
-			} catch (NameNotFoundException e) {}
-			
-		}else{
-			Toast.makeText(this, R.string.download_failed, Toast.LENGTH_LONG);
+		if(!result){
+			Toast.makeText(this, R.string.download_failed, Toast.LENGTH_LONG).show();
 		}
 		assetsDialog.dismiss();
 	}
 
-	private OnClickListener downloadClicker = new OnClickListener(){
-		public void onClick(View v){
-			//startActivityForResult(new Intent(getApplicationContext(), DownloadActivity.class), 0);
-			startActivityForResult(new Intent(getApplicationContext(), DownloadListActivity.class), 0);
-		}
-	};
-
-	private OnClickListener startGameClicker = new OnClickListener(){
+	private final OnClickListener startGameListener = new OnClickListener(){
 		public void onClick(View v){
 			startActivity(new Intent(getApplicationContext(), StartGameActivity.class));
 		}
 	};
+	
+	private final OnClickListener startNetGameListener = new OnClickListener() {
+		public void onClick(View v) {
+			State state = Netplay.getAppInstance(getApplicationContext()).getState();
+			switch(state) {
+			case NOT_CONNECTED:
+		        FragmentManager fm = getSupportFragmentManager();
+		        StartNetgameDialog startNetgameDialog = new StartNetgameDialog();
+		        startNetgameDialog.show(fm, "start_netgame_dialog");
+				break;
+			case CONNECTING:
+				onNetConnectingStarted();
+				break;
+			default:
+				startActivity(new Intent(getApplicationContext(), LobbyActivity.class));
+				break;
+			}
+		}
+	};
+	
+	private BroadcastReceiver connectedReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			startActivity(new Intent(getApplicationContext(), LobbyActivity.class));
+		}
+	};
+	
+	private BroadcastReceiver connectionFailedReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			if(intent.getBooleanExtra(Netplay.EXTRA_HAS_ERROR, true)) {
+				Toast.makeText(getApplicationContext(), intent.getStringExtra(Netplay.EXTRA_MESSAGE), Toast.LENGTH_LONG).show();
+			}
+		}
+	};
+	
+	private BroadcastReceiver passwordRequestedReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+	        FragmentManager fm = getSupportFragmentManager();
+	        PasswordDialog passwordDialog = new PasswordDialog(intent.getStringExtra(Netplay.EXTRA_PLAYERNAME));
+	        passwordDialog.show(fm, "fragment_password_dialog");
+		}
+	};
+
+	public void onNetConnectingStarted() {
+        FragmentManager fm = getSupportFragmentManager();
+        ConnectingDialog connectingDialog = new ConnectingDialog();
+        connectingDialog.show(fm, "fragment_connecting_dialog");
+	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,279 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.MapFile;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TableRow;
+import android.widget.Toast;
+
+public class MapFragment extends Fragment implements RoomStateManager.Observer {
+	private Spinner mapTypeSpinner, mapNameSpinner, templateSpinner, mazeSizeSpinner;
+	private TableRow nameRow, templateRow, mazeSizeRow;
+	private ImageView mapPreview;
+	private List<MapFile> mapFiles;
+	private RoomStateManager stateManager;
+	private Random random = new Random();
+	private CalmDownHandler mapPreviewHandler;
+	
+	/*
+	 * Rendering the preview can take a few seconds on Android, so we want to prevent preview
+	 * requests from queueing up if maps are changed quickly. So if there is already a preview
+	 * being generated, we store our latest request in the newPreviewRequest variable instead.
+	 * Once the current preview is finished generating it will start on that one.
+	 */
+	private boolean previewGenerationInProgress;
+	private MapRecipe newPreviewRequest;
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+		View v = inflater.inflate(R.layout.fragment_map, container, false);
+
+		final Context appContext = getActivity().getApplicationContext();
+		mapPreviewHandler = new CalmDownHandler(getActivity().getMainLooper(), new Runnable() {
+			public void run() {
+				if(!previewGenerationInProgress) {
+					mapPreview.setImageResource(R.drawable.roomlist_preparing);
+					MapPreviewGenerator.startPreviewGeneration(appContext, stateManager.getMapRecipe(), mapPreviewListener);
+					previewGenerationInProgress = true;
+				} else {
+					newPreviewRequest = stateManager.getMapRecipe();
+				}
+			}
+		}, 250);
+		
+		nameRow = (TableRow) v.findViewById(R.id.rowMapName);
+		templateRow = (TableRow) v.findViewById(R.id.rowTemplateFilter);
+		mazeSizeRow = (TableRow) v.findViewById(R.id.rowMazeSize);
+		mapPreview = (ImageView) v.findViewById(R.id.mapPreview);
+		mapPreview.setImageDrawable(null);;
+		mapPreview.setOnClickListener(mapClickListener);
+		
+		try {
+			mapFiles = FrontendDataUtils.getMaps(getActivity());
+		} catch (IOException e) {
+			Toast.makeText(getActivity().getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			getActivity().finish();
+		}
+		Collections.sort(mapFiles, MapFile.MISSIONS_FIRST_NAME_ORDER);
+		
+		List<String> mapNames = MapFile.toDisplayNameList(mapFiles, getResources());
+		mapTypeSpinner = prepareSpinner(v, R.id.spinMapType, Arrays.asList(getResources().getStringArray(R.array.map_types)), mapTypeSelectedListener);
+		mapNameSpinner = prepareSpinner(v, R.id.spinMapName, mapNames, mapNameSelectedListener);
+		templateSpinner = prepareSpinner(v, R.id.spinTemplateFilter, Arrays.asList(getResources().getStringArray(R.array.map_templates)), mapTemplateSelectedListener);
+		mazeSizeSpinner = prepareSpinner(v, R.id.spinMazeSize, Arrays.asList(getResources().getStringArray(R.array.map_maze_sizes)), mazeSizeSelectedListener);
+
+		stateManager.registerObserver(this);
+		MapRecipe map = stateManager.getMapRecipe();
+		if(map != null) {
+			updateDisplay(map);
+		}
+		setChiefState(stateManager.getChiefStatus());
+		mapPreviewHandler.activity();
+		return v;
+	}
+	
+	private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) {
+		Spinner spinner = (Spinner)v.findViewById(id);
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(itemSelectedListener);
+		return spinner;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		try {
+			stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager();
+		} catch(ClassCastException e) {
+			throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e);
+		}
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		mapPreviewHandler.stop();
+		stateManager.unregisterObserver(this);
+	}
+	
+	private void setChiefState(boolean chiefState) {
+		mapTypeSpinner.setEnabled(chiefState);
+		mapNameSpinner.setEnabled(chiefState);
+		templateSpinner.setEnabled(chiefState);
+		mazeSizeSpinner.setEnabled(chiefState);
+		mapPreview.setEnabled(chiefState);
+		
+		if(chiefState) {
+			sendMapnameAndGenerator();
+			stateManager.changeMapTemplate(templateSpinner.getSelectedItemPosition());
+			stateManager.changeMazeSize(mazeSizeSpinner.getSelectedItemPosition());
+		}
+	}
+	
+	private void updateDisplay(MapRecipe map) {
+		nameRow.setVisibility(map.mapgen == Frontlib.MAPGEN_NAMED ? View.VISIBLE : View.GONE);
+		templateRow.setVisibility(map.mapgen == Frontlib.MAPGEN_REGULAR ? View.VISIBLE : View.GONE);
+		mazeSizeRow.setVisibility(map.mapgen == Frontlib.MAPGEN_MAZE ? View.VISIBLE : View.GONE);
+		
+		mapTypeSpinner.setSelection(map.mapgen);
+		int mapPosition = findMapPosition(mapFiles, map.name);
+		if(mapPosition >= 0) {
+			mapNameSpinner.setSelection(mapPosition);
+		}
+		templateSpinner.setSelection(map.templateFilter);
+		mazeSizeSpinner.setSelection(map.mazeSize);
+	}
+	
+	private static int findMapPosition(List<MapFile> mapFiles, String mapName) {
+		for(int i=0; i<mapFiles.size(); i++) {
+			if(mapName.equals(mapFiles.get(i).name)) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private void sendMapnameAndGenerator() {
+		int mapType = mapTypeSpinner.getSelectedItemPosition();
+		String mapName = mapFiles.get(mapNameSpinner.getSelectedItemPosition()).name;
+		stateManager.changeMapNameAndGenerator(MapRecipe.mapnameForGenerator(mapType, mapName));
+	}
+	
+	private final OnItemSelectedListener mapTypeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			sendMapnameAndGenerator();
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener mapNameSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			sendMapnameAndGenerator();
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener mapTemplateSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeMapTemplate(position);
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener mazeSizeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeMazeSize(position);
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnClickListener mapClickListener = new OnClickListener() {
+		public void onClick(View v) {
+			stateManager.changeMapSeed(MapRecipe.makeRandomSeed());
+			switch(mapTypeSpinner.getSelectedItemPosition()) {
+			case Frontlib.MAPGEN_NAMED:
+				mapNameSpinner.setSelection(random.nextInt(mapNameSpinner.getCount()));
+				break;
+			case Frontlib.MAPGEN_REGULAR:
+				templateSpinner.setSelection(Frontlib.TEMPLATEFILTER_ALL);
+				break;
+			case Frontlib.MAPGEN_MAZE:
+				mazeSizeSpinner.setSelection(random.nextInt(mazeSizeSpinner.getCount()));
+				break;
+			}
+		}
+	};
+	
+	public void onChiefStatusChanged(boolean isChief) {
+		setChiefState(isChief);
+	}
+	
+	public void onMapChanged(MapRecipe recipe) {
+		updateDisplay(recipe);
+		mapPreviewHandler.activity();
+	}
+	
+	public void onGameStyleChanged(String gameStyle) { }
+	public void onSchemeChanged(Scheme scheme) { }
+	public void onWeaponsetChanged(Weaponset weaponset) { }
+	
+	private MapPreviewGenerator.Listener mapPreviewListener = new MapPreviewGenerator.Listener() {
+		public void onMapPreviewResult(Drawable preview) {
+			if(newPreviewRequest != null) {
+				MapPreviewGenerator.startPreviewGeneration(getActivity().getApplicationContext(), newPreviewRequest, mapPreviewListener);
+				newPreviewRequest = null;
+			} else {
+				if(mapPreview != null) {
+					mapPreview.setImageDrawable(preview);
+				}
+				previewGenerationInProgress = false;
+			}
+		}
+	};
+	
+	/**
+	 * This class allows you to define a runnable that is called when there has been no activity
+	 * for a set amount of time, where activity is determined by calls to the activity() method
+	 * of the handler. It is used here to update the map preview when there have been no updates
+	 * to the relevant map information for a time, to prevent triggering several updates at once
+	 * when different parts of the information change.
+	 */
+	private static final class CalmDownHandler extends Handler {
+		int runningMessagesCounter = 0;
+		final Runnable inactivityRunnable;
+		final long inactivityMs;
+		boolean stopped;
+
+		public CalmDownHandler(Looper looper, Runnable runnable, long inactivityMs) {
+			super(looper);
+			this.inactivityRunnable = runnable;
+			this.inactivityMs = inactivityMs;
+		}
+		
+		public void activity() {
+			runningMessagesCounter++;
+			sendMessageDelayed(obtainMessage(), inactivityMs);
+		}
+		
+		@Override
+		public void handleMessage(Message msg) {
+			runningMessagesCounter--;
+			if(runningMessagesCounter==0 && !stopped) {
+				inactivityRunnable.run();
+			}
+		}
+		
+		public void stop() {
+			stopped = true;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapPreviewGenerator.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,200 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.hedgewars.hedgeroid.Datastructures.MapFile;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapRecipePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapimageCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.util.FileUtils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.sun.jna.Pointer;
+
+/**
+ * A class that asynchronously generates a map preview from a MapRecipe.
+ * 
+ * For named maps, this will load the preview image from the filesystem. For others,
+ * it will call the engine to generate a preview image.
+ */
+public final class MapPreviewGenerator implements Runnable {
+	private static final String TAG = MapPreviewGenerator.class.getSimpleName();
+	private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+
+	private final Context appContext;
+	private final MapRecipe map;
+	private final Listener listener;
+	
+	private boolean resultAvailable;
+	private Drawable result;
+	
+	public static interface Listener {
+		/**
+		 * This is called on the UI thread once the preview is ready or failed.
+		 * In case of failure, null is passed.
+		 */
+		void onMapPreviewResult(Drawable preview);
+	}
+
+	private MapPreviewGenerator(Context appContext, MapRecipe map, Listener listener) {
+		this.appContext = appContext;
+		this.map = map;
+		this.listener = listener;
+	}
+	
+	public void run() {
+		if (map.mapgen == Frontlib.MAPGEN_NAMED) {
+			postToListener(loadPreviewFromFile(appContext, map.name));
+		} else {
+			resultAvailable = false;
+			result = null;
+			MapconnPtr conn = Flib.INSTANCE.flib_mapconn_create(MapRecipePtr.createJavaOwned(map));
+			if (conn == null) {
+				postToListener(null);
+				return;
+			}
+			try {
+				int port = Flib.INSTANCE.flib_mapconn_getport(conn);
+				Flib.INSTANCE.flib_mapconn_onSuccess(conn, successCb, null);
+				Flib.INSTANCE.flib_mapconn_onFailure(conn, failureCb, null);
+	
+				String configPath;
+				try {
+					configPath = FileUtils.getCachePath(appContext).getAbsolutePath();
+				} catch(FileNotFoundException e) {
+					return;
+				}
+				
+				startEngine(configPath, port);
+				long startTime = System.nanoTime();
+				do {
+					Flib.INSTANCE.flib_mapconn_tick(conn);
+					try {
+						Thread.sleep(50);
+					} catch (InterruptedException e) {
+						// ignore
+					}
+				} while(!resultAvailable && System.nanoTime()-startTime < 15000000000l); // 15 seconds timeout
+			} finally {
+				Flib.INSTANCE.flib_mapconn_destroy(conn);
+				postToListener(result);
+			}
+		}
+	}
+	
+	public static void startPreviewGeneration(Context appContext, MapRecipe map, Listener listener) {
+		new Thread(new MapPreviewGenerator(appContext, map, listener)).start();
+	}
+	
+	private static Drawable loadPreviewFromFile(Context appContext, String mapName) {
+		if(!mapName.startsWith("+")) {
+			try {
+				File previewFile = MapFile.getPreviewFile(appContext, mapName);
+				return Drawable.createFromPath(previewFile.getAbsolutePath());
+			} catch (FileNotFoundException e) {
+				Log.w("MapPreviewGenerator", "Preview for map "+mapName+" not found.");
+			}
+		}
+		return null;
+	}
+	
+	private static void startEngine(final String configPath, final int port) {
+		new Thread(new Runnable() {
+			public void run() {
+				Log.d(TAG, "Starting engine "+port);
+				synchronized(PascalExports.engineMutex) {
+					PascalExports.HWGenLandPreview(port);
+				}
+				Log.d(TAG, "Engine finished");
+			}
+		}).start();
+	}
+	
+	private void postToListener(final Drawable result) {
+		mainHandler.post(new Runnable() {
+			public void run() {
+				listener.onMapPreviewResult(result);
+			}
+		});
+	}
+	
+	/**
+	 * Let's be extra nice here and clip off the left and right sides, so the preview is centered...
+	 * Since the image is present in bytes, we can save some effort by checking entire byte-columns first.
+	 */
+	private final MapimageCallback successCb = new MapimageCallback() {
+		public void callback(Pointer context, Pointer buffer, int hedgehogCount) {
+			Log.d(TAG, "Running success handler");
+			byte[] mapdata = buffer.getByteArray(0, Frontlib.MAPIMAGE_BYTES);
+			
+			int leftmostPixel = Frontlib.MAPIMAGE_WIDTH;
+			int rightmostPixel = -1;
+			int bytesPerLine = Frontlib.MAPIMAGE_WIDTH/8;
+			
+			// Find the leftmost pixel
+			for(int xbyte=0; xbyte<bytesPerLine; xbyte++) {
+				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+					int b = 0xff&mapdata[xbyte+y*bytesPerLine];
+					if(b != 0) {
+						leftmostPixel = Math.min(leftmostPixel, Integer.numberOfLeadingZeros(b)-24+xbyte*8);
+					}
+				}
+				if(leftmostPixel!=Frontlib.MAPIMAGE_WIDTH) break;
+			}
+			
+			// Find the rightmost pixel
+			for(int xbyte=bytesPerLine-1; xbyte>=0; xbyte--) {
+				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+					int b = mapdata[xbyte+y*bytesPerLine];
+					if(b != 0) {
+						rightmostPixel = Math.max(rightmostPixel, xbyte*8+7-Integer.numberOfTrailingZeros(b));
+					}
+				}
+				if(rightmostPixel!=-1) break;
+			}
+		
+			// No pixel was set at all -> use default width
+			if(rightmostPixel==-1) {
+				leftmostPixel = 0;
+				rightmostPixel = Frontlib.MAPIMAGE_WIDTH-1;
+			}
+			
+			Bitmap bitmap = Bitmap.createBitmap(rightmostPixel-leftmostPixel+1, Frontlib.MAPIMAGE_HEIGHT, Config.ARGB_8888);
+			for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+				for(int x=0; x<bitmap.getWidth(); x++) {
+					bitmap.setPixel(x, y, isPixelSet(mapdata, x+leftmostPixel, y) ? Color.YELLOW : Color.TRANSPARENT);
+				}
+			}
+			result = new BitmapDrawable(bitmap);
+			resultAvailable = true;
+		}
+	};
+	
+	private static boolean isPixelSet(byte[] imgdata, int x, int y) {
+		int pixelnum = x+Frontlib.MAPIMAGE_WIDTH*y;
+		return (imgdata[pixelnum>>3] & (128>>(pixelnum&7))) != 0;
+	}
+	
+	private final StrCallback failureCb = new StrCallback() {
+		public void callback(Pointer context, String reason) {
+			Log.e(TAG, "Error generating map preview: "+reason);
+			result = null;
+			resultAvailable = true;
+		}
+	};
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/NetplayStateFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,120 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.Toast;
+
+/**
+ * Fragment for use by an activity that depends on the state of the network
+ * connection. The activity must implement the NetplayStateListener interface.
+ * 
+ * This fragment manages a few aspects of the netplay connection: Requesting
+ * the network system loop to run at high frequency while the activity is in
+ * the foreground, and reacting to changes in the networking state by calling
+ * a callback method on the activity. 
+ */
+public class NetplayStateFragment extends Fragment {
+    private Netplay netplay;
+    private Context appContext;
+    private LocalBroadcastManager broadcastManager;
+    private NetplayStateListener listener;
+    private State knownState;
+    
+    interface NetplayStateListener {
+    	/**
+    	 * This is called while the activity is running, and every time during resume, if
+    	 * a change in the networking state is detected. It is also called once
+    	 * with the initial state (which could be called a change from the "unknown" state).
+    	 */
+    	void onNetplayStateChanged(State newState);
+    } 
+    
+    @Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		try {
+			listener = (NetplayStateListener) activity;
+		} catch(ClassCastException e) {
+			throw new ClassCastException("Activity " + activity + " must implement NetplayStateListener to use NetplayStateFragment.");
+		}
+	}
+	
+	@Override
+	public void onDetach() {
+		super.onDetach();
+		listener = null;
+	}
+	
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        appContext = getActivity().getApplicationContext();
+        broadcastManager = LocalBroadcastManager.getInstance(appContext);
+        netplay = Netplay.getAppInstance(appContext);
+    }    
+
+    @Override
+    public void onResume() {
+    	super.onResume();
+    	broadcastManager.registerReceiver(disconnectReceiver, new IntentFilter(Netplay.ACTION_DISCONNECTED));
+    	broadcastManager.registerReceiver(leaveRoomReceiver, new IntentFilter(Netplay.ACTION_LEFT_ROOM));
+    	broadcastManager.registerReceiver(stateChangeReceiver, new IntentFilter(Netplay.ACTION_STATE_CHANGED));
+    	
+    	State newState = netplay.getState();
+		if(knownState != newState) {
+    		listener.onNetplayStateChanged(newState);
+    		knownState = newState;
+    	}
+    }
+    
+    @Override
+    public void onPause() {
+    	super.onPause();
+    	broadcastManager.unregisterReceiver(disconnectReceiver);
+    	broadcastManager.unregisterReceiver(leaveRoomReceiver);
+    	broadcastManager.unregisterReceiver(stateChangeReceiver);
+    }
+
+	private final BroadcastReceiver disconnectReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			if(intent.getBooleanExtra(Netplay.EXTRA_HAS_ERROR, true)) {
+				String message = intent.getStringExtra(Netplay.EXTRA_MESSAGE);
+				String toastText = getString(R.string.toast_disconnected, message);
+				Toast.makeText(appContext, toastText, Toast.LENGTH_LONG).show();
+			}
+		}
+	};
+	
+	private final BroadcastReceiver leaveRoomReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			int reason = intent.getIntExtra(Netplay.EXTRA_REASON, -1);
+			if(reason == Frontlib.NETCONN_ROOMLEAVE_ABANDONED) {
+				Toast.makeText(appContext, R.string.toast_room_abandoned, Toast.LENGTH_LONG).show();
+			} else if(reason == Frontlib.NETCONN_ROOMLEAVE_KICKED) {
+				Toast.makeText(appContext, R.string.toast_kicked, Toast.LENGTH_LONG).show();
+			}
+		}
+	};
+	
+	private final BroadcastReceiver stateChangeReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			State newState = netplay.getState();
+			listener.onNetplayStateChanged(newState);
+			knownState = newState;
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/PasswordDialog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,60 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.InputType;
+import android.text.method.PasswordTransformationMethod;
+import android.widget.EditText;
+
+public class PasswordDialog extends ConnectionDependendDialogFragment {
+	String username;
+	
+	public PasswordDialog() {
+	}
+	
+	public PasswordDialog(String username) {
+		this.username = username;
+	}
+	
+	@Override
+	public void onSaveInstanceState(Bundle icicle) {
+		super.onSaveInstanceState(icicle);
+		icicle.putString("username", username);
+	}
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		if(savedInstanceState != null) {
+			username = savedInstanceState.getString("username");
+		}
+		final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		final EditText editText = new EditText(getActivity());
+		final Netplay netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		
+		editText.setHint(R.string.dialog_password_hint);
+		editText.setId(android.R.id.text1);
+		editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+		editText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+		builder.setView(editText);
+		builder.setTitle(R.string.dialog_password_title);
+		builder.setMessage(getString(R.string.dialog_password_message, username));
+		builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				String password = editText.getText().toString();
+				editText.setText("");
+				netplay.sendPassword(password);
+			}
+		});
+		return builder.create();
+	}
+	
+	@Override
+	public void onCancel(DialogInterface dialog) {
+		super.onCancel(dialog);
+		Netplay.getAppInstance(getActivity().getApplicationContext()).disconnect();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,85 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.NetplayStateFragment.NetplayStateListener;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.widget.TabHost;
+import android.widget.Toast;
+
+public class RoomActivity extends FragmentActivity implements NetplayStateListener, TeamAddDialog.Listener, RoomStateManager.Provider {
+	private TabHost tabHost;
+	private Netplay netplay;
+	
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        netplay = Netplay.getAppInstance(getApplicationContext());
+        
+        setContentView(R.layout.activity_netroom);
+        ChatFragment chatFragment = (ChatFragment)getSupportFragmentManager().findFragmentById(R.id.chatFragment);
+        chatFragment.setInRoom(true);
+        
+        FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+        trans.add(new NetplayStateFragment(), "netplayFragment");
+        trans.commit();
+        
+        /*tabHost = (TabHost)findViewById(android.R.id.tabhost);
+        if(tabHost != null) {
+	        tabHost.setup();
+	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
+
+	        //tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_chat, getResources().getDrawable(R.drawable.edit))).setContent(R.id.chatFragment));
+	        //tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_players, getResources().getDrawable(R.drawable.human))).setContent(R.id.playerListFragment));
+	
+	        if (icicle != null) {
+	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
+	        }
+        }*/
+    }
+
+	@Override
+	public void onBackPressed() {
+		netplay.sendLeaveRoom(null);
+	}
+    
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        if(tabHost != null) {
+        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
+        }
+    }
+    
+    public void onNetplayStateChanged(State newState) {
+    	switch(newState) {
+    	case NOT_CONNECTED:
+    	case CONNECTING:
+    	case LOBBY:
+    		finish();
+    		break;
+    	case ROOM:
+    		// Do nothing
+    		break;
+    	case INGAME:
+    		//startActivity(new Intent(getApplicationContext(), RoomActivity.class));
+    		Toast.makeText(getApplicationContext(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+    		break;
+		default:
+			throw new IllegalStateException("Unknown connection state: "+newState);
+    	}
+    }
+    
+	public void onTeamAddDialogSubmitted(Team newTeam) {
+		netplay.sendAddTeam(newTeam);
+	}
+	
+	public RoomStateManager getRoomStateManager() {
+		return netplay.getRoomStateManager();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomPlayerlistAdapter.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,41 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class RoomPlayerlistAdapter extends ObservableTreeMapAdapter<String, PlayerInRoom> {
+	@Override
+	protected Comparator<PlayerInRoom> getEntryOrder() {
+		return AlphabeticalOrderComparator.INSTANCE;
+	}
+
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(parent.getContext());
+			v = vi.inflate(R.layout.listview_player, null);
+		}
+
+		PlayerInRoom player = getItem(position);
+		TextView username = (TextView) v.findViewById(android.R.id.text1);
+		username.setText(player.player.name);
+		int readyDrawable = player.ready ? R.drawable.lightbulb_on : R.drawable.lightbulb_off;
+		username.setCompoundDrawablesWithIntrinsicBounds(readyDrawable, 0, 0, 0);
+		return v;
+	}
+	
+	private static final class AlphabeticalOrderComparator implements Comparator<PlayerInRoom> {
+		public static final AlphabeticalOrderComparator INSTANCE = new AlphabeticalOrderComparator();
+		public int compare(PlayerInRoom lhs, PlayerInRoom rhs) {
+			return lhs.player.name.compareToIgnoreCase(rhs.player.name);
+		};
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomPlayerlistFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,103 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.RunGameListener;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+
+public class RoomPlayerlistFragment extends ListFragment implements OnItemClickListener, RunGameListener {
+	private Netplay netplay;
+	private RoomPlayerlistAdapter adapter;
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		netplay.registerRunGameListener(this);
+		adapter = new RoomPlayerlistAdapter();
+		adapter.setSource(netplay.roomPlayerlist);
+		setListAdapter(adapter);
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+		netplay.unregisterRunGameListener(this);
+	}
+	
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		registerForContextMenu(getListView());
+		getListView().setOnItemClickListener(this);
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v,
+			ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
+		String playerName = adapter.getItem(info.position).player.name;
+		
+		MenuInflater inflater = getActivity().getMenuInflater();
+		inflater.inflate(R.menu.room_playerlist_context, menu);
+		if(netplay.isChief() && !playerName.equals(netplay.getPlayerName())) {
+			inflater.inflate(R.menu.room_playerlist_chief_context, menu);
+		}
+		menu.setHeaderIcon(R.drawable.human);
+		menu.setHeaderTitle(playerName);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
+		PlayerInRoom player = adapter.getItem(info.position);
+		switch(item.getItemId()) {
+		case R.id.player_info:
+			netplay.sendPlayerInfoQuery(player.player.name);
+			return true;
+		case R.id.player_kick:
+			netplay.sendKick(player.player.name);
+			return true;
+		default:
+			return super.onContextItemSelected(item);
+		}
+	}
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		return inflater.inflate(R.layout.fragment_playerlist, container, false);
+	}
+	
+	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+		Player player = adapter.getItem(position).player;
+		if(player.name.equals(netplay.getPlayerName())) {
+			netplay.sendToggleReady();
+		}
+	}
+	
+	// TODO this is really the wrong place for this...
+	public void runGame(GameConfig config) {
+		SDLActivity.startConfig = config;
+		SDLActivity.startNetgame = true;
+		startActivity(new Intent(getActivity().getApplicationContext(), SDLActivity.class));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomStateManager.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,70 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+
+/**
+ * This interface is supposed to abstract the handling of room state for several fragments
+ * that can display and manipulate it. The purpose of this is to allow using these fragments
+ * both for setting up networked and local games, despite the fact that for local games
+ * the settings can be changed immediately in memory, while they have to be sent out to the
+ * server for networked games.
+ * 
+ * If/when the state changes as result of calling one of the "changeX" functions, that will
+ * also trigger the corresponding change listener method. There is no guarantee that calling
+ * a changeX method will actually change the setting (e.g. if you're not room chief).
+ * 
+ * For local games, getChiefStatus is always true.
+ * 
+ * Implementations of this interface are probably not thread safe and should only be used on
+ * the UI thread.
+ */
+public interface RoomStateManager {
+	// Query current state
+	MapRecipe getMapRecipe();
+	boolean getChiefStatus();
+	Scheme getScheme();
+	String getGameStyle();
+	Weaponset getWeaponset();
+	
+	// Manipulate state
+	void changeMapRecipe(MapRecipe map);
+	void changeMapTheme(String theme);
+
+	/**
+	 * This function sets both the map's name and generator. There is no function
+	 * to change them independendly since e.g. the QtFrontend relies on them being
+	 * consistent.
+	 * 
+	 * If the name parameter is equal to one of the MapRecipe.MAPNAME_REGULAR, MAPNAME_MAZE
+	 * or MAPNAME_DRAWN constants, the map generator is set accordingly. Otherwise, the
+	 * map generator is set to represent a mapfile. The map's name is always set to
+	 * the parameter.
+	 */
+	void changeMapNameAndGenerator(String mapName);
+	void changeMapTemplate(int template);
+	void changeMazeSize(int mazeSize);
+	void changeMapSeed(String seed);
+	void changeMapDrawdata(byte[] drawdata);
+	
+	void changeScheme(Scheme scheme);
+	void changeGameStyle(String style);
+	void changeWeaponset(Weaponset weaponset);
+	
+	// Observe state
+	void registerObserver(Observer observer);
+	void unregisterObserver(Observer observer);
+	
+	public interface Observer {
+		void onMapChanged(MapRecipe recipe);
+		void onChiefStatusChanged(boolean isChief);
+		void onSchemeChanged(Scheme scheme);
+		void onGameStyleChanged(String gameStyle);
+		void onWeaponsetChanged(Weaponset weaponset);
+	}
+	
+	public interface Provider {
+		RoomStateManager getRoomStateManager();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomlistAdapter.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,91 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.RoomWithId;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class RoomlistAdapter extends ObservableTreeMapAdapter<String, RoomWithId> {
+	private Context context;
+	
+	public RoomlistAdapter(Context context) {
+		this.context = context;
+	}
+	
+	@Override
+	protected Comparator<RoomWithId> getEntryOrder() {
+		return RoomWithId.NEWEST_FIRST_ORDER;
+	}
+	
+	@Override
+	public long getItemId(int position) {
+		return getItem(position).id;
+	}
+	
+	@Override
+	public boolean hasStableIds() {
+		return true;
+	}
+	
+	private static CharSequence formatExtra(Resources res, Room room) {
+		String ownermsg = res.getString(R.string.roomlist_owner, room.owner);
+		String mapmsg = res.getString(R.string.roomlist_map, room.formatMapName(res));
+		String scheme = room.scheme.equals(room.weapons) ? room.scheme : room.scheme + " / " + room.weapons;
+		String schememsg = res.getString(R.string.roomlist_scheme, scheme);
+		return ownermsg + ". " + mapmsg + ", " + schememsg;
+	}
+	
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(context);
+			v = vi.inflate(R.layout.listview_room, null);
+		}
+		
+		Room room = getItem(position).room;
+		int iconRes = room.inProgress ? R.drawable.roomlist_ingame : R.drawable.roomlist_preparing;
+		
+		if(v.findViewById(android.R.id.text1) == null) {
+			// Tabular room list
+			TextView roomnameView = (TextView)v.findViewById(R.id.roomname);
+			TextView playerCountView = (TextView)v.findViewById(R.id.playercount);
+			TextView teamCountView = (TextView)v.findViewById(R.id.teamcount);
+			TextView ownerView = (TextView)v.findViewById(R.id.owner);
+			TextView mapView = (TextView)v.findViewById(R.id.map);
+			TextView schemeView = (TextView)v.findViewById(R.id.scheme);
+			TextView weaponView = (TextView)v.findViewById(R.id.weapons);
+			
+			roomnameView.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
+			roomnameView.setText(room.name);
+			if(playerCountView != null) {
+				playerCountView.setText(String.valueOf(room.playerCount));
+			}
+			if(teamCountView != null) {
+				teamCountView.setText(String.valueOf(room.teamCount));
+			}
+			ownerView.setText(room.owner);
+			mapView.setText(room.formatMapName(context.getResources()));
+			schemeView.setText(room.scheme);
+			weaponView.setText(room.weapons);
+		} else {
+			// Small room list
+			TextView v1 = (TextView)v.findViewById(android.R.id.text1);
+			TextView v2 = (TextView)v.findViewById(android.R.id.text2);
+			
+			v1.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
+			v1.setText(room.name);
+			v2.setText(formatExtra(context.getResources(), room));
+		}
+		
+		return v;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomlistFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,72 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.support.v4.app.ListFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+
+public class RoomlistFragment extends ListFragment implements OnItemClickListener {
+	private static final int AUTO_REFRESH_INTERVAL_MS = 15000;
+	
+	private Netplay netplay;
+	private RoomlistAdapter adapter;
+	private CountDownTimer autoRefreshTimer = new CountDownTimer(Long.MAX_VALUE, AUTO_REFRESH_INTERVAL_MS) {
+		@Override
+		public void onTick(long millisUntilFinished) {
+			netplay.sendRoomlistRequest();
+		}
+		
+		@Override
+		public void onFinish() { }
+	};
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new RoomlistAdapter(getActivity());
+		adapter.setSource(netplay.roomList);
+		setListAdapter(adapter);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		return inflater.inflate(R.layout.lobby_rooms_fragment, container, false);
+	}
+	
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		getListView().setOnItemClickListener(this);
+	}
+	
+	@Override
+	public void onResume() {
+		super.onResume();
+		autoRefreshTimer.start();
+	}
+	
+	@Override
+	public void onPause() {
+		super.onPause();
+		autoRefreshTimer.cancel();
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+	}
+	
+	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+		netplay.sendJoinRoom(adapter.getItem(position).room.name);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SDLActivity.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SDLActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -1,14 +1,25 @@
 package org.hedgewars.hedgeroid;
 
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
 import javax.microedition.khronos.egl.EGLDisplay;
 import javax.microedition.khronos.egl.EGLSurface;
 
-import org.hedgewars.hedgeroid.EngineProtocol.EngineProtocolNetwork;
-import org.hedgewars.hedgeroid.EngineProtocol.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
 import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.IntCallback;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.util.FileUtils;
+import org.hedgewars.hedgeroid.util.TickHandler;
+
+import com.sun.jna.Pointer;
 
 import android.app.Activity;
 import android.content.Context;
@@ -23,7 +34,9 @@
 import android.media.AudioTrack;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Message;
+import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -37,13 +50,20 @@
     SDL Activity
  */
 public class SDLActivity extends Activity {
-
+	/**
+	 * Set startConfig to the desired config when starting this activity. This avoids having to parcel all
+	 * the config objects into the Intent. Not particularly elegant, but it's actually a recommended
+	 * way to do this (http://developer.android.com/guide/faq/framework.html#3)
+	 */
+	public static volatile GameConfig startConfig;
+	public static volatile boolean startNetgame;
+	
 	// Main components
 	public static SDLActivity mSingleton;
 	private static SDLSurface mSurface;
 
 	// This is what SDL runs in. It invokes SDL_main(), eventually
-	private static Thread mSDLThread;
+	private static Thread mSDLThread; // Guarded by SDLActivity.class
 
 	// Audio
 	private static Thread mAudioThread;
@@ -74,11 +94,8 @@
 		mSingleton = this;
 
 		// Set up the surface
-		GameConfig config = getIntent().getParcelableExtra("config");
-
-		mSurface = new SDLSurface(getApplication(), config);
+		mSurface = new SDLSurface(getApplication(), startConfig, startNetgame);
 		setContentView(mSurface);
-		SurfaceHolder holder = mSurface.getHolder();
 	}
 
 	// Events
@@ -108,15 +125,15 @@
 		SDLActivity.nativeQuit();
 
 		// Now wait for the SDL thread to quit
-		if (mSDLThread != null) {
-			try {
-				mSDLThread.join();
-			} catch(Exception e) {
-				Log.v("SDL", "Problem stopping thread: " + e);
+		synchronized(SDLActivity.class) {
+			if (mSDLThread != null) {
+				try {
+					mSDLThread.join();
+				} catch(Exception e) {
+					Log.w("SDL", "Problem stopping thread: " + e);
+				}
+				mSDLThread = null;
 			}
-			mSDLThread = null;
-
-			//Log.v("SDL", "Finished waiting for SDL thread");
 		}
 	}
 
@@ -140,8 +157,14 @@
 		commandHandler.sendMessage(msg);
 	}
 
+	public static void synchronizedNativeInit(String...args) {
+		synchronized(PascalExports.engineMutex) {
+			nativeInit(args);
+		}
+	}
+	
 	// C functions we call
-	public static native void nativeInit(String...args);
+	private static native void nativeInit(String...args);
 	public static native void nativeQuit();
 	public static native void nativePause();
 	public static native void nativeResume();
@@ -174,14 +197,32 @@
 		return mSingleton;
 	}
 
-	public static void startApp(int width, int height, GameConfig config) {
-		// Start up the C app thread
-		if (mSDLThread == null) {
-			mSDLThread = new Thread(new SDLMain(width, height, config), "SDLThread");
-			mSDLThread.start();
-		}
-		else {
-			SDLActivity.nativeResume();
+	public static void startApp(final int width, final int height, GameConfig config, boolean netgame) {
+		synchronized(SDLActivity.class) {
+			// Start up the C app thread TODO this is silly code
+			if (mSDLThread == null) {
+				final AtomicBoolean gameconnStartDone = new AtomicBoolean(false);
+				GameConnection.Listener listener = new GameConnection.Listener() {
+					public void gameConnectionReady(int port) {
+						mSDLThread = new Thread(new SDLMain(width, height, port, "Medo"));
+						mSDLThread.start();
+						gameconnStartDone.set(true);
+					}
+					
+					public void gameConnectionDisconnected(int reason) {
+						Log.e("startApp", "disconnected: "+reason);
+						gameconnStartDone.set(true);
+					}
+				};
+				if(netgame) {
+					Netplay netplay = Netplay.getAppInstance(mSingleton.getApplicationContext());
+					GameConnection.forNetgame(config, netplay, listener);
+				} else {
+					GameConnection.forLocalGame(config, listener);
+				}
+			} else {
+				SDLActivity.nativeResume();
+			}
 		}
 	}
 
@@ -414,36 +455,33 @@
  */
 class SDLMain implements Runnable {
 
-	private int surfaceWidth, surfaceHeight;
-	private GameConfig config;
-
-	public SDLMain(int width, int height, GameConfig _config) {
-		config = _config;
+	private final int surfaceWidth, surfaceHeight;
+	private final int port;
+	private final String playerName;
+	HandlerThread thread = new HandlerThread("IPC thread");
+	
+	public SDLMain(int width, int height, int port, String playerName) {
 		surfaceWidth = width;
 		surfaceHeight = height;
+		this.port = port;
+		this.playerName = playerName;
 	}
 
 	public void run() {
 		//Set up the IPC socket server to communicate with the engine
-		EngineProtocolNetwork ipc = new EngineProtocolNetwork(config);
-
-		String path = Utils.getDataPath(SDLActivity.mSingleton);//This represents the data directory
+		String path = FileUtils.getDataPath(SDLActivity.mSingleton);//This represents the data directory
 		path = path.substring(0, path.length()-1);//remove the trailing '/'
 
-
+		Log.d("SDLMain", "Starting engine");
 		// Runs SDL_main() with added parameters
-		SDLActivity.nativeInit(new String[] { String.valueOf(ipc.port),
-				String.valueOf(surfaceWidth), String.valueOf(surfaceHeight),
-				"0", "en.txt", "xeli", "1", "1", "1", path, ""  });
-
 		try {
-			ipc.quitIPC();
-			ipc.join();
-		} catch (InterruptedException e) {
-			e.printStackTrace();
+			SDLActivity.synchronizedNativeInit(new String[] { String.valueOf(port),
+					String.valueOf(surfaceWidth), String.valueOf(surfaceHeight),
+					"0", "en.txt", Base64.encodeToString(playerName.getBytes("UTF-8"), 0), "1", "1", "1", path, ""  });
+		} catch (UnsupportedEncodingException e) {
+			throw new AssertionError(e); // never happens
 		}
-		Log.v("SDL", "SDL thread terminated");
-		//Log.v("SDL", "SDL thread terminated");
+		Log.d("SDLMain", "Engine stopped");
 	}
 }
 
@@ -458,12 +496,13 @@
 View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
 
 	private GameConfig config;
-
+	private boolean netgame;
+	
 	// Sensors
 	private static SensorManager mSensorManager;
 
 	// Startup    
-	public SDLSurface(Context context, GameConfig _config) {
+	public SDLSurface(Context context, GameConfig _config, boolean netgame) {
 		super(context);
 		getHolder().addCallback(this); 
 
@@ -475,6 +514,7 @@
 
 		mSensorManager = (SensorManager)context.getSystemService("sensor");
 		config = _config;
+		this.netgame = netgame;
 	}
 
 	// Called when we have a valid drawing surface
@@ -544,7 +584,7 @@
 		SDLActivity.onNativeResize(width, height, sdlFormat);
 		Log.v("SDL", "Window size:" + width + "x"+height);
 
-		SDLActivity.startApp(width, height, config);
+		SDLActivity.startApp(width, height, config, netgame);
 	}
 
 	// unused
@@ -580,34 +620,29 @@
 
 	// Touch events
 	public boolean onTouch(View v, MotionEvent event) {
-		{
-			final int touchDevId = event.getDeviceId();
-			final int pointerCount = event.getPointerCount();
-			// touchId, pointerId, action, x, y, pressure
-			int actionPointerIndex = event.getActionIndex();
-			int pointerFingerId = event.getPointerId(actionPointerIndex);
-			int action = event.getActionMasked();
-
-			float x = event.getX(actionPointerIndex);
-			float y = event.getY(actionPointerIndex);
-			float p = event.getPressure(actionPointerIndex);
+		final int action = event.getAction() & MotionEvent.ACTION_MASK;
+		final int actionPointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;		
 
-			if (action == MotionEvent.ACTION_MOVE && pointerCount > 1) {
-				// TODO send motion to every pointer if its position has
-				// changed since prev event.
-				for (int i = 0; i < pointerCount; i++) {
-					pointerFingerId = event.getPointerId(i);
-					x = event.getX(i);
-					y = event.getY(i);
-					p = event.getPressure(i);
-					SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
-				}
-			} else {
-				SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
+		if (action == MotionEvent.ACTION_MOVE) {
+			// TODO send motion to every pointer if its position has
+			// changed since prev event.
+			for (int i = 0; i < event.getPointerCount(); i++) {
+				sendNativeTouch(event, action, i);
 			}
+		} else {
+			sendNativeTouch(event, action, actionPointerIndex);
 		}
 		return true;
 	} 
+	
+	private static void sendNativeTouch(MotionEvent event, int action, int pointerIndex) {
+		int touchDevId = event.getDeviceId();
+		int pointerFingerId = event.getPointerId(pointerIndex);
+		float x = event.getX(pointerIndex);
+		float y = event.getY(pointerIndex);
+		float pressure = event.getPressure(pointerIndex);
+		SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, pressure);
+	}
 
 	// Sensor events
 	public void enableSensor(int sensortype, boolean enabled) {
@@ -633,6 +668,5 @@
 					event.values[2] / SensorManager.GRAVITY_EARTH);
 		}
 	}
-
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SchemeCreatorActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,7 @@
+package org.hedgewars.hedgeroid;
+
+import android.app.Activity;
+
+public class SchemeCreatorActivity extends Activity {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SchemeListActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,8 @@
+package org.hedgewars.hedgeroid;
+
+import android.app.Activity;
+
+// TODO
+public class SchemeListActivity extends Activity {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SettingsFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,201 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Schemes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.util.FileUtils;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+public class SettingsFragment extends Fragment implements RoomStateManager.Observer {
+	private Spinner styleSpinner, schemeSpinner, weaponsetSpinner, themeSpinner;
+	private ImageView themeIcon;
+	
+	private List<String> styles;
+	private List<Scheme> schemes;
+	private List<Weaponset> weaponsets;
+	private List<String> themes;
+	
+	private RoomStateManager stateManager;
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View v = inflater.inflate(R.layout.fragment_settings, container, false);
+		themeIcon = (ImageView)v.findViewById(R.id.imgTheme);
+		
+		try {
+			styles = FrontendDataUtils.getGameStyles(getActivity());
+			schemes = Schemes.loadAllSchemes(getActivity());
+			weaponsets = Weaponsets.loadAllWeaponsets(getActivity());
+			themes = FrontendDataUtils.getThemes(getActivity());
+		} catch (IOException e) {
+			Toast.makeText(getActivity().getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			getActivity().finish();
+		}
+		
+		Collections.sort(styles, String.CASE_INSENSITIVE_ORDER);
+		Collections.sort(schemes, Scheme.NAME_ORDER);
+		Collections.sort(weaponsets, Weaponset.NAME_ORDER);
+		Collections.sort(themes, String.CASE_INSENSITIVE_ORDER);
+		
+		styleSpinner = prepareSpinner(v, R.id.spinGameplay, styles, styleSelectedListener);
+		schemeSpinner = prepareSpinner(v, R.id.spinGamescheme, Schemes.toNameList(schemes), schemeSelectedListener);
+		weaponsetSpinner = prepareSpinner(v, R.id.spinweapons, Weaponsets.toNameList(weaponsets), weaponsetSelectedListener);
+		themeSpinner = prepareSpinner(v, R.id.spinTheme, themes, themeSelectedListener);
+		
+		stateManager.registerObserver(this);
+
+		if(stateManager.getGameStyle() != null) {
+			styleSpinner.setSelection(styles.indexOf(stateManager.getGameStyle()), false);
+		}
+		if(stateManager.getScheme() != null) {
+			schemeSpinner.setSelection(getSchemePosition(schemes, stateManager.getScheme().name), false);
+		}
+		if(stateManager.getWeaponset() != null) {
+			weaponsetSpinner.setSelection(getWeaponsetPosition(weaponsets, stateManager.getWeaponset().name), false);
+		}
+		if(stateManager.getMapRecipe() != null) {
+			themeSpinner.setSelection(themes.indexOf(stateManager.getMapRecipe().theme), false);
+		}
+		
+		setChiefState(stateManager.getChiefStatus());
+		
+		return v;
+	}
+	
+	private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) {
+		Spinner spinner = (Spinner)v.findViewById(id);
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(itemSelectedListener);
+		return spinner;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		try {
+			stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager();
+		} catch(ClassCastException e) {
+			throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e);
+		}
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		stateManager.unregisterObserver(this);
+	}
+	
+	private static int getSchemePosition(List<Scheme> schemes, String scheme) {
+		for(int i=0; i<schemes.size(); i++) {
+			if(schemes.get(i).name.equals(scheme)) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private static int getWeaponsetPosition(List<Weaponset> weaponsets, String weaponset) {
+		for(int i=0; i<weaponsets.size(); i++) {
+			if(weaponsets.get(i).name.equals(weaponset)) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private void setChiefState(boolean chiefState) {
+		styleSpinner.setEnabled(chiefState);
+		schemeSpinner.setEnabled(chiefState);
+		weaponsetSpinner.setEnabled(chiefState);
+		themeSpinner.setEnabled(chiefState);
+		
+		if(chiefState) {
+			stateManager.changeGameStyle(styles.get(styleSpinner.getSelectedItemPosition()));
+			stateManager.changeScheme(schemes.get(schemeSpinner.getSelectedItemPosition()));
+			stateManager.changeWeaponset(weaponsets.get(weaponsetSpinner.getSelectedItemPosition()));
+			stateManager.changeMapTheme(themes.get(themeSpinner.getSelectedItemPosition()));
+		}
+	}
+	
+	private final OnItemSelectedListener styleSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeGameStyle(styles.get(position));
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener schemeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeScheme(schemes.get(position));
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener weaponsetSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeWeaponset(weaponsets.get(position));
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener themeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeMapTheme(themes.get(position));
+			String theme = themes.get(position);
+			try {
+				File iconFile = new File(FileUtils.getDataPathFile(getActivity()), "Themes/" + theme + "/icon@2X.png");
+				Drawable themeIconDrawable = Drawable.createFromPath(iconFile.getAbsolutePath());
+				themeIcon.setImageDrawable(themeIconDrawable);
+			} catch (FileNotFoundException e) {
+				Log.e("SettingsFragment", "Unable to find preview for theme "+theme, e);
+			}
+		};
+		public void onNothingSelected(AdapterView<?> arg0) {};
+	};
+	
+	public void onChiefStatusChanged(boolean isChief) {
+		setChiefState(isChief);
+	}
+	
+	public void onGameStyleChanged(String gameStyle) {
+		styleSpinner.setSelection(styles.indexOf(gameStyle));
+	}
+	
+	public void onMapChanged(MapRecipe recipe) {
+		themeSpinner.setSelection(themes.indexOf(recipe.theme));
+	}
+	
+	public void onSchemeChanged(Scheme scheme) {
+		schemeSpinner.setSelection(getSchemePosition(schemes, scheme.name));
+	}
+	
+	public void onWeaponsetChanged(Weaponset weaponset) {
+		weaponsetSpinner.setSelection(getWeaponsetPosition(weaponsets, weaponset.name));
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/StartGameActivity.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/StartGameActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -19,21 +19,29 @@
 
 package org.hedgewars.hedgeroid;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
 import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
-import org.hedgewars.hedgeroid.Datastructures.Map;
-import org.hedgewars.hedgeroid.Datastructures.Map.MapType;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.MapFile;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
 import org.hedgewars.hedgeroid.Datastructures.Scheme;
-import org.hedgewars.hedgeroid.Datastructures.Team;
-import org.hedgewars.hedgeroid.Datastructures.Weapon;
-import org.hedgewars.hedgeroid.EngineProtocol.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.Schemes;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.app.Activity;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Parcelable;
-import android.preference.PreferenceManager;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.AdapterView;
@@ -45,32 +53,29 @@
 import android.widget.Toast;
 
 public class StartGameActivity extends Activity {
-
 	public static final int ACTIVITY_TEAM_SELECTOR = 0;
+	
+	private ImageButton start, back, team;
+	private Spinner mapSpinner, styleSpinner, schemeSpinner, weaponsetSpinner, themeSpinner;
+	private ImageView themeIcon, mapPreview, teamCount;
 
-	private GameConfig config = null;
-	private ImageButton start, back, team;
-	private Spinner maps, gameplay, gamescheme, weapons, themes;
-	private ImageView themeIcon, mapPreview, teamCount;
+	private List<MapFile> mapFiles;
+	private List<String> styles;
+	private List<Scheme> schemes;
+	private List<Weaponset> weaponsets;
+	private List<String> themes;
+	
+	private List<TeamInGame> teams = new ArrayList<TeamInGame>();
 
 	public void onCreate(Bundle savedInstanceState){
 		super.onCreate(savedInstanceState);
 
-		Scheme.parseBasicFlags(this);
-		config = new GameConfig();
-
 		setContentView(R.layout.starting_game);
 
 		back = (ImageButton) findViewById(R.id.btnBack);
 		team = (ImageButton) findViewById(R.id.btnTeams);
 		start = (ImageButton) findViewById(R.id.btnStart);
 
-		maps = (Spinner) findViewById(R.id.spinMaps);
-		gameplay = (Spinner) findViewById(R.id.spinGameplay);
-		gamescheme = (Spinner) findViewById(R.id.spinGamescheme);
-		weapons = (Spinner) findViewById(R.id.spinweapons);
-		themes = (Spinner) findViewById(R.id.spinTheme);
-
 		themeIcon = (ImageView) findViewById(R.id.imgTheme);
 		mapPreview = (ImageView) findViewById(R.id.mapPreview);
 		teamCount = (ImageView) findViewById(R.id.imgTeamsCount);
@@ -79,62 +84,58 @@
 		back.setOnClickListener(backClicker);
 		team.setOnClickListener(teamClicker);
 
-		ArrayAdapter<?> adapter = new ArrayAdapter<Map>(this, R.layout.listview_item, FrontendDataUtils.getMaps(this));
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		maps.setAdapter(adapter);
-		maps.setOnItemSelectedListener(mapsClicker);
-		//set to first nonmap
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((Map)adapter.getItem(i)).getType() == MapType.TYPE_DEFAULT){
-				maps.setSelection(i, false);
-				break;
-			}
+		try {
+			mapFiles = FrontendDataUtils.getMaps(this);
+			styles = FrontendDataUtils.getGameStyles(this);
+			schemes = Schemes.loadAllSchemes(this);
+			weaponsets = Weaponsets.loadAllWeaponsets(this);
+			themes = FrontendDataUtils.getThemes(this);
+		} catch (IOException e) {
+			Toast.makeText(getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			finish();
 		}
-
-		adapter = new ArrayAdapter<String>(this, R.layout.listview_item, FrontendDataUtils.getGameplay(this));
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		gameplay.setAdapter(adapter);
-		gameplay.setOnItemSelectedListener(gameplayClicker);
-		//set to first nonmap
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((String)adapter.getItem(i)).equals("None")){
-				gameplay.setSelection(i, false);
+		
+		Collections.sort(mapFiles, MapFile.MISSIONS_FIRST_NAME_ORDER);
+		Collections.sort(styles, String.CASE_INSENSITIVE_ORDER);
+		Collections.sort(schemes, Scheme.NAME_ORDER);
+		Collections.sort(weaponsets, Weaponset.NAME_ORDER);
+		Collections.sort(themes, String.CASE_INSENSITIVE_ORDER);
+		
+		List<String> mapNames = MapFile.toDisplayNameList(mapFiles, getResources());
+		List<String> schemeNames = Schemes.toNameList(schemes);
+		List<String> weaponsetNames = Weaponsets.toNameList(weaponsets);
+		View rootView = findViewById(android.R.id.content);
+		mapSpinner = prepareSpinner(rootView, R.id.spinMaps, mapNames, mapsClicker);
+		styleSpinner = prepareSpinner(rootView, R.id.spinGameplay, styles, null);
+		schemeSpinner = prepareSpinner(rootView, R.id.spinGamescheme, schemeNames, null);
+		weaponsetSpinner = prepareSpinner(rootView, R.id.spinweapons, weaponsetNames, null);
+		themeSpinner = prepareSpinner(rootView, R.id.spinTheme, themes, themesClicker);
+		
+		// set map to first nonmission
+		for(int i = 0; i < mapFiles.size(); i++){
+			if(!mapFiles.get(i).isMission){
+				mapSpinner.setSelection(i, false);
 				break;
 			}
 		}
-
-		adapter = new ArrayAdapter<Scheme>(this, R.layout.listview_item, FrontendDataUtils.getSchemes(this));
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		gamescheme.setAdapter(adapter);
-		gamescheme.setOnItemSelectedListener(schemeClicker);
-		//set to first nonmap
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((Scheme)adapter.getItem(i)).toString().equals("Default")){
-				gamescheme.setSelection(i, false);
-				break;
-			}
-		}
-		
-		
-		adapter = new ArrayAdapter<Weapon>(this, R.layout.listview_item, FrontendDataUtils.getWeapons(this));
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		weapons.setAdapter(adapter);
-		weapons.setOnItemSelectedListener(weaponClicker);
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((Weapon)adapter.getItem(i)).toString().equals("Crazy")){
-				weapons.setSelection(i, false);
-				break;
-			}
-		}
-		adapter = new ArrayAdapter<String>(this, R.layout.listview_item, FrontendDataUtils.getThemes(this));
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		themes.setAdapter(adapter);
-		themes.setOnItemSelectedListener(themesClicker);
+		styleSpinner.setSelection(styles.indexOf(GameConfig.DEFAULT_STYLE), false);
+		schemeSpinner.setSelection(schemeNames.indexOf(GameConfig.DEFAULT_SCHEME), false);
+		weaponsetSpinner.setSelection(weaponsetNames.indexOf(GameConfig.DEFAULT_WEAPONSET), false);
+		themeSpinner.setSelection(themes.indexOf(GameConfig.DEFAULT_THEME), false);
 	}
 
+	private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) {
+		Spinner spinner = (Spinner)v.findViewById(id);
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(itemSelectedListener);
+		return spinner;
+	}
+	
 	private void startTeamsActivity(){
 		Intent i = new Intent(StartGameActivity.this, TeamSelectionActivity.class);
-		i.putParcelableArrayListExtra("teams", config.teams);
+		TeamSelectionActivity.activityParams = new ArrayList<TeamInGame>(teams);
 		startActivityForResult(i, ACTIVITY_TEAM_SELECTOR);
 	}
 
@@ -142,12 +143,9 @@
 		switch(requestCode){
 		case ACTIVITY_TEAM_SELECTOR:
 			if(resultCode == Activity.RESULT_OK){
-				Parcelable[] parcelables = (Parcelable[])data.getParcelableArrayExtra("teams");
-				config.teams.clear();
-				for(Parcelable t : parcelables){
-					config.teams.add((Team)t);
-				}
-				teamCount.getDrawable().setLevel(config.teams.size());
+				teams = new ArrayList<TeamInGame>(TeamSelectionActivity.activityReturn);
+				TeamSelectionActivity.activityReturn = Collections.emptyList();
+				teamCount.getDrawable().setLevel(teams.size());
 			}
 			break;
 		}
@@ -157,10 +155,9 @@
 	private OnItemSelectedListener themesClicker = new OnItemSelectedListener(){
 
 		public void onItemSelected(AdapterView<?> arg0, View view, int position, long rowId) {
-			String themeName = (String) arg0.getAdapter().getItem(position);
-			Drawable themeIconDrawable = Drawable.createFromPath(Utils.getDataPath(StartGameActivity.this) + "Themes/" + themeName + "/icon@2X.png");
+			String themeName = themes.get(position);
+			Drawable themeIconDrawable = Drawable.createFromPath(FileUtils.getDataPath(StartGameActivity.this) + "Themes/" + themeName + "/icon@2X.png");
 			themeIcon.setImageDrawable(themeIconDrawable);
-			config.theme = themeName;
 		}
 
 		public void onNothingSelected(AdapterView<?> arg0) {
@@ -171,9 +168,13 @@
 	private OnItemSelectedListener mapsClicker = new OnItemSelectedListener(){
 
 		public void onItemSelected(AdapterView<?> arg0, View view, int position,long rowId) {
-			Map map = (Map)arg0.getAdapter().getItem(position);
-			mapPreview.setImageDrawable(map.getDrawable());
-			config.map = map;
+			MapFile map = mapFiles.get(position);
+			try {
+				File previewFile = map.getPreviewFile(getApplicationContext());
+				mapPreview.setImageDrawable(Drawable.createFromPath(previewFile.getAbsolutePath()));
+			} catch (FileNotFoundException e) {
+				mapPreview.setImageDrawable(null);
+			}
 		}
 
 		public void onNothingSelected(AdapterView<?> arg0) {
@@ -181,41 +182,23 @@
 
 	};
 
-	private OnItemSelectedListener weaponClicker = new OnItemSelectedListener(){
-		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
-			config.weapon = (Weapon)arg0.getAdapter().getItem(arg2);
-		}
-		public void onNothingSelected(AdapterView<?> arg0) {
-
-		}
-	};
-	private OnItemSelectedListener schemeClicker = new OnItemSelectedListener(){
-		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
-			config.scheme = (Scheme)arg0.getAdapter().getItem(arg2);
-		}
-		public void onNothingSelected(AdapterView<?> arg0) {
-
-		}
-	};
-	private OnItemSelectedListener gameplayClicker = new OnItemSelectedListener(){
-		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
-			//config = ()arg0.getAdapter().getItem(arg2);
-		}
-		public void onNothingSelected(AdapterView<?> arg0) {
-
-		}
-	};
-
 	private OnClickListener startClicker = new OnClickListener(){
 		public void onClick(View v) {
-			if(config.teams.size() < 2){
-				Toast.makeText(StartGameActivity.this, R.string.not_enough_teams, Toast.LENGTH_LONG).show();
+			if(teams.size() < 2) {
+				Toast.makeText(getApplicationContext(), R.string.not_enough_teams, Toast.LENGTH_LONG).show();
 				startTeamsActivity();
+			} else {
+				String style = styles.get(styleSpinner.getSelectedItemPosition());
+				Scheme scheme = schemes.get(schemeSpinner.getSelectedItemPosition());
+				String mapName = mapFiles.get(mapSpinner.getSelectedItemPosition()).name;
+				String theme = themes.get(themeSpinner.getSelectedItemPosition());
+				MapRecipe map = MapRecipe.makeMap(mapName, UUID.randomUUID().toString(), theme);
+				Weaponset weaponset = weaponsets.get(weaponsetSpinner.getSelectedItemPosition());
+				SDLActivity.startConfig = new GameConfig(style, scheme, map, teams, weaponset);
+				SDLActivity.startNetgame = false;
+				Intent i = new Intent(StartGameActivity.this, SDLActivity.class);
+				startActivity(i);
 			}
-			else{
-				Intent i = new Intent(StartGameActivity.this, SDLActivity.class);
-				i.putExtra("config", config);
-				startActivity(i);}
 		}
 	};
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/StartNetgameDialog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,76 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class StartNetgameDialog extends DialogFragment {
+	private static final String PREF_PLAYERNAME = "playerName";
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		SharedPreferences prefs = getActivity().getSharedPreferences("settings", Context.MODE_PRIVATE);
+		final String playerName = prefs.getString(PREF_PLAYERNAME, "Player");
+		final EditText editText = new EditText(getActivity());
+		final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		
+		editText.setText(playerName);
+		editText.setHint(R.string.start_netgame_dialog_playername_hint);
+		editText.setId(android.R.id.text1);
+		editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
+		editText.setSingleLine();
+
+		builder.setTitle(R.string.start_netgame_dialog_title);
+		builder.setMessage(R.string.start_netgame_dialog_message);
+		builder.setView(editText);
+		builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				editText.setText(playerName);
+			}
+		});
+		
+		editText.setOnEditorActionListener(new OnEditorActionListener() {
+			public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+				boolean handled = false;
+				if(actionId == EditorInfo.IME_ACTION_DONE) {
+					startConnection(v.getText().toString());
+					handled = true;
+				}
+				return handled;
+			}
+		});
+		
+		builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				startConnection(editText.getText().toString());
+			}
+		});
+
+		return builder.create();
+	}
+	
+	private void startConnection(String username) {
+		if(username.length() > 0) {
+			SharedPreferences prefs = getActivity().getSharedPreferences("settings", Context.MODE_PRIVATE);
+			Editor edit = prefs.edit();
+			edit.putString(PREF_PLAYERNAME, username);
+			edit.commit();
+			
+			Netplay.getAppInstance(getActivity().getApplicationContext()).connectToDefaultServer(username);
+			getDialog().dismiss();
+			((MainActivity)getActivity()).onNetConnectingStarted();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamAddDialog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,92 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+public class TeamAddDialog extends DialogFragment {
+	private static final String STATE_TEAMS_ALREADY_IN_GAME = "teamAlreadyInGame";
+	private ArrayList<String> teamsAlreadyInGame;
+	private List<Team> availableTeams;
+	private Listener listener;
+	
+	public static interface Listener {
+		void onTeamAddDialogSubmitted(Team newTeam);
+	}
+	
+	public TeamAddDialog() {
+		// Only for reflection-based instantiation by the framework
+	}
+	
+	TeamAddDialog(Collection<String> teamsAlreadyInGame) {
+		this.teamsAlreadyInGame = new ArrayList<String>(teamsAlreadyInGame);
+	}
+	
+	@Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		try {
+			listener = (Listener) activity;
+		} catch(ClassCastException e) {
+			throw new ClassCastException("Activity " + activity + " must implement TeamAddDialog.Listener to use TeamAddDialog.");
+		}
+	}
+	
+	@Override
+	public void onDetach() {
+		super.onDetach();
+		listener = null;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		if(savedInstanceState != null) {
+			teamsAlreadyInGame = savedInstanceState.getStringArrayList(STATE_TEAMS_ALREADY_IN_GAME);
+		}
+		availableTeams = new ArrayList<Team>();
+		List<Team> teams = FrontendDataUtils.getTeams(getActivity());
+		for(Team team : teams) {
+			if(!teamsAlreadyInGame.contains(team.name)) {
+				availableTeams.add(team);
+			}
+		}
+		Collections.sort(availableTeams, Team.NAME_ORDER);
+	}
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		builder.setTitle(R.string.dialog_addteam_title);
+		builder.setIcon(R.drawable.human);
+		String[] teamNames = new String[availableTeams.size()];
+		for(int i=0; i<availableTeams.size(); i++) {
+			teamNames[i] = availableTeams.get(i).name;
+		}
+		builder.setItems(teamNames, new OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				listener.onTeamAddDialogSubmitted(availableTeams.get(which));
+			}
+		});
+		return builder.create();
+	}
+	
+	@Override
+	public void onSaveInstanceState(Bundle outState) {
+		super.onSaveInstanceState(outState);
+		outState.putStringArrayList(STATE_TEAMS_ALREADY_IN_GAME, teamsAlreadyInGame);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamCreatorActivity.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamCreatorActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -21,14 +21,17 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
 
 import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.Hog;
 import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.app.Activity;
 import android.graphics.Bitmap;
@@ -37,7 +40,6 @@
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.ArrayAdapter;
@@ -49,23 +51,29 @@
 import android.widget.ScrollView;
 import android.widget.SimpleAdapter;
 import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
 import android.widget.TextView;
 import android.widget.Toast;
 
-public class TeamCreatorActivity extends Activity implements Runnable{
-
+/**
+ * Edit or create a team. If a team should be edited, it is supplied in the extras
+ * as parameter oldTeamName.
+ */
+public class TeamCreatorActivity extends Activity implements Runnable {
+	public static final String PARAMETER_EXISTING_TEAMNAME = "existingTeamName";
+	
 	private TextView name;
 	private Spinner difficulty, grave, flag, voice, fort;
 	private ImageView imgFort;
 	private ArrayList<ImageButton> hogDice = new ArrayList<ImageButton>();
 	private ArrayList<Spinner> hogHat = new ArrayList<Spinner>();
 	private ArrayList<EditText> hogName = new ArrayList<EditText>();
-	private ImageButton back, save, voiceButton;
+	private ImageButton voiceButton;
 	private ScrollView scroller;
 	private MediaPlayer mp = null;
-	private boolean settingsChanged = false;
-	private boolean saved = false;
-	private String fileName = null;
+	private boolean initComplete = false;
+	
+	private String existingTeamName = null;
 
 	private final List<HashMap<String, ?>> flagsData = new ArrayList<HashMap<String, ?>>();
 	private final List<HashMap<String, ?>> typesData = new ArrayList<HashMap<String, ?>>();
@@ -76,6 +84,16 @@
 
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
+		initComplete = false;
+		
+		// Restore state and read parameters 
+		if(savedInstanceState != null) {
+			existingTeamName = savedInstanceState.getString(PARAMETER_EXISTING_TEAMNAME);
+		} else {
+			existingTeamName = getIntent().getStringExtra(PARAMETER_EXISTING_TEAMNAME);
+		}
+		
+		// Set up view
 		setContentView(R.layout.team_creation);
 
 		name = (TextView) findViewById(R.id.txtName);
@@ -87,15 +105,11 @@
 
 		imgFort = (ImageView) findViewById(R.id.imgFort);
 
-		back = (ImageButton) findViewById(R.id.btnBack);
-		save = (ImageButton) findViewById(R.id.btnSave);
 		voiceButton = (ImageButton) findViewById(R.id.btnPlay);
 
 		scroller = (ScrollView) findViewById(R.id.scroller);
 
-		save.setOnClickListener(saveClicker);
-		back.setOnClickListener(backClicker);
-
+		// Wire view elements
 		LinearLayout ll = (LinearLayout) findViewById(R.id.HogsContainer);
 		for (int i = 0; i < ll.getChildCount(); i++) {
 			RelativeLayout team_creation_entry = (RelativeLayout) ll.getChildAt(i);
@@ -108,107 +122,116 @@
 					.findViewById(R.id.txtTeam1));
 		}
 
-		SimpleAdapter sa = new SimpleAdapter(this, gravesData,
-				R.layout.spinner_textimg_entry, new String[] { "txt", "img" },
-				new int[] { R.id.spinner_txt, R.id.spinner_img });
-		sa.setDropDownViewResource(R.layout.spinner_textimg_dropdown_entry);
-		sa.setViewBinder(viewBinder);
-		grave.setAdapter(sa);
-		grave.setOnFocusChangeListener(focusser);
-
-		sa = new SimpleAdapter(this, flagsData, R.layout.spinner_textimg_entry,
-				new String[] { "txt", "img" }, new int[] { R.id.spinner_txt,
-				R.id.spinner_img });
-		sa.setDropDownViewResource(R.layout.spinner_textimg_dropdown_entry);
-		sa.setViewBinder(viewBinder);
-		flag.setAdapter(sa);
-		flag.setOnFocusChangeListener(focusser);
-
-		sa = new SimpleAdapter(this, typesData, R.layout.spinner_textimg_entry,
-				new String[] { "txt", "img" }, new int[] { R.id.spinner_txt,
-				R.id.spinner_img });
-		sa.setDropDownViewResource(R.layout.spinner_textimg_dropdown_entry);
-		difficulty.setAdapter(sa);
-		difficulty.setOnFocusChangeListener(focusser);
-
-		sa = new SimpleAdapter(this, hatsData, R.layout.spinner_textimg_entry,
-				new String[] { "txt", "img" }, new int[] { R.id.spinner_txt,
-				R.id.spinner_img });
-		sa.setDropDownViewResource(R.layout.spinner_textimg_dropdown_entry);
-		sa.setViewBinder(viewBinder);
+		grave.setAdapter(createMapSpinnerAdapter(gravesData));
+		flag.setAdapter(createMapSpinnerAdapter(flagsData));
+		difficulty.setAdapter(createMapSpinnerAdapter(typesData));
+		SpinnerAdapter hatAdapter = createMapSpinnerAdapter(hatsData);
 		for (Spinner spin : hogHat) {
-			spin.setAdapter(sa);
+			spin.setAdapter(hatAdapter);
 		}
 
-		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.listview_item, voicesData);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		voice.setAdapter(adapter);
-		voice.setOnFocusChangeListener(focusser);
+
+		voice.setAdapter(createListSpinnerAdapter(voicesData));
 		voiceButton.setOnClickListener(voiceClicker);
 
-		adapter = new ArrayAdapter<String>(this, R.layout.listview_item, fortsData);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		fort.setAdapter(adapter);
+		fort.setAdapter(createListSpinnerAdapter(fortsData));
 		fort.setOnItemSelectedListener(fortSelector);
-		fort.setOnFocusChangeListener(focusser);
 
 		new Thread(this).start();
 	}
 
-	public void run(){
-		final ArrayList<HashMap<String, ?>> gravesDataNew = FrontendDataUtils.getGraves(this);
-		this.runOnUiThread(new Runnable(){
-			public void run() {
-				copy(gravesData, gravesDataNew);
-				((SimpleAdapter)grave.getAdapter()).notifyDataSetChanged();
-			}
-		});
-		
-		final ArrayList<HashMap<String, ?>> flagsDataNew = FrontendDataUtils.getFlags(this);
-		this.runOnUiThread(new Runnable(){
-			public void run() {
-				copy(flagsData, flagsDataNew);
-				((SimpleAdapter)flag.getAdapter()).notifyDataSetChanged();
-			}
-		});
-		
-		final ArrayList<HashMap<String, ?>> typesDataNew = FrontendDataUtils.getTypes(this);
-		this.runOnUiThread(new Runnable(){
-			public void run() {
-				copy(typesData, typesDataNew);
-				((SimpleAdapter)difficulty.getAdapter()).notifyDataSetChanged();
-			}
-		});
-		
-		final ArrayList<HashMap<String, ?>> hatsDataNew = FrontendDataUtils.getHats(this);
-		this.runOnUiThread(new Runnable(){
-			public void run() {
-				copy(hatsData, hatsDataNew);
-				((SimpleAdapter)hogHat.get(0).getAdapter()).notifyDataSetChanged();
-			}
-		});
-		
-		final ArrayList<String> voicesDataNew = FrontendDataUtils.getVoices(this);
-		this.runOnUiThread(new Runnable(){
-			public void run() {
-				copy(voicesData, voicesDataNew);
-				((ArrayAdapter<String>)voice.getAdapter()).notifyDataSetChanged();
-			}
-		});
-		
-		final ArrayList<String> fortsDataNew = FrontendDataUtils.getForts(this);
-		this.runOnUiThread(new Runnable(){
-			public void run() {
-				copy(fortsData, fortsDataNew);
-				((ArrayAdapter<String>)fort.getAdapter()).notifyDataSetChanged();
-			}
-		});
+	private SpinnerAdapter createMapSpinnerAdapter(List<? extends Map<String, ?>> data) {
+		SimpleAdapter sa = new SimpleAdapter(this, data,
+				R.layout.spinner_textimg_entry, new String[] { "txt", "img" },
+				new int[] { R.id.spinner_txt, R.id.spinner_img });
+		sa.setDropDownViewResource(R.layout.spinner_textimg_dropdown_entry);
+		sa.setViewBinder(viewBinder);
+		return sa;
+	}
+	
+	private SpinnerAdapter createListSpinnerAdapter(List<String> data) {
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.listview_item, data);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		return adapter;
 	}
 	
-	private static <T> void copy(List<T> dest, List<T> src){
-		for(T t: src) dest.add(t);
+	public void run(){
+		try {
+			final ArrayList<HashMap<String, ?>> gravesDataNew = FrontendDataUtils.getGraves(this);
+			runOnUiThread(new Runnable(){
+				public void run() {
+					gravesData.addAll(gravesDataNew);
+					((SimpleAdapter)grave.getAdapter()).notifyDataSetChanged();
+				}
+			});
+			
+			final ArrayList<HashMap<String, ?>> flagsDataNew = FrontendDataUtils.getFlags(this);
+			runOnUiThread(new Runnable(){
+				public void run() {
+					flagsData.addAll(flagsDataNew);
+					((SimpleAdapter)flag.getAdapter()).notifyDataSetChanged();
+				}
+			});
+			
+			final ArrayList<HashMap<String, ?>> typesDataNew = FrontendDataUtils.getTypes(this);
+			runOnUiThread(new Runnable(){
+				public void run() {
+					typesData.addAll(typesDataNew);
+					((SimpleAdapter)difficulty.getAdapter()).notifyDataSetChanged();
+				}
+			});
+			
+			final ArrayList<HashMap<String, ?>> hatsDataNew = FrontendDataUtils.getHats(this);
+			runOnUiThread(new Runnable(){
+				public void run() {
+					hatsData.addAll(hatsDataNew);
+					((SimpleAdapter)hogHat.get(0).getAdapter()).notifyDataSetChanged();
+				}
+			});
+			
+			final ArrayList<String> voicesDataNew = FrontendDataUtils.getVoices(this);
+			runOnUiThread(new Runnable(){
+				public void run() {
+					voicesData.addAll(voicesDataNew);
+					((ArrayAdapter<?>)voice.getAdapter()).notifyDataSetChanged();
+				}
+			});
+			
+			final ArrayList<String> fortsDataNew = FrontendDataUtils.getForts(this);
+			runOnUiThread(new Runnable(){
+				public void run() {
+					fortsData.addAll(fortsDataNew);
+					((ArrayAdapter<?>)fort.getAdapter()).notifyDataSetChanged();
+				}
+			});
+			
+			if(existingTeamName!=null) {
+				final Team loadedTeam = Team.load(Team.getTeamfileByName(getApplicationContext(), existingTeamName));
+				if(loadedTeam==null) {
+					existingTeamName = null;
+				} else {
+					runOnUiThread(new Runnable(){
+						public void run() {
+							setTeamValues(loadedTeam);
+						}
+					});
+				}
+			}
+			runOnUiThread(new Runnable(){
+				public void run() {
+					initComplete = true;
+				}
+			});
+		} catch(FileNotFoundException e) {
+			this.runOnUiThread(new Runnable(){
+				public void run() {
+					Toast.makeText(getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+					finish();
+				}
+			});
+		}
 	}
-
+	
 	public void onDestroy() {
 		super.onDestroy();
 		if (mp != null) {
@@ -217,101 +240,68 @@
 		}
 	}
 
-	private OnFocusChangeListener focusser = new OnFocusChangeListener() {
-		public void onFocusChange(View v, boolean hasFocus) {
-			settingsChanged = true;
-		}
-
-	};
-
-	public void onBackPressed() {
-		onFinishing();
-		super.onBackPressed();
-
+	@Override
+	protected void onSaveInstanceState(Bundle outState) {
+		super.onSaveInstanceState(outState);
+		outState.putString(PARAMETER_EXISTING_TEAMNAME, existingTeamName);
 	}
 
-	private OnClickListener backClicker = new OnClickListener() {
-		public void onClick(View v) {
-			onFinishing();
-			finish();
+	public void onBackPressed() {
+		if(initComplete) {
+			saveTeam();
 		}
-	};
-
-	private void onFinishing() {
-		if (settingsChanged) {
-			setResult(RESULT_OK);
-		} else {
-			setResult(RESULT_CANCELED);
-		}
+		setResult(RESULT_OK);
+		super.onBackPressed();
 	}
 
-	private OnClickListener saveClicker = new OnClickListener() {
-		public void onClick(View v) {
-			Toast.makeText(TeamCreatorActivity.this, R.string.saved, Toast.LENGTH_SHORT).show();
-			saved = true;
-			Team team = new Team();
-			team.name = name.getText().toString();
-			HashMap<String, Object> hashmap = (HashMap<String, Object>) flag.getSelectedItem();
-
-			team.flag = (String) hashmap.get("txt");
-			team.fort = fort.getSelectedItem().toString();
-			hashmap = (HashMap<String, Object>) grave.getSelectedItem();
-			team.grave = hashmap.get("txt").toString();
-			team.hash = "0";
-			team.voice = voice.getSelectedItem().toString();
-			team.file = fileName;
-
-			hashmap = ((HashMap<String, Object>) difficulty.getSelectedItem());
-			String levelString = hashmap.get("txt").toString();
-			int levelInt;
-			if (levelString.equals(getString(R.string.human))) {
-				levelInt = 0;
-			} else if (levelString.equals(getString(R.string.bot5))) {
-				levelInt = 1;
-			} else if (levelString.equals(getString(R.string.bot4))) {
-				levelInt = 2;
-			} else if (levelString.equals(getString(R.string.bot3))) {
-				levelInt = 3;
-			} else if (levelString.equals(getString(R.string.bot2))) {
-				levelInt = 4;
-			} else {
-				levelInt = 5;
+	private void saveTeam() {
+		String teamName = name.getText().toString();
+		String teamFlag = (String)((Map<String, Object>) flag.getSelectedItem()).get("txt");
+		String teamFort = fort.getSelectedItem().toString();
+		String teamGrave = (String)((Map<String, Object>) grave.getSelectedItem()).get("txt");
+		String teamVoice = voice.getSelectedItem().toString();
+		int levelInt = (Integer)((Map<String, Object>) difficulty.getSelectedItem()).get("level");
+		
+		List<Hog> hogs = new ArrayList<Hog>();
+		for (int i = 0; i < hogName.size(); i++) {
+			String name = hogName.get(i).getText().toString();
+			String hat = ((Map<String, Object>) hogHat.get(i).getSelectedItem()).get("txt").toString();
+			hogs.add(new Hog(name, hat, levelInt));
+		}
+		
+		Team team = new Team(teamName, teamGrave, teamFlag, teamVoice, teamFort, hogs);
+		File teamsDir = new File(getFilesDir(), Team.DIRECTORY_TEAMS);
+		if (!teamsDir.exists()) teamsDir.mkdir();
+		
+		File newFile = Team.getTeamfileByName(this, teamName);
+		File oldFile = null;
+		if(existingTeamName != null) {
+			oldFile = Team.getTeamfileByName(this, existingTeamName);
+		}
+		try {
+			team.save(newFile);
+			// If the team was renamed, delete the old file.
+			if(oldFile != null && oldFile.isFile() && !oldFile.equals(newFile)) {
+				oldFile.delete();
 			}
-
-			for (int i = 0; i < hogName.size(); i++) {
-				team.hogNames[i] = hogName.get(i).getText().toString();
-				hashmap = (HashMap<String, Object>) hogHat.get(i).getSelectedItem();
-				team.hats[i] = hashmap.get("txt").toString();
-				team.levels[i] = levelInt;
-			}
-			try {
-				File teamsDir = new File(getFilesDir().getAbsolutePath() + '/' + Team.DIRECTORY_TEAMS);
-				if (!teamsDir.exists()) teamsDir.mkdir();
-				if(team.file == null){
-					team.setFileName(TeamCreatorActivity.this);
-				}
-				FileOutputStream fos = new FileOutputStream(String.format("%s/%s", teamsDir.getAbsolutePath(), team.file));
-				team.writeToXml(fos);
-			} catch (FileNotFoundException e) {
-				e.printStackTrace();
-			}
+			existingTeamName = teamName;
+		} catch(IOException e) {
+			Toast.makeText(getApplicationContext(), R.string.error_save_failed, Toast.LENGTH_SHORT).show();
 		}
-
 	};
 
 	private OnItemSelectedListener fortSelector = new OnItemSelectedListener() {
 		public void onItemSelected(AdapterView<?> arg0, View arg1,
 				int position, long arg3) {
-			settingsChanged = true;
 			String fortName = (String) arg0.getAdapter().getItem(position);
-			Drawable fortIconDrawable = Drawable.createFromPath(Utils
+			Drawable fortIconDrawable = Drawable.createFromPath(FileUtils
 					.getDataPath(TeamCreatorActivity.this)
 					+ "Forts/"
 					+ fortName + "L.png");
 			imgFort.setImageDrawable(fortIconDrawable);
 			scroller.fullScroll(ScrollView.FOCUS_DOWN);// Scroll the scrollview
 			// to the bottom, work
-			// around for scollview
+			// around for scrollview
 			// invalidation (scrolls
 			// back to top)
 		}
@@ -325,7 +315,7 @@
 		public void onClick(View v) {
 			try {
 				File dir = new File(String.format("%sSounds/voices/%s",
-						Utils.getDataPath(TeamCreatorActivity.this),
+						FileUtils.getDataPath(TeamCreatorActivity.this),
 						voice.getSelectedItem()));
 				String file = "";
 				File[] dirs = dir.listFiles();
@@ -350,54 +340,48 @@
 		}
 	};
 
+	@SuppressWarnings("unchecked")
 	private void setTeamValues(Team t){
-
-		if (t != null) {
+		if (t == null) {
+			return;
+		}
+		
+		try {
 			name.setText(t.name);
-			int position = ((ArrayAdapter<String>) voice.getAdapter()).getPosition(t.voice);
-			voice.setSelection(position);
-
-			position = ((ArrayAdapter<String>) fort.getAdapter()).getPosition(t.fort);
-			fort.setSelection(position);
-
-			position = 0;
-			for (HashMap<String, ?> hashmap : typesData) {
-				if (hashmap.get("txt").equals(t.levels[0])) {
-					difficulty.setSelection(position);
-					break;
-				}
+			voice.setSelection(findPosition((ArrayAdapter<String>) voice.getAdapter(), t.voice));
+			fort.setSelection(findPosition((ArrayAdapter<String>) fort.getAdapter(), t.fort));
+			difficulty.setSelection(findPosition(typesData, "level", Integer.valueOf(t.hogs.get(0).level)));
+			grave.setSelection(findPosition(gravesData, "txt", t.grave));
+			flag.setSelection(findPosition(flagsData, "txt", t.flag));
+	
+			for (int i = 0; i < Team.HEDGEHOGS_PER_TEAM; i++) {
+				hogHat.get(i).setSelection(findPosition(hatsData, "txt", t.hogs.get(i).hat));
+				hogName.get(i).setText(t.hogs.get(i).name);
 			}
-
-			position = 0;
-			for (HashMap<String, ?> hashmap : gravesData) {
-				if (hashmap.get("txt").equals(t.grave)) {
-					grave.setSelection(position);
-					break;
-				}
-			}
-
-			position = 0;
-			for (HashMap<String, ?> hashmap : typesData) {
-				if (hashmap.get("txt").equals(t.flag)) {
-					flag.setSelection(position);
-					break;
-				}
-			}
-
-			for (int i = 0; i < Team.maxNumberOfHogs; i++) {
-				position = 0;
-				for (HashMap<String, ?> hashmap : hatsData) {
-					if (hashmap.get("txt").equals(t.hats[i])) {
-						hogHat.get(i).setSelection(position);
-					}
-				}
-
-				hogName.get(i).setText(t.hogNames[i]);
-			}
-			this.fileName = t.file;
+		} catch(NoSuchElementException e) {
+			Toast.makeText(getApplicationContext(), R.string.error_team_attribute_not_found, Toast.LENGTH_LONG).show();
+			finish();
 		}
 	}
 
+	int findPosition(ArrayAdapter<String> adapter, String value) throws NoSuchElementException {
+		int position = adapter.getPosition(value);
+		if(position<0) {
+			throw new NoSuchElementException();
+		}
+		return position;
+	}
+	
+	int findPosition(List<? extends Map<String, ?>> data, String key, Object value) throws NoSuchElementException {
+		int position = 0;
+		for (Map<String, ?> map : data) {
+			if (map.get(key).equals(value)) {
+				return position;
+			}
+			position++;
+		}
+		throw new NoSuchElementException();
+	}
 
 	private SimpleAdapter.ViewBinder viewBinder = new SimpleAdapter.ViewBinder() {
 
@@ -412,5 +396,4 @@
 			}
 		}
 	};
-
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamSelectionActivity.java	Fri Aug 17 10:39:23 2012 -0400
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamSelectionActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -19,18 +19,18 @@
 
 package org.hedgewars.hedgeroid;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
 import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
 import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
-import android.os.Parcelable;
 import android.view.ContextMenu;
 import android.view.MenuItem;
 import android.view.View;
@@ -41,16 +41,17 @@
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.ListView;
-import android.widget.RelativeLayout;
 import android.widget.SimpleAdapter;
 import android.widget.SimpleAdapter.ViewBinder;
 import android.widget.TextView;
 
 public class TeamSelectionActivity extends Activity implements Runnable{
+	private static final int ACTIVITY_TEAMCREATION = 0;
+	
+	public static volatile List<TeamInGame> activityParams;
+	public static volatile List<TeamInGame> activityReturn;
 
-	private static final int ACTIVITY_TEAMCREATION = 0;
-
-	private ImageButton addTeam, back;
+	private ImageButton addTeam;
 	private ListView availableTeams, selectedTeams;
 	private List<HashMap<String, Object>> availableTeamsList, selectedTeamsList;
 	private TextView txtInfo;
@@ -61,12 +62,10 @@
 		setContentView(R.layout.team_selector);
 
 		addTeam = (ImageButton) findViewById(R.id.btnAdd);
-		back = (ImageButton) findViewById(R.id.btnBack);
 		txtInfo = (TextView) findViewById(R.id.txtInfo);
 		selectedTeams = (ListView) findViewById(R.id.selectedTeams);
 		availableTeams = (ListView) findViewById(R.id.availableTeams);
 		addTeam.setOnClickListener(addTeamClicker);
-		back.setOnClickListener(backClicker);
 
 		availableTeamsList = new ArrayList<HashMap<String, Object>>();
 		SimpleAdapter adapter = new SimpleAdapter(this, availableTeamsList, R.layout.team_selection_entry_simple, new String[]{"txt", "img"}, new int[]{R.id.txtName, R.id.imgDifficulty});
@@ -86,23 +85,33 @@
 	}
 
 	public void run(){
-		List<HashMap<String, Object>> teamsList = FrontendDataUtils.getTeams(this);//teams from xml
-		ArrayList<Team> teamsStartGame = getIntent().getParcelableArrayListExtra("teams");//possible selected teams
-
-		for(HashMap<String, Object> hashmap : teamsList){
+		List<Team> teams = FrontendDataUtils.getTeams(this);
+		List<TeamInGame> existingTeams = activityParams;
+		final List<TeamInGame> newSelectedList = new ArrayList<TeamInGame>();
+		final List<Team> newAvailableList = new ArrayList<Team>();
+		
+		for(Team team : teams){
 			boolean added = false;
-			for(Team t : teamsStartGame){
-				if(((Team)hashmap.get("team")).equals(t)){//add to available or add to selected
-					selectedTeamsList.add(FrontendDataUtils.teamToMap(t));//create a new hashmap to ensure all variables are entered into the map
+			for(TeamInGame existingTeam : existingTeams){
+				if(team.name.equals(existingTeam.team.name)){ // add to available or add to selected
+					newSelectedList.add(new TeamInGame(team, existingTeam.ingameAttribs));
 					added = true;
 					break;
 				}
 			}
-			if(!added) availableTeamsList.add(hashmap);
+			if(!added) newAvailableList.add(team);
 		}
 
 		this.runOnUiThread(new Runnable(){
 			public void run() {
+				availableTeamsList.clear();
+				selectedTeamsList.clear();
+				for(TeamInGame t : newSelectedList) {
+					selectedTeamsList.add(toMap(t));
+				}
+				for(Team t : newAvailableList) {
+					availableTeamsList.add(toMap(t));
+				}
 				((SimpleAdapter)selectedTeams.getAdapter()).notifyDataSetChanged();
 				((SimpleAdapter)availableTeams.getAdapter()).notifyDataSetChanged();		
 			}
@@ -116,7 +125,7 @@
 				setTeamColor(view, (Integer)data);
 				return true;
 			case R.id.teamCount:
-				setTeamHogCount((ImageView)view, (Integer)data);
+				((ImageView)view).getDrawable().setLevel((Integer)data);
 				return true;
 			default:
 				return false;
@@ -138,75 +147,36 @@
 	 * Updates the list view when TeamCreationActivity is shutdown and the user returns to this point
 	 */
 	private void updateListViews(){
-		unregisterForContextMenu(availableTeams);
-		availableTeamsList = FrontendDataUtils.getTeams(this);
+		List<Team> teams = FrontendDataUtils.getTeams(this);
+		availableTeamsList.clear();
+		for(Team team : teams) {
+			availableTeamsList.add(toMap(team));
+		}
+		
 		ArrayList<HashMap<String, Object>> toBeRemoved = new ArrayList<HashMap<String, Object>>();
+		ArrayList<HashMap<String, Object>> toBeRemovedFromSelected = new ArrayList<HashMap<String, Object>>();
 		for(HashMap<String, Object> hashmap : selectedTeamsList){
 			String name = (String)hashmap.get("txt");
-
+			boolean exists = false;
 			for(HashMap<String, Object> hash : availableTeamsList){
 				if(name.equals((String)hash.get("txt"))){
 					toBeRemoved.add(hash);
+					exists = true;
+					break;
 				}
 			}
+			if(!exists) {
+				toBeRemovedFromSelected.add(hashmap);
+			}
 		}
 		for(HashMap<String, Object> hash: toBeRemoved) availableTeamsList.remove(hash);
-
-		SimpleAdapter adapter = new SimpleAdapter(this, availableTeamsList, R.layout.team_selection_entry, new String[]{"txt", "img"}, new int[]{R.id.txtName, R.id.imgDifficulty});
-		availableTeams.setAdapter(adapter);
-		registerForContextMenu(availableTeams);
-		availableTeams.setOnItemClickListener(availableClicker);
-
-
-	}
-
-	private void setTeamColor(int position, int color){
-		View iv = ((RelativeLayout)selectedTeams.getChildAt(position)).findViewById(R.id.teamCount);
-		setTeamColor(iv, color);
-	}
-	private void setTeamColor(View iv, int color){
-		iv.setBackgroundColor(0xFF000000 + color);
-	}
-
-	private void setTeamHogCount(int position, int count){
-		ImageView iv = (ImageView)((RelativeLayout)selectedTeams.getChildAt(position)).findViewById(R.id.teamCount);
-		setTeamHogCount(iv, count);
+		for(HashMap<String, Object> hash: toBeRemovedFromSelected) selectedTeamsList.remove(hash);
+		((SimpleAdapter)selectedTeams.getAdapter()).notifyDataSetChanged();
+		((SimpleAdapter)availableTeams.getAdapter()).notifyDataSetChanged();
 	}
 
-	private void setTeamHogCount(ImageView iv, int count){
-
-		switch(count){
-		case 0:
-			iv.setImageResource(R.drawable.teamcount0);
-			break;
-		case 1:
-			iv.setImageResource(R.drawable.teamcount1);
-			break;
-		case 2:
-			iv.setImageResource(R.drawable.teamcount2);
-			break;
-		case 3:
-			iv.setImageResource(R.drawable.teamcount3);
-			break;
-		case 4:
-			iv.setImageResource(R.drawable.teamcount4);
-			break;
-		case 5:
-			iv.setImageResource(R.drawable.teamcount5);
-			break;
-		case 6:
-			iv.setImageResource(R.drawable.teamcount6);
-			break;
-		case 7:
-			iv.setImageResource(R.drawable.teamcount7);
-			break;
-		case 8:
-			iv.setImageResource(R.drawable.teamcount8);
-			break;
-		case 9:
-			iv.setImageResource(R.drawable.teamcount9);
-			break;
-		}
+	private void setTeamColor(View iv, int colorIndex){
+		iv.setBackgroundColor(0xFF000000 + TeamIngameAttributes.TEAM_COLORS[colorIndex]);
 	}
 
 	public void onBackPressed(){
@@ -220,13 +190,6 @@
 		}
 	};
 
-	private OnClickListener backClicker = new OnClickListener(){
-		public void onClick(View v){
-			returnTeams();
-			finish();
-		}
-	};
-
 	private OnItemClickListener availableClicker = new OnItemClickListener(){
 		public void onItemClick(AdapterView<?> arg0, View arg1, int position,long arg3) {
 			selectAvailableTeamsItem(position);
@@ -253,21 +216,19 @@
 	public boolean onContextItemSelected(MenuItem item){
 		AdapterView.AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
 		int position = menuInfo.position;
+		Team team = (Team)availableTeamsList.get(position).get("team");
 		switch(item.getItemId()){
 		case 0://select
 			selectAvailableTeamsItem(position);
 			return true;
 		case 1://delete
-			Team team = (Team)availableTeamsList.get(position).get("team");
-			File f = new File(String.format("%s/%s/%s", TeamSelectionActivity.this.getFilesDir(), Team.DIRECTORY_TEAMS, team.file));
-			f.delete();
+			Team.getTeamfileByName(getApplicationContext(), team.name).delete();
 			availableTeamsList.remove(position);
 			((SimpleAdapter)availableTeams.getAdapter()).notifyDataSetChanged();
 			return true;
 		case 2://edit
 			Intent i = new Intent(TeamSelectionActivity.this, TeamCreatorActivity.class);
-			Team t = (Team)availableTeamsList.get(position).get("team");
-			i.putExtra("team", t);
+			i.putExtra(TeamCreatorActivity.PARAMETER_EXISTING_TEAMNAME, team.name);
 			startActivityForResult(i, ACTIVITY_TEAMCREATION);
 			return true;
 		}
@@ -276,14 +237,12 @@
 
 	private void selectAvailableTeamsItem(int position){
 		HashMap<String, Object> hash = (HashMap<String, Object>) availableTeamsList.get(position);
-		Team t = (Team)hash.get("team");
 		int[] illegalcolors = new int[selectedTeamsList.size()];
 		for(int i = 0; i < selectedTeamsList.size(); i++){
-			illegalcolors[i] = ((Team)selectedTeamsList.get(i).get("team")).color;
+			illegalcolors[i] = (Integer)selectedTeamsList.get(i).get("color");
 		}
-		t.setRandomColor(illegalcolors);
-		hash.put("color", t.color);
-		hash.put("count", t.hogCount);
+		hash.put("color", TeamIngameAttributes.randomColorIndex(illegalcolors));
+		hash.put("count", TeamIngameAttributes.DEFAULT_HOG_COUNT);
 
 		selectedTeamsList.add(hash);
 		availableTeamsList.remove(position);
@@ -293,15 +252,36 @@
 		txtInfo.setText(String.format(getResources().getString(R.string.teams_info_template), selectedTeamsList.size()));
 	}
 
-	private void returnTeams(){
-		int teamsCount = selectedTeamsList.size();
-		Intent i = new Intent();
-		Parcelable[] teams = new Parcelable[teamsCount];
-		for(int x = 0 ; x < teamsCount; x++){
-			teams[x] = (Team)selectedTeamsList.get(x).get("team");
+	private void returnTeams() {
+		List<TeamInGame> result = new ArrayList<TeamInGame>();
+		for(HashMap<String, Object> item : selectedTeamsList) {
+			result.add(new TeamInGame((Team)item.get("team"), new TeamIngameAttributes("Player", (Integer)item.get("color"), (Integer)item.get("count"), false)));
 		}
-		i.putExtra("teams", teams);
-		setResult(Activity.RESULT_OK, i);
-
+		activityReturn = result;
+		setResult(Activity.RESULT_OK);
+	}
+	
+	private static final int[] botlevelDrawables = new int[] {
+		R.drawable.human, R.drawable.bot5, R.drawable.bot4, R.drawable.bot3, R.drawable.bot2, R.drawable.bot1
+	};
+		
+	private static HashMap<String, Object> toMap(Team t) {
+		HashMap<String, Object> map = new HashMap<String, Object>();
+		map.put("team", t);
+		map.put("txt", t.name);
+		int botlevel = t.hogs.get(0).level;
+		if(botlevel<0 || botlevel>=botlevelDrawables.length) {
+			map.put("img", R.drawable.bot1);
+		} else {
+			map.put("img", botlevelDrawables[botlevel]);
+		}	
+		return map;
+	}
+	
+	private static HashMap<String, Object> toMap(TeamInGame t) {
+		HashMap<String, Object> map = toMap(t.team);
+		map.put("color", t.ingameAttribs.colorIndex);
+		map.put("count", t.ingameAttribs.hogCount);
+		return map;
 	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamlistAdapter.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,112 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+public class TeamlistAdapter extends ObservableTreeMapAdapter<String, TeamInGame> {
+	private boolean colorHogcountEnabled = false;
+	private Listener listener;
+	
+	@Override
+	protected Comparator<TeamInGame> getEntryOrder() {
+		return TeamInGame.NAME_ORDER;
+	}
+
+	public void setColorHogcountEnabled(boolean colorHogcountEnabled) {
+		this.colorHogcountEnabled = colorHogcountEnabled;
+		notifyDataSetChanged();
+	}
+	
+	public void setListener(Listener listener) {
+		this.listener = listener;
+	}
+	
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(parent.getContext());
+			v = vi.inflate(R.layout.listview_team, null);
+		}
+
+		TeamInGame team = getItem(position);
+		TextView teamNameView = (TextView) v.findViewById(android.R.id.text1);
+		ImageButton colorButton = (ImageButton) v.findViewById(R.id.colorButton);
+		ImageButton hogCountButton = (ImageButton) v.findViewById(R.id.hogCountButton);
+		
+		teamNameView.setText(team.team.name);
+		int teamImage;
+		if(team.ingameAttribs.remoteDriven) {
+			teamImage = R.drawable.team_net_by_level;
+		} else {
+			teamImage = R.drawable.team_local_by_level;
+		}
+		
+		Drawable d = parent.getContext().getResources().getDrawable(teamImage).mutate();
+		d.setLevel(team.team.hogs.get(0).level);
+		teamNameView.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
+		hogCountButton.getDrawable().setLevel(team.ingameAttribs.hogCount);
+		colorButton.setImageDrawable(new ColorDrawable(TeamIngameAttributes.TEAM_COLORS[team.ingameAttribs.colorIndex]));
+		
+		colorButton.setEnabled(colorHogcountEnabled);
+		hogCountButton.setEnabled(colorHogcountEnabled);
+		
+		colorButton.setOnClickListener(new ButtonClickListener(team, Type.COLOR_BUTTON));
+		hogCountButton.setOnClickListener(new ButtonClickListener(team, Type.HOGCOUNT_BUTTON));
+		
+		if(team.ingameAttribs.remoteDriven) {
+			teamNameView.setClickable(false);
+		} else {
+			teamNameView.setOnClickListener(new ButtonClickListener(team, Type.TEAM_VIEW));
+		}
+		
+		return v;
+	}
+	
+	private static enum Type {COLOR_BUTTON, HOGCOUNT_BUTTON, TEAM_VIEW}
+	private final class ButtonClickListener implements OnClickListener {
+		private final TeamInGame team;
+		private final Type type;
+		
+		public ButtonClickListener(TeamInGame team, Type type) {
+			this.team = team;
+			this.type = type;
+		}
+		
+		public void onClick(View v) {
+			if(listener != null) {
+				switch(type) {
+				case COLOR_BUTTON:
+					listener.onColorClicked(team);
+					break;
+				case HOGCOUNT_BUTTON:
+					listener.onHogcountClicked(team);
+					break;
+				case TEAM_VIEW:
+					listener.onTeamClicked(team);
+					break;
+				default:
+					throw new IllegalStateException();	
+				}
+			}
+		}
+	}
+	
+	public interface Listener {
+		void onTeamClicked(TeamInGame team);
+		void onColorClicked(TeamInGame team);
+		void onHogcountClicked(TeamInGame team);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamlistFragment.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,122 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.database.DataSetObserver;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+/**
+ *  TODO use an interface for querying and manipulating the team list, to allow re-using this fragment
+ *  in local play
+ */
+public class TeamlistFragment extends ListFragment implements TeamlistAdapter.Listener, RoomStateManager.Observer {
+	private Netplay netplay;
+	private TeamlistAdapter adapter;
+	private Button addTeamButton;
+	private DataSetObserver teamlistObserver;
+	private RoomStateManager stateManager;
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		try {
+			stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager();
+		} catch(ClassCastException e) {
+			throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e);
+		}
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new TeamlistAdapter();
+		adapter.setSource(netplay.roomTeamlist);
+		adapter.setColorHogcountEnabled(stateManager.getChiefStatus());
+		adapter.setListener(this);
+		setListAdapter(adapter);
+		stateManager.registerObserver(this);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View v = inflater.inflate(R.layout.fragment_teamlist, container, false);
+		addTeamButton = (Button)v.findViewById(R.id.addTeamButton);
+		addTeamButton.setOnClickListener(new OnClickListener() {
+			public void onClick(View v) {
+				new TeamAddDialog(getCurrentTeamNames()).show(getFragmentManager(), "team_add_dialog");
+			}
+		});
+		
+		teamlistObserver = new DataSetObserver() {
+			@Override
+			public void onChanged() {
+				addTeamButton.setEnabled(netplay.roomTeamlist.getMap().size() < Team.maxNumberOfTeams);
+			}
+		};
+		netplay.roomTeamlist.registerObserver(teamlistObserver);
+		teamlistObserver.onChanged();
+		
+		return v;
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+		adapter.setListener(null);
+		netplay.roomTeamlist.unregisterObserver(teamlistObserver);
+		stateManager.unregisterObserver(this);
+	}
+
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+	}
+	
+	private Collection<String> getCurrentTeamNames() {
+		List<String> names = new ArrayList<String>();
+		for(TeamInGame team : netplay.roomTeamlist.getMap().values()) {
+			names.add(team.team.name);
+		}
+		return names;
+	}
+	
+	public void onColorClicked(TeamInGame team) {
+		netplay.sendTeamColorIndex(team.team.name, (team.ingameAttribs.colorIndex+1)%TeamIngameAttributes.TEAM_COLORS.length);
+	}
+	
+	public void onHogcountClicked(TeamInGame team) {
+		int newHogCount = team.ingameAttribs.hogCount+1;
+		if(newHogCount>Team.HEDGEHOGS_PER_TEAM) {
+			newHogCount = 1;
+		}
+		netplay.sendTeamHogCount(team.team.name, newHogCount);
+	}
+	
+	public void onTeamClicked(TeamInGame team) {
+		netplay.sendRemoveTeam(team.team.name);
+	}
+	
+	public void onChiefStatusChanged(boolean isChief) {
+		adapter.setColorHogcountEnabled(isChief);
+	}
+	
+	public void onGameStyleChanged(String gameStyle) { }
+	public void onMapChanged(MapRecipe recipe) { }
+	public void onSchemeChanged(Scheme scheme) { }
+	public void onWeaponsetChanged(Weaponset weaponset) { }
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java	Fri Aug 17 10:39:23 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,221 +0,0 @@
-/*
- * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
- * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
-
-package org.hedgewars.hedgeroid;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.os.Build;
-import android.os.Environment;
-import android.util.Log;
-
-public class Utils {
-
-	private static final String ROOT_DIR = "Data/";
-
-	/**
-	 * get the path to which we should download all the data files
-	 * @param c context 
-	 * @return absolute path
-	 */
-	public static String getCachePath(Context c){
-		if(Build.VERSION.SDK_INT < 8){//8 == Build.VERSION_CODES.FROYO
-			return PreFroyoSDCardDir.getDownloadPath(c) + '/';
-		}else{
-			return FroyoSDCardDir.getDownloadPath(c) + '/';
-		}
-	}
-
-	public static String getDataPath(Context c){
-		return getCachePath(c) + ROOT_DIR;
-	}
-
-	static class FroyoSDCardDir{
-		public static String getDownloadPath(Context c){
-			File f =  c.getExternalCacheDir();
-			if(f != null){
-				return f.getAbsolutePath();
-			}else{
-				return null;
-			}	
-		}
-	}
-
-	static class PreFroyoSDCardDir{
-		public static String getDownloadPath(Context c){
-			if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
-				if(Environment.getExternalStorageDirectory() != null)
-					return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Hedgewars/";				
-			}
-			return null;
-		}
-	}
-
-	/**
-	 * Get files from dirName, dir name is relative to {@link getDownloadPath}
-	 * @param dirName
-	 * @param c context
-	 * @return string of files
-	 */
-	public static String[] getFileNamesFromRelativeDir(Context c, String dirName){
-		String prefix = getDataPath(c);
-		File f = new File(prefix + dirName);
-
-		if(f.exists() && f.isDirectory()) return f.list();
-		else{
-
-			Log.e("Utils::", "Couldn't find dir: " + dirName);
-			return new String[0];
-		}
-	}
-
-	/**
-	 * Return a File array with all the files from dirName
-	 * @param c
-	 * @param dirName
-	 * @return
-	 */
-	public static File[] getFilesFromRelativeDir(Context c, String dirName){
-		String prefix = getDataPath(c);
-		File f = new File(prefix + dirName);
-
-		if(f.exists() && f.isDirectory()) return f.listFiles();
-		else {
-			Log.e("Utils::", "Dir not found: " + dirName);
-			return new File[0];
-		}
-	}
-
-	/**
-	 * Checks if this directory has a file with suffix suffix
-	 * @param f - directory
-	 * @return
-	 */
-	public static boolean hasFileWithSuffix(File f, String suffix){
-		if(f.isDirectory()){
-			for(String s : f.list()){
-				if(s.endsWith(suffix)) return true;
-			}
-			return false;
-		}else{
-			return false;
-		}
-	}
-
-	/**
-	 * Gives back all dirs which contain a file with suffix fileSuffix
-	 * @param c
-	 * @param path
-	 * @param fileSuffix
-	 * @return
-	 */
-	public static List<String> getDirsWithFileSuffix(Context c, String path, String fileSuffix){
-		File[] files = getFilesFromRelativeDir(c,path);
-		ArrayList<String> ret = new ArrayList<String>();
-
-		for(File f : files){
-			if(hasFileWithSuffix(f, fileSuffix)) ret.add(f.getName());
-		}
-		return ret;
-	}
-
-	/**
-	 * Get all files from directory dir which have the given suffix
-	 * @param c
-	 * @param dir
-	 * @param suffix
-	 * @param removeSuffix
-	 * @return
-	 */
-	public static ArrayList<String> getFilesFromDirWithSuffix(Context c, String dir, String suffix, boolean removeSuffix){
-		String[] files = Utils.getFileNamesFromRelativeDir(c, dir);
-		ArrayList<String> ret = new ArrayList<String>();
-		for(String s : files){
-			if(s.endsWith(suffix)){
-				if(removeSuffix) ret.add(s.substring(0, s.length()-suffix.length()));
-				else ret.add(s);
-			}
-		}
-		return ret;
-	}
-
-	/**
-	 * Moves resources pointed to by sourceResId (from @res/raw/) to the app's private data directory
-	 * @param c
-	 * @param sourceResId
-	 * @param directory
-	 */
-	public static void resRawToFilesDir(Context c, int sourceResId, String directory){
-		byte[] buffer = new byte[1024];
-		InputStream bis = null;
-		BufferedOutputStream bos = null;
-		File schemesDirFile = new File(c.getFilesDir().getAbsolutePath() + '/' + directory);
-		schemesDirFile.mkdirs();
-		String schemesDirPath = schemesDirFile.getAbsolutePath() + '/';
-
-		//Get an array with the resource files ID
-		TypedArray ta = c.getResources().obtainTypedArray(sourceResId);
-		int[] resIds = new int[ta.length()];
-		for(int i = 0; i < ta.length(); i++){
-			resIds[i] = ta.getResourceId(i, 0);
-		}
-
-		for(int id : resIds){
-			String fileName = c.getResources().getResourceEntryName(id);
-			File f = new File(schemesDirPath + fileName);
-			try {
-				if(!f.createNewFile()){
-					f.delete();
-					f.createNewFile();
-				}
-
-				bis = c.getResources().openRawResource(id);
-				bos = new BufferedOutputStream(new FileOutputStream(f), 1024);
-				int read = 0;
-				while((read = bis.read(buffer)) != -1){
-					bos.write(buffer, 0, read);
-				}
-
-			} catch (IOException e) {
-				e.printStackTrace();
-			}finally{
-				if(bis != null)
-					try { 
-						bis.close();
-					} catch (IOException e) {
-						e.printStackTrace();
-					}
-					if(bos != null)
-						try {
-							bos.close();
-						} catch (IOException e) {
-							e.printStackTrace();
-						}
-			}
-		}
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/WeaponsetCreatorActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,8 @@
+package org.hedgewars.hedgeroid;
+
+import android.support.v4.app.FragmentActivity;
+
+// TODO
+public class WeaponsetCreatorActivity extends FragmentActivity {
+	public static final String PARAMETER_EXISTING_WEAPONSETNAME="existingWeaponsetName";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/WeaponsetListActivity.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,107 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.SimpleAdapter;
+import android.widget.Toast;
+
+public class WeaponsetListActivity extends ListActivity implements OnItemClickListener {
+	private List<Weaponset> userWeaponsets;
+	private Button addButton;
+	
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_weaponsetlist);
+		addButton = (Button)findViewById(R.id.addButton);
+		addButton.setOnClickListener(new OnClickListener() {
+			public void onClick(View v) {
+				editWeaponset(null);
+			}
+		});
+	}
+	
+	@Override
+	public void onResume() {
+		super.onResume();
+		updateList();
+		getListView().setOnItemClickListener(this);
+		registerForContextMenu(getListView());
+	}
+	
+	private List<Map<String, ?>> weaponsetsToMap(List<Weaponset> weaponsets) {
+		List<Map<String, ?>> result = new ArrayList<Map<String, ?>>();
+		for(Weaponset weaponset : weaponsets) {
+			result.add(Collections.singletonMap("txt", weaponset.name));
+		}
+		return result;
+	}
+	
+	public void onItemClick(AdapterView<?> adapterView, View v, int position, long arg3) {
+		editWeaponset(userWeaponsets.get(position).name);
+	}
+	
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuinfo){
+		menu.add(ContextMenu.NONE, 0, ContextMenu.NONE, R.string.edit);
+		menu.add(ContextMenu.NONE, 1, ContextMenu.NONE, R.string.delete);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item){
+		AdapterView.AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
+		int position = menuInfo.position;
+		Weaponset weaponset = userWeaponsets.get(position);
+		switch(item.getItemId()){
+		case 0:
+			editWeaponset(weaponset.name);
+			return true;
+		case 1:
+			try {
+				Weaponsets.deleteUserWeaponset(this, weaponset.name);
+			} catch (IOException e) {
+				Toast.makeText(this.getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_SHORT).show();
+			}
+			updateList();
+			return true;
+		}
+		return false;
+	}
+	
+	private void updateList() {
+		try {
+			userWeaponsets = Weaponsets.loadUserWeaponsets(this);
+		} catch (IOException e) {
+			Toast.makeText(this, R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			finish();
+		}
+		Collections.sort(userWeaponsets, Weaponset.NAME_ORDER);
+		ListAdapter adapter = new SimpleAdapter(this, weaponsetsToMap(userWeaponsets), android.R.layout.simple_list_item_1, new String[]{"txt"}, new int[]{android.R.id.text1});
+		setListAdapter(adapter);
+	}
+	
+	private void editWeaponset(String weaponsetName) {
+		Intent i = new Intent(this, WeaponsetCreatorActivity.class);
+		i.putExtra(WeaponsetCreatorActivity.PARAMETER_EXISTING_WEAPONSETNAME, weaponsetName);
+		startActivity(i);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/frontlib/Flib.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,35 @@
+package org.hedgewars.hedgeroid.frontlib;
+
+import java.util.Collections;
+
+import android.util.Log;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+
+public class Flib {
+	static {
+		System.loadLibrary("SDL_net");
+		System.setProperty("jna.encoding", "UTF8"); // Ugly global setting, but it seems JNA doesn't allow setting this per-library... 
+	}
+	public static final Frontlib INSTANCE = (Frontlib)Native.loadLibrary("frontlib", Frontlib.class, Collections.singletonMap(Library.OPTION_TYPE_MAPPER, FrontlibTypeMapper.INSTANCE));
+	
+	// Hook frontlib logging into Android logging
+	private static final Frontlib.LogCallback logCb = new Frontlib.LogCallback() {
+		public void callback(int level, String message) {
+			if(level >= Frontlib.FLIB_LOGLEVEL_ERROR) {
+				Log.e("Frontlib", message);
+			} else if(level == Frontlib.FLIB_LOGLEVEL_WARNING){
+				Log.w("Frontlib", message);
+			} else if(level == Frontlib.FLIB_LOGLEVEL_INFO){
+				Log.i("Frontlib", message);
+			} else if(level <= Frontlib.FLIB_LOGLEVEL_DEBUG){
+				Log.d("Frontlib", message);
+			}
+		}
+	};
+	static {
+		INSTANCE.flib_log_setLevel(Frontlib.FLIB_LOGLEVEL_ALL);
+		INSTANCE.flib_log_setCallback(logCb);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/frontlib/Frontlib.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,1169 @@
+package org.hedgewars.hedgeroid.frontlib;
+import java.io.UnsupportedEncodingException;
+import java.nio.Buffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.hedgewars.hedgeroid.Datastructures.Hog;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.MetaScheme;
+import org.hedgewars.hedgeroid.Datastructures.MetaScheme.Mod;
+import org.hedgewars.hedgeroid.Datastructures.MetaScheme.Setting;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+
+import com.sun.jna.Callback;
+import com.sun.jna.Library;
+import com.sun.jna.Memory;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+import com.sun.jna.PointerType;
+import com.sun.jna.Structure;
+
+/**
+ * Here is an introduction to the most important aspects of the JNA code.
+ * 
+ * This interface permits access to the Hedgewars frontend library (frontlib)
+ * from Java. Each function directly contained in the Frontlib interface
+ * represents a mapped C function. The Structure classes (ending in -Struct) are
+ * mappings of C structs, and the PointerType classes (ending in -Ptr) represent
+ * pointers to structs.
+ * 
+ * Quick notes for USING these classes from outside this package:
+ * 
+ * Usage should be fairly straightforward, but there are a few surprising
+ * gotchas. First, when you implement callbacks, YOU are responsible for
+ * ensuring that the callback objects are not garbage-collected while they might
+ * still be called! So make sure you keep them in member variables or similar,
+ * because Java will not know if there are still native references to them.
+ * 
+ * When using Frontlib from outside its package, you only interact with structs
+ * via the PointerType classes. They allow you to get at the data of the struct
+ * with a function called deref(), which creates a plain normal Java object
+ * representing the data (e.g. SchemePtr.deref() will give you a Scheme object).
+ * 
+ * Remember that you usually have to destroy structs that you receive from the
+ * library, because they are owned by the native code, not Java. The recommended
+ * pattern for most cases is to call deref() on the pointer to get a Java object
+ * (that you can keep as long as you like), and then immediately destroy the
+ * struct if it needs destroying. To find out whether and how the struct needs
+ * to be destroyed, see the library's documentation of the function that you got
+ * the struct from.
+ * 
+ * To pass new structs to the library, you can use the static createJavaOwned()
+ * function in each PointerType, which creates a new struct from the Java object
+ * you provide, and returns a pointer to that struct that you can pass to
+ * library functions. This new structure's memory is owned and managed by Java
+ * code, so do not destroy it with frontlib functions!
+ * 
+ * There is a slight mismatch between the data model for the game setup. The
+ * frontlib supports setting initial health and weaponset per-hog, because the
+ * engine allows for that, but currently neither the networking protocol nor the
+ * PC frontend support this feature, so the Android version does not take
+ * advantage of it either and treats both as per-game settings. The initial
+ * health is contained in the game scheme, the weaponset is explicitly part of
+ * the GameConfig. When converting GameConfig to a native flib_gamesetup, both
+ * are automatically copied to all hogs in the game, and for the reverse
+ * conversion the weaponset of the first hog of the first team is used as the
+ * GameConfig weaponset. This means that GameConfig.weaponset will be null if
+ * there are no teams in the game.
+ * 
+ * When starting a network game, you only need to query the GameSetupPtr from
+ * the netconn and use it to create the gameconn - this is preferable to using
+ * your own recreation of the game setup, because that way the same piece of
+ * code is used to determine the game setup on all platforms.
+ * 
+ * The "context" parameter of the callbacks is never needed here because JNA
+ * generates function code for each callback object. Don't worry about it, just
+ * pass null for context and ignore the context parameter in the callbacks.
+ * 
+ * Finally, the library functions are documented in the actual library, not
+ * here, so check the docs there to find out what exactly each function does!
+ * 
+ * Notes about the structure of this class (for the next one who has to touch
+ * this...):
+ * 
+ * Java/C interop is quite fiddly and error-prone, so as long as things work,
+ * try to stick to the established patterns.
+ * 
+ * Structure types should always be hidden from the outside world, because they
+ * can be misused too easily. For example, if you get a Structure from the
+ * library, change a String value in there and pass it back, JNA will re-write
+ * that string using Java-owned memory without freeing the old native-owned
+ * string, which causes a memory leak and possibly a double-free or other Bad
+ * Things (tm). To avoid problems like this, Structure types are only used
+ * internally, to map existing structures to Java types (without modifying them)
+ * or to create brand-new, Java-owned structures. Both operations are exposed to
+ * the outside through the PointerType classes corresponding to the structures
+ * in question.
+ * 
+ * Since all of the struct mapping happens in Java, it is never checked against
+ * the actual struct declarations in the library. That means strange things can
+ * start happening at runtime if the frontlib structs are modified without
+ * changing the mappings here to match. This also applies to the function
+ * signatures: JNA checks whether the functions actually exist when loading the
+ * library, but it has no way of knowing whether the signatures are correct. If
+ * the signatures here deviate from those in the frontlib, you might get stack
+ * corruption.
+ * 
+ * In order to check at least the function signatures, take a look at the file
+ * extra/jnacontrol.c in the frontlib sources. You can validate whether the
+ * function signatures are still correct by copy-pasting them into jnaControl.c
+ * and compiling it against the frontlib headers. The typedefs and #defines in
+ * that file will make the compiler see the Java method signatures as C function
+ * declarations. Since the same functions are already declared in the frontlib
+ * headers, the compiler will give you errors if the signatures don't match.
+ */
+public interface Frontlib extends Library {
+	static final int NATIVE_INT_SIZE = 4;
+	static final int NATIVE_BOOL_SIZE = 1;
+	
+	public static class NetconnPtr extends PointerType { }
+	public static class MapconnPtr extends PointerType { }
+	public static class GameconnPtr extends PointerType { }
+	
+	// TODO avoid code duplication in the pointer types
+	public static class MetaschemePtr extends PointerType {
+		public MetaScheme deref() {
+			return deref(getPointer());
+		}
+		
+		public static MetaScheme deref(Pointer p) {
+			MetaschemeStruct struct = new MetaschemeStruct(p);
+			struct.read();
+			return struct.toMetaScheme();
+		}
+	}
+	
+	public static class RoomArrayPtr extends PointerType { 
+		public Room[] getRooms(int count) {
+			Pointer ptr = getPointer();
+			if(ptr == null) {
+				return new Room[0];
+			}
+			Pointer[] untypedPtrs = ptr.getPointerArray(0, count);
+			Room[] result = new Room[count];
+			for(int i=0; i<count; i++) {
+				result[i] = RoomPtr.deref(untypedPtrs[i]);
+			}
+			return result;
+		}
+	}
+	
+	public static class RoomPtr extends PointerType {
+		public Room deref() {
+			return deref(getPointer());
+		}
+		
+		public static Room deref(Pointer p) {
+			RoomStruct struct = new RoomStruct(p);
+			struct.read();
+			return struct.toRoomlistRoom();
+		}
+	}
+	
+	public static class TeamPtr extends PointerType {
+		private TeamStruct javaOwnedInstance; 
+		
+		public TeamInGame deref() {
+			TeamStruct struct = new TeamStruct(getPointer());
+			struct.read();
+			return struct.toTeamInGame();
+		}
+		
+		public static TeamPtr createJavaOwned(Team t) {
+			return createJavaOwned(new TeamInGame(t, null));
+		}
+		
+		public static TeamPtr createJavaOwned(TeamInGame ingameTeam) {
+			TeamPtr result = new TeamPtr();
+			result.javaOwnedInstance = new TeamStruct();
+			result.javaOwnedInstance.fillFrom(ingameTeam.team, ingameTeam.ingameAttribs);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	public static class WeaponsetPtr extends PointerType {
+		private WeaponsetStruct javaOwnedInstance; 
+		
+		public Weaponset deref() {
+			WeaponsetStruct struct = new WeaponsetStruct(getPointer());
+			struct.read();
+			return struct.toWeaponset();
+		}
+		
+		public static WeaponsetPtr createJavaOwned(Weaponset weaponset) {
+			WeaponsetPtr result = new WeaponsetPtr();
+			result.javaOwnedInstance = new WeaponsetStruct();
+			result.javaOwnedInstance.fillFrom(weaponset);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	public static class WeaponsetListPtr extends PointerType {
+		private WeaponsetListStruct javaOwnedInstance;
+		
+		public List<Weaponset> deref() {
+			WeaponsetListStruct struct = new WeaponsetListStruct(getPointer());
+			struct.read();
+			return struct.toWeaponsetList();
+		}
+		
+		public static WeaponsetListPtr createJavaOwned(List<Weaponset> list) {
+			WeaponsetListPtr result = new WeaponsetListPtr();
+			result.javaOwnedInstance = new WeaponsetListStruct();
+			result.javaOwnedInstance.fillFrom(list);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	public static class MapRecipePtr extends PointerType {
+		private MapRecipeStruct javaOwnedInstance;
+		
+		public MapRecipe deref() {
+			MapRecipeStruct struct = new MapRecipeStruct(getPointer());
+			struct.read();
+			return struct.toMapRecipe();
+		}
+		
+		public static MapRecipePtr createJavaOwned(MapRecipe recipe) {
+			MapRecipePtr result = new MapRecipePtr();
+			result.javaOwnedInstance = new MapRecipeStruct();
+			result.javaOwnedInstance.fillFrom(recipe);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	public static class SchemePtr extends PointerType {
+		private SchemeStruct javaOwnedInstance;
+		
+		public Scheme deref() {
+			SchemeStruct struct = new SchemeStruct(getPointer());
+			struct.read();
+			return struct.toScheme();
+		}
+		
+		public static SchemePtr createJavaOwned(Scheme scheme) {
+			SchemePtr result = new SchemePtr();
+			result.javaOwnedInstance = new SchemeStruct();
+			result.javaOwnedInstance.fillFrom(scheme);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	public static class SchemelistPtr extends PointerType {
+		private SchemelistStruct javaOwnedInstance;
+		
+		public List<Scheme> deref() {
+			SchemelistStruct struct = new SchemelistStruct(getPointer());
+			struct.read();
+			return struct.toSchemeList();
+		}
+		
+		public static SchemelistPtr createJavaOwned(List<Scheme> schemes) {
+			SchemelistPtr result = new SchemelistPtr();
+			result.javaOwnedInstance = new SchemelistStruct();
+			result.javaOwnedInstance.fillFrom(schemes);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	public static class GameSetupPtr extends PointerType {
+		private GameSetupStruct javaOwnedInstance;
+		
+		public GameConfig deref() {
+			GameSetupStruct struct = new GameSetupStruct(getPointer());
+			struct.read();
+			return struct.toGameConfig();
+		}
+		
+		public static GameSetupPtr createJavaOwned(GameConfig conf) {
+			GameSetupPtr result = new GameSetupPtr();
+			result.javaOwnedInstance = new GameSetupStruct();
+			result.javaOwnedInstance.fillFrom(conf);
+			result.javaOwnedInstance.autoWrite();
+			result.setPointer(result.javaOwnedInstance.getPointer());
+			return result;
+		}
+	}
+	
+	static class HogStruct extends Structure {
+		public static class ByVal extends HogStruct implements Structure.ByValue {}
+		public static class ByRef extends HogStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"name", "hat", "rounds", "kills", "deaths", "suicides", "difficulty", "initialHealth", "weaponset"};
+
+		public HogStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public HogStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(Hog hog) {
+			difficulty = hog.level;
+			hat = hog.hat;
+			name = hog.name;
+		}
+		
+		public Hog toHog() {
+			return new Hog(name, hat, difficulty);
+		}
+		
+		public String name;
+		public String hat;
+		
+		public int rounds;
+		public int kills;
+		public int deaths;
+		public int suicides;
+	
+		public int difficulty;
+		
+		public int initialHealth;
+		public WeaponsetStruct.ByRef weaponset;
+	}
+	
+	static class TeamStruct extends Structure {
+		public static class ByVal extends TeamStruct implements Structure.ByValue {}
+		public static class ByRef extends TeamStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"hogs", "name", "grave", "fort", "voicepack", "flag", "bindings", "bindingCount", "rounds", "wins", "campaignProgress", "colorIndex", "hogsInGame", "remoteDriven", "ownerName"};
+
+		public TeamStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public TeamStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(Team team, TeamIngameAttributes attrs) {
+			if(team != null) {
+				name = team.name;
+				grave = team.grave;
+				flag = team.flag;
+				voicepack = team.voice;
+				fort = team.fort;
+				if(team.hogs.size() != Team.HEDGEHOGS_PER_TEAM) {
+					throw new IllegalArgumentException();
+				}
+				for(int i=0; i<hogs.length; i++) {
+					hogs[i] = new HogStruct();
+					hogs[i].fillFrom(team.hogs.get(i));
+				}
+			}
+			
+			if(attrs != null) {
+				hogsInGame = attrs.hogCount;
+				ownerName = attrs.ownerName;
+				colorIndex = attrs.colorIndex;
+				remoteDriven = attrs.remoteDriven;
+			}
+		}
+		
+		public void fillFrom(TeamInGame team, WeaponsetStruct.ByRef weaponset, int initialHealth) {
+			fillFrom(team.team, team.ingameAttribs);
+			for(int i=0; i<hogs.length; i++) {
+				hogs[i].initialHealth = initialHealth;
+				hogs[i].weaponset = weaponset;
+			}
+		}
+		
+		public Team toTeam() {
+			List<Hog> hogList = new ArrayList<Hog>();
+			for(int i=0; i<hogs.length; i++) {
+				hogList.add(hogs[i].toHog());
+			}
+			return new Team(name, grave, flag, voicepack, fort, hogList);
+		}
+		
+		public TeamIngameAttributes toTeamIngameAttributes() {
+			return new TeamIngameAttributes(ownerName, colorIndex, hogsInGame, remoteDriven);
+		}
+		
+		public TeamInGame toTeamInGame() {
+			return new TeamInGame(toTeam(), toTeamIngameAttributes());
+		}
+		
+		public HogStruct[] hogs = new HogStruct[Team.HEDGEHOGS_PER_TEAM];
+		public String name;
+		public String grave;
+		public String fort;
+		public String voicepack;
+		public String flag;
+		
+		public Pointer bindings;
+		public int bindingCount;
+		
+		public int rounds;
+		public int wins;
+		public int campaignProgress;
+		
+		public int colorIndex;
+		public int hogsInGame;
+		public boolean remoteDriven;
+		public String ownerName;
+	}
+	
+	static class WeaponsetStruct extends Structure {
+		public static class ByVal extends WeaponsetStruct implements Structure.ByValue {}
+		public static class ByRef extends WeaponsetStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"loadout", "crateprob", "crateammo", "delay", "name"};
+		
+		public WeaponsetStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public WeaponsetStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(Weaponset weaponset) {
+			fillWeaponInfo(loadout, weaponset.loadout);
+			fillWeaponInfo(crateprob, weaponset.crateProb);
+			fillWeaponInfo(crateammo, weaponset.crateAmmo);
+			fillWeaponInfo(delay, weaponset.delay);
+			name = weaponset.name;
+		}
+		
+		private static void fillWeaponInfo(byte[] array, String str) {
+			for(int i=0; i<array.length-1; i++) {
+				array[i] = (byte) (i<str.length() ? str.charAt(i) : '0');
+			}
+			array[array.length-1] = (byte)0;
+		}
+		
+		public Weaponset toWeaponset() {
+			return new Weaponset(name, weaponInfoToString(loadout), weaponInfoToString(crateprob), weaponInfoToString(crateammo), weaponInfoToString(delay));
+		}
+		
+		private static String weaponInfoToString(byte[] array) {
+			try {
+				return new String(array, 0, array.length-1, "ASCII");
+			} catch (UnsupportedEncodingException e) {
+				throw new AssertionError();
+			}
+		}
+		
+		public byte[] loadout = new byte[Weaponset.WEAPONS_COUNT+1];
+		public byte[] crateprob = new byte[Weaponset.WEAPONS_COUNT+1];
+		public byte[] crateammo = new byte[Weaponset.WEAPONS_COUNT+1];
+		public byte[] delay = new byte[Weaponset.WEAPONS_COUNT+1];
+		public String name;
+	}
+	
+	/**
+	 * Represents a flib_weaponset*, for use as part of a flib_weaponset**
+	 */
+	static class WeaponsetPointerByReference extends Structure implements Structure.ByReference {
+		private static String[] FIELD_ORDER = new String[] {"weaponset"};
+		
+		public WeaponsetPointerByReference() { super(); setFieldOrder(FIELD_ORDER); }
+		public WeaponsetPointerByReference(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public WeaponsetStruct.ByRef weaponset;
+	}
+	
+	static class WeaponsetListStruct extends Structure {
+		public static class ByVal extends WeaponsetListStruct implements Structure.ByValue {}
+		public static class ByRef extends WeaponsetListStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"weaponsetCount", "weaponsets"};
+		
+		public WeaponsetListStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public WeaponsetListStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(List<Weaponset> list) {
+			weaponsetCount = list.size();
+			if(weaponsetCount<=0) {
+				weaponsets = null;
+			} else {
+				weaponsets = new WeaponsetPointerByReference();
+				Structure[] structs = weaponsets.toArray(weaponsetCount);
+				
+				for(int i=0; i<weaponsetCount; i++) {
+					WeaponsetPointerByReference pstruct = (WeaponsetPointerByReference)structs[i];
+					pstruct.weaponset = new WeaponsetStruct.ByRef();
+					pstruct.weaponset.fillFrom(list.get(i));
+				}
+			}
+		}
+		
+		/**
+		 * Only use on native-owned structs!
+		 * Calling this method on a Java-owned struct could cause garbage collection of referenced
+		 * structures.
+		 */
+		public List<Weaponset> toWeaponsetList() {
+			if(weaponsetCount<=0) {
+				return new ArrayList<Weaponset>();
+			} else {
+				List<Weaponset> list = new ArrayList<Weaponset>(weaponsetCount);
+				Structure[] structs = weaponsets.toArray(weaponsetCount);
+				
+				for(int i=0; i<weaponsetCount; i++) {
+					WeaponsetPointerByReference pstruct = (WeaponsetPointerByReference)structs[i];
+					list.add(pstruct.weaponset.toWeaponset());
+				}
+				return list;
+			}
+		}
+		
+		public int weaponsetCount;
+		public WeaponsetPointerByReference weaponsets;
+	}
+	
+	static class RoomStruct extends Structure {
+		public static class ByVal extends RoomStruct implements Structure.ByValue {}
+		public static class ByRef extends RoomStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"inProgress", "name", "playerCount", "teamCount", "owner", "map", "scheme", "weapons"};
+		
+		public RoomStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public RoomStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+
+		public Room toRoomlistRoom() {
+			return new Room(name, map, scheme, weapons, owner, playerCount, teamCount, inProgress);
+		}
+		
+	    public boolean inProgress;
+	    public String name;
+	    public int playerCount;
+	    public int teamCount;
+	    public String owner;
+	    public String map;
+	    public String scheme;
+	    public String weapons;
+	}
+	
+	static class MapRecipeStruct extends Structure {
+		public static class ByVal extends MapRecipeStruct implements Structure.ByValue {}
+		public static class ByRef extends MapRecipeStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"mapgen", "name", "seed", "theme", "drawData", "drawDataSize", "templateFilter", "mazeSize"};
+		
+		public MapRecipeStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public MapRecipeStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(MapRecipe map) {
+			mapgen = map.mapgen;
+			name = map.name;
+			seed = map.seed;
+			theme = map.theme;
+			byte[] buf = map.getDrawData();
+			if(buf != null) {
+				drawData = new Memory(buf.length);
+				drawData.write(0, buf, 0, buf.length);
+				drawDataSize = new NativeLong(buf.length);
+			} else {
+				drawData = null;
+				drawDataSize = new NativeLong(0);
+			}
+			templateFilter = map.templateFilter;
+			mazeSize = map.mazeSize;
+		}
+		
+		public MapRecipe toMapRecipe() {
+			byte[] buf = null;
+			if(drawData != null && drawDataSize.intValue()>0) {
+				buf = new byte[drawDataSize.intValue()];
+				drawData.read(0, buf, 0, drawDataSize.intValue());
+			}
+			return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, buf);
+		}
+		
+		public int mapgen;
+		public String name;
+		public String seed;
+		public String theme;
+		public Pointer drawData;
+		public NativeLong drawDataSize;
+		public int templateFilter;
+		public int mazeSize;
+	}
+	
+	static class MetaschemeSettingStruct extends Structure {
+		public static class ByVal extends MetaschemeSettingStruct implements Structure.ByValue {}
+		public static class ByRef extends MetaschemeSettingStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"name", "engineCommand", "maxMeansInfinity", "times1000", "min", "max", "def"};
+		
+		public MetaschemeSettingStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public MetaschemeSettingStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(Setting setting) {
+			name = setting.name;
+			engineCommand = setting.engineCommand;
+			maxMeansInfinity = setting.maxMeansInfinity;
+			times1000 = setting.times1000;
+			min = setting.min;
+			max = setting.max;
+			def = setting.def;
+		}
+		
+		public MetaScheme.Setting toMetaSchemeSetting() {
+			return new MetaScheme.Setting(name, engineCommand, maxMeansInfinity, times1000, min, max, def);
+		}
+		
+		public String name;
+		public String engineCommand;
+		public boolean maxMeansInfinity;
+		public boolean times1000;
+		public int min;
+		public int max;
+		public int def;
+	}
+	
+	static class MetaschemeModStruct extends Structure {
+		public static class ByVal extends MetaschemeModStruct implements Structure.ByValue {}
+		public static class ByRef extends MetaschemeModStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"name", "bitmaskIndex"};
+		
+		public MetaschemeModStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public MetaschemeModStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(Mod mod) {
+			name = mod.name;
+			bitmaskIndex = mod.bitmaskIndex;
+		}
+		
+		public MetaScheme.Mod toMetaSchemeMod() {
+			return new MetaScheme.Mod(name, bitmaskIndex);
+		}
+
+		public String name;
+		public int bitmaskIndex;
+
+	}
+	
+	static class MetaschemeStruct extends Structure {
+		public static class ByVal extends MetaschemeStruct implements Structure.ByValue {}
+		public static class ByRef extends MetaschemeStruct implements Structure.ByReference {}
+
+		private static String[] FIELD_ORDER = new String[] {"settingCount", "modCount", "settings", "mods"};
+		
+		public MetaschemeStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public MetaschemeStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		/**
+		 * Only use on native-owned structs!
+		 * Calling this method on a Java-owned struct could cause garbage collection of referenced
+		 * structures.
+		 */
+		public MetaScheme toMetaScheme() {
+			List<MetaScheme.Setting> settingList = new ArrayList<MetaScheme.Setting>(settingCount);
+			List<MetaScheme.Mod> modList = new ArrayList<MetaScheme.Mod>(modCount);
+			
+			Structure[] settingStructs = settings.toArray(settingCount);
+			Structure[] modStructs = mods.toArray(modCount);
+			
+			for(int i=0; i<settingCount; i++) {
+				MetaschemeSettingStruct mss = (MetaschemeSettingStruct)settingStructs[i];
+				settingList.add(mss.toMetaSchemeSetting());
+			}
+			
+			for(int i=0; i<modCount; i++) {
+				MetaschemeModStruct mms = (MetaschemeModStruct)modStructs[i];
+				modList.add(mms.toMetaSchemeMod());
+			}
+			
+			return new MetaScheme(modList, settingList);
+		}
+		
+		public int settingCount;
+		public int modCount;
+		public MetaschemeSettingStruct.ByRef settings;
+		public MetaschemeModStruct.ByRef mods;
+	}
+	
+	static class SchemeStruct extends Structure {
+		public static class ByVal extends SchemeStruct implements Structure.ByValue {}
+		public static class ByRef extends SchemeStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"name", "settings", "mod"};
+		
+		public SchemeStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public SchemeStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(Scheme scheme) {
+			MetaScheme meta = MetaScheme.INSTANCE;
+			name = scheme.name;
+			settings = new Memory(NATIVE_INT_SIZE * meta.settings.size());
+			for(int i=0; i<meta.settings.size(); i++) {
+				Integer value = scheme.settings.get(meta.settings.get(i).name);
+				settings.setInt(NATIVE_INT_SIZE*i, value);
+			}
+			mods = new Memory(NATIVE_BOOL_SIZE * meta.mods.size());
+			for(int i=0; i<meta.mods.size(); i++) {
+				Boolean value = scheme.mods.get(meta.mods.get(i).name);
+				mods.setByte(NATIVE_BOOL_SIZE*i, (byte)(value ? 1 : 0));
+			}
+		}
+
+		public Scheme toScheme() {
+			Map<String, Integer> settingsMap = new HashMap<String, Integer>();
+			MetaScheme meta = MetaScheme.INSTANCE;
+			for(int i=0; i<meta.settings.size(); i++) {
+				settingsMap.put(meta.settings.get(i).name, settings.getInt(NATIVE_INT_SIZE*i));
+			}
+			Map<String, Boolean> modsMap = new HashMap<String, Boolean>();
+			for(int i=0; i<meta.mods.size(); i++) {
+				modsMap.put(meta.mods.get(i).name, mods.getByte(i) != 0 ? Boolean.TRUE : Boolean.FALSE);
+			}
+			return new Scheme(name, settingsMap, modsMap);
+		}
+		
+		public String name;
+		public Pointer settings;
+		public Pointer mods;
+	}
+	
+	/**
+	 * Represents a flib_scheme*, for use as part of a flib_scheme**
+	 */
+	static class SchemePointerByReference extends Structure implements Structure.ByReference {
+		private static String[] FIELD_ORDER = new String[] {"scheme"};
+		
+		public SchemePointerByReference() { super(); setFieldOrder(FIELD_ORDER); }
+		public SchemePointerByReference(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public SchemeStruct.ByRef scheme;
+	}
+	
+	static class SchemelistStruct extends Structure {
+		public static class ByVal extends SchemelistStruct implements Structure.ByValue {}
+		public static class ByRef extends SchemelistStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"schemeCount", "schemes"};
+		
+		public SchemelistStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public SchemelistStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(List<Scheme> schemeList) {
+			schemeCount = schemeList.size();
+			if(schemeCount<=0) {
+				schemes = null;
+			} else {
+				schemes = new SchemePointerByReference();
+				Structure[] schemePtrStructs = schemes.toArray(schemeCount);
+				
+				for(int i=0; i<this.schemeCount; i++) {
+					SchemePointerByReference spbr = (SchemePointerByReference)schemePtrStructs[i];
+					spbr.scheme = new SchemeStruct.ByRef();
+					spbr.scheme.fillFrom(schemeList.get(i));
+				}
+			}
+		}
+
+		/**
+		 * Only use on native-owned structs!
+		 * Calling this method on a Java-owned struct could cause garbage collection of referenced
+		 * structures.
+		 */
+		public List<Scheme> toSchemeList() {
+			if(schemeCount<=0) {
+				return new ArrayList<Scheme>();
+			} else {
+				List<Scheme> schemeList = new ArrayList<Scheme>(schemeCount);
+				
+				Structure[] schemePtrStructs = schemes.toArray(schemeCount);
+				
+				for(int i=0; i<schemeCount; i++) {
+					SchemePointerByReference spbr2 = (SchemePointerByReference)schemePtrStructs[i];
+					schemeList.add(spbr2.scheme.toScheme());
+				}
+				return schemeList;
+			}
+		}
+		
+		public int schemeCount;
+		public SchemePointerByReference schemes;
+	}
+	
+	/**
+	 * Represents a flib_team*, for use as part of a flib_team**
+	 */
+	static class TeamPointerByReference extends Structure implements Structure.ByReference {
+		private static String[] FIELD_ORDER = new String[] {"team"};
+		
+		public TeamPointerByReference() { super(); setFieldOrder(FIELD_ORDER); }
+		public TeamPointerByReference(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public TeamStruct.ByRef team;
+	}
+	
+	static class TeamlistStruct extends Structure {
+		public static class ByVal extends TeamlistStruct implements Structure.ByValue {}
+		public static class ByRef extends TeamlistStruct implements Structure.ByReference {}
+
+		private static String[] FIELD_ORDER = new String[] {"teamCount", "teams"};
+		
+		public TeamlistStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public TeamlistStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(List<TeamInGame> teamList, WeaponsetStruct.ByRef weaponset, int initialHealth) {
+			teamCount = teamList.size();
+			if(teamCount <= 0) {
+				teams = null;
+			} else {
+				teams = new TeamPointerByReference();
+				Structure[] teamPtrStructs = teams.toArray(teamCount);
+				
+				for(int i=0; i<this.teamCount; i++) {
+					TeamPointerByReference tpbr = (TeamPointerByReference)teamPtrStructs[i];
+					tpbr.team = new TeamStruct.ByRef();
+					tpbr.team.fillFrom(teamList.get(i), weaponset, initialHealth);
+				}
+			}
+		}
+
+		public List<TeamInGame> toTeamInGameList() {
+			if(teamCount<=0) {
+				return new ArrayList<TeamInGame>();
+			} else {
+				List<TeamInGame> result = new ArrayList<TeamInGame>(teamCount);
+				Structure[] structs = teams.toArray(teamCount);
+				
+				for(int i=0; i<teamCount; i++) {
+					TeamPointerByReference struct = (TeamPointerByReference)structs[i];
+					result.add(struct.team.toTeamInGame());
+				}
+				return result;
+			}
+		}
+		
+		public int teamCount;
+		public TeamPointerByReference teams;
+	}
+	
+	static class GameSetupStruct extends Structure {
+		public static class ByVal extends GameSetupStruct implements Structure.ByValue {}
+		public static class ByRef extends GameSetupStruct implements Structure.ByReference {}
+		private static String[] FIELD_ORDER = new String[] {"script", "gamescheme", "map", "teamlist"};
+		
+		public GameSetupStruct() { super(); setFieldOrder(FIELD_ORDER); }
+		public GameSetupStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
+		
+		public void fillFrom(GameConfig conf) {
+			script = conf.style;
+			gamescheme = new SchemeStruct.ByRef();
+			gamescheme.fillFrom(conf.scheme);
+			map = new MapRecipeStruct.ByRef();
+			map.fillFrom(conf.map);
+			
+			/*
+			 * At this point we deviate from the usual copying pattern because the frontlib
+			 * expects per-hog weapons and initial health, but the UI models them as per-
+			 * game, so we extract them from the config here and pass them on to be included
+			 * in each hog.
+			 */
+			WeaponsetStruct.ByRef wss = new WeaponsetStruct.ByRef();
+			wss.fillFrom(conf.weaponset);
+			int initialHealth = conf.scheme.getHealth();
+			
+			teamlist = new TeamlistStruct.ByRef();
+			teamlist.fillFrom(conf.teams, wss, initialHealth);
+		}
+		
+		public GameConfig toGameConfig() {
+			Scheme scheme = gamescheme != null ? gamescheme.toScheme() : null;
+			MapRecipe mapRecipe = map != null ? map.toMapRecipe() : null;
+			List<TeamInGame> teams = teamlist != null ? teamlist.toTeamInGameList() : null;
+			
+			WeaponsetStruct weaponsetStruct = teamlist != null && teamlist.teamCount>0 ? teamlist.teams.team.hogs[0].weaponset : null;
+			Weaponset weaponset = weaponsetStruct != null ? weaponsetStruct.toWeaponset() : null;
+			return new GameConfig(script, scheme, mapRecipe, teams, weaponset);
+		}
+
+		public String script;
+		public SchemeStruct.ByRef gamescheme;
+		public MapRecipeStruct.ByRef map;
+		public TeamlistStruct.ByRef teamlist;
+	}
+	
+	/*
+	 * Callback interfaces. The context parameter is never needed here and
+	 * should always be ignored. Be sure to keep a reference to each callback
+	 * for as long as they might be called by native code, to avoid premature
+	 * garbage collection.
+	 */
+	public static interface VoidCallback extends Callback {
+		void callback(Pointer context);
+	}
+	
+	public static interface StrCallback extends Callback {
+		void callback(Pointer context, String arg1);
+	}
+	
+	public static interface IntCallback extends Callback {
+		void callback(Pointer context, int arg1);
+	}
+	
+	public static interface IntStrCallback extends Callback {
+		void callback(Pointer context, int arg1, String arg2);
+	}
+	
+	public static interface StrIntCallback extends Callback {
+		void callback(Pointer context, String arg1, int arg2);
+	}
+	
+	public static interface StrStrCallback extends Callback {
+		void callback(Pointer context, String arg1, String arg2);
+	}
+	
+	public static interface RoomCallback extends Callback {
+		void callback(Pointer context, RoomPtr arg1);
+	}
+	
+	public static interface RoomListCallback extends Callback {
+		void callback(Pointer context, RoomArrayPtr arg1, int count);
+	}
+	
+	public static interface StrRoomCallback extends Callback {
+		void callback(Pointer context, String arg1, RoomPtr arg2);
+	}
+	
+	public static interface BoolCallback extends Callback {
+		void callback(Pointer context, boolean arg1);
+	}
+	
+	public static interface StrBoolCallback extends Callback {
+		void callback(Pointer context, String arg1, boolean arg2);
+	}
+	
+	public static interface TeamCallback extends Callback {
+		void callback(Pointer context, TeamPtr arg1);
+	}
+	
+	public static interface BytesCallback extends Callback {
+		void callback(Pointer context, Pointer buffer, NativeLong size);
+	}
+	
+	public static interface BytesBoolCallback extends Callback {
+		void callback(Pointer context, Pointer buffer, NativeLong size, boolean arg3);
+	}
+	
+	public static interface SchemeCallback extends Callback {
+		void callback(Pointer context, SchemePtr arg1);
+	}
+	
+	public static interface MapIntCallback extends Callback {
+		void callback(Pointer context, MapRecipePtr arg1, int arg2);
+	}
+	
+	public static interface WeaponsetCallback extends Callback {
+		void callback(Pointer context, WeaponsetPtr arg1);
+	}
+	
+	public static interface MapimageCallback extends Callback {
+		void callback(Pointer context, Pointer buffer, int hedgehogCount);
+	}
+	
+	public static interface LogCallback extends Callback {
+		void callback(int level, String logMessage);
+	}
+	
+	// frontlib.h
+    int flib_init();
+    void flib_quit();
+	
+    // hwconsts.h
+    int flib_get_teamcolor(int colorIndex);
+    int flib_get_teamcolor_count();
+    int flib_get_hedgehogs_per_team();
+    int flib_get_weapons_count();
+	MetaschemePtr flib_get_metascheme();
+	
+    // net/netconn.h
+	static final int NETCONN_STATE_CONNECTING = 0;
+	static final int NETCONN_STATE_LOBBY = 1;
+	static final int NETCONN_STATE_ROOM = 2;
+	static final int NETCONN_STATE_DISCONNECTED = 10;
+	
+	static final int NETCONN_DISCONNECT_NORMAL = 0;
+	static final int NETCONN_DISCONNECT_SERVER_TOO_OLD = 1;
+	static final int NETCONN_DISCONNECT_AUTH_FAILED = 2;
+	static final int NETCONN_DISCONNECT_CONNLOST = 3;
+	static final int NETCONN_DISCONNECT_INTERNAL_ERROR = 100;
+	
+	static final int NETCONN_ROOMLEAVE_ABANDONED = 0;
+	static final int NETCONN_ROOMLEAVE_KICKED = 1;
+	
+	static final int NETCONN_MSG_TYPE_PLAYERINFO = 0;
+	static final int NETCONN_MSG_TYPE_SERVERMESSAGE = 1;
+	static final int NETCONN_MSG_TYPE_WARNING = 2;
+	static final int NETCONN_MSG_TYPE_ERROR = 3;
+	
+	static final int NETCONN_MAPCHANGE_FULL = 0;
+	static final int NETCONN_MAPCHANGE_MAP = 1;
+	static final int NETCONN_MAPCHANGE_MAPGEN = 2;
+	static final int NETCONN_MAPCHANGE_DRAWNMAP = 3;
+	static final int NETCONN_MAPCHANGE_MAZE_SIZE = 4;
+	static final int NETCONN_MAPCHANGE_TEMPLATE = 5;
+	static final int NETCONN_MAPCHANGE_THEME = 6;
+	static final int NETCONN_MAPCHANGE_SEED = 7;
+    
+	NetconnPtr flib_netconn_create(String playerName, String dataDirPath, String host, int port);
+	void flib_netconn_destroy(NetconnPtr conn);
+
+	void flib_netconn_tick(NetconnPtr conn);
+	boolean flib_netconn_is_chief(NetconnPtr conn);
+	String flib_netconn_get_playername(NetconnPtr conn);
+	GameSetupPtr flib_netconn_create_gamesetup(NetconnPtr conn);
+	int flib_netconn_send_quit(NetconnPtr conn, String quitmsg);
+	int flib_netconn_send_chat(NetconnPtr conn, String chat);
+	int flib_netconn_send_teamchat(NetconnPtr conn, String msg);
+	int flib_netconn_send_password(NetconnPtr conn, String passwd);
+	int flib_netconn_send_nick(NetconnPtr conn, String nick);
+	int flib_netconn_send_request_roomlist(NetconnPtr conn);
+	int flib_netconn_send_joinRoom(NetconnPtr conn, String room);
+	int flib_netconn_send_createRoom(NetconnPtr conn, String room);
+	int flib_netconn_send_renameRoom(NetconnPtr conn, String roomName);
+	int flib_netconn_send_leaveRoom(NetconnPtr conn, String msg);
+	int flib_netconn_send_toggleReady(NetconnPtr conn);
+	int flib_netconn_send_addTeam(NetconnPtr conn, TeamPtr team);
+	int flib_netconn_send_removeTeam(NetconnPtr conn, String teamname);
+	int flib_netconn_send_engineMessage(NetconnPtr conn, Pointer message, NativeLong size);
+	int flib_netconn_send_teamHogCount(NetconnPtr conn, String teamname, int hogcount);
+	int flib_netconn_send_teamColor(NetconnPtr conn, String teamname, int colorIndex);
+	int flib_netconn_send_weaponset(NetconnPtr conn, WeaponsetPtr weaponset);
+	int flib_netconn_send_map(NetconnPtr conn, MapRecipePtr map);
+	int flib_netconn_send_mapName(NetconnPtr conn, String mapName);
+	int flib_netconn_send_mapGen(NetconnPtr conn, int mapGen);
+	int flib_netconn_send_mapTemplate(NetconnPtr conn, int templateFilter);
+	int flib_netconn_send_mapMazeSize(NetconnPtr conn, int mazeSize);
+	int flib_netconn_send_mapSeed(NetconnPtr conn, String seed);
+	int flib_netconn_send_mapTheme(NetconnPtr conn, String theme);
+	int flib_netconn_send_mapDrawdata(NetconnPtr conn, Pointer drawData, NativeLong size);
+	int flib_netconn_send_script(NetconnPtr conn, String scriptName);
+	int flib_netconn_send_scheme(NetconnPtr conn, SchemePtr scheme);
+	int flib_netconn_send_roundfinished(NetconnPtr conn, boolean withoutError);
+	int flib_netconn_send_ban(NetconnPtr conn, String playerName);
+	int flib_netconn_send_kick(NetconnPtr conn, String playerName);
+	int flib_netconn_send_playerInfo(NetconnPtr conn, String playerName);
+	int flib_netconn_send_playerFollow(NetconnPtr conn, String playerName);
+	int flib_netconn_send_startGame(NetconnPtr conn);
+	int flib_netconn_send_toggleRestrictJoins(NetconnPtr conn);
+	int flib_netconn_send_toggleRestrictTeams(NetconnPtr conn);
+	int flib_netconn_send_clearAccountsCache(NetconnPtr conn);
+	int flib_netconn_send_setServerVar(NetconnPtr conn, String name, String value);
+	int flib_netconn_send_getServerVars(NetconnPtr conn);
+	
+	void flib_netconn_onMessage(NetconnPtr conn, IntStrCallback callback, Pointer context);
+	void flib_netconn_onChat(NetconnPtr conn, StrStrCallback callback, Pointer context);
+	void flib_netconn_onConnected(NetconnPtr conn, VoidCallback callback, Pointer context);
+	void flib_netconn_onDisconnected(NetconnPtr conn, IntStrCallback callback, Pointer context);
+	void flib_netconn_onRoomlist(NetconnPtr conn, RoomListCallback callback, Pointer context);
+	void flib_netconn_onRoomAdd(NetconnPtr conn, RoomCallback callback, Pointer context);
+	void flib_netconn_onRoomDelete(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onRoomUpdate(NetconnPtr conn, StrRoomCallback callback, Pointer context);
+	void flib_netconn_onLobbyJoin(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onLobbyLeave(NetconnPtr conn, StrStrCallback callback, Pointer context);
+	void flib_netconn_onNickTaken(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onPasswordRequest(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onEnterRoom(NetconnPtr conn, BoolCallback callback, Pointer context);
+	void flib_netconn_onRoomChiefStatus(NetconnPtr conn, BoolCallback callback, Pointer context);
+	void flib_netconn_onReadyState(NetconnPtr conn, StrBoolCallback callback, Pointer context);
+	void flib_netconn_onLeaveRoom(NetconnPtr conn, IntStrCallback callback, Pointer context);
+	void flib_netconn_onTeamAdd(NetconnPtr conn, TeamCallback callback, Pointer context);
+	void flib_netconn_onTeamDelete(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onRoomJoin(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onRoomLeave(NetconnPtr conn, StrStrCallback callback, Pointer context);
+	void flib_netconn_onRunGame(NetconnPtr conn, VoidCallback callback, Pointer context);
+	void flib_netconn_onTeamAccepted(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onHogCountChanged(NetconnPtr conn, StrIntCallback callback, Pointer context);
+	void flib_netconn_onTeamColorChanged(NetconnPtr conn, StrIntCallback callback, Pointer context);
+	void flib_netconn_onEngineMessage(NetconnPtr conn, BytesCallback callback, Pointer context);
+	void flib_netconn_onCfgScheme(NetconnPtr conn, SchemeCallback callback, Pointer context);
+	void flib_netconn_onMapChanged(NetconnPtr conn, MapIntCallback callback, Pointer context);
+	void flib_netconn_onScriptChanged(NetconnPtr conn, StrCallback callback, Pointer context);
+	void flib_netconn_onWeaponsetChanged(NetconnPtr conn, WeaponsetCallback callback, Pointer context);
+	void flib_netconn_onAdminAccess(NetconnPtr conn, VoidCallback callback, Pointer context);
+	void flib_netconn_onServerVar(NetconnPtr conn, StrStrCallback callback, Pointer context);
+
+	// ipc/gameconn.h
+	static final int GAME_END_FINISHED = 0;
+	static final int GAME_END_INTERRUPTED = 1;
+	static final int GAME_END_HALTED = 2;
+	static final int GAME_END_ERROR = 3;
+	
+	GameconnPtr flib_gameconn_create(String playerName, GameSetupPtr setup, boolean netgame);
+	GameconnPtr flib_gameconn_create_playdemo(Buffer demo, NativeLong size);
+	GameconnPtr flib_gameconn_create_loadgame(String playerName, Buffer save, NativeLong size);
+	GameconnPtr flib_gameconn_create_campaign(String playerName, String seed, String script);
+
+	void flib_gameconn_destroy(GameconnPtr conn);
+	int flib_gameconn_getport(GameconnPtr conn);
+	void flib_gameconn_tick(GameconnPtr conn);
+
+	int flib_gameconn_send_enginemsg(GameconnPtr conn, Pointer data, NativeLong len);
+	int flib_gameconn_send_textmsg(GameconnPtr conn, int msgtype, String msg);
+	int flib_gameconn_send_chatmsg(GameconnPtr conn, String playername, String msg);
+	int flib_gameconn_send_quit(GameconnPtr conn);
+	
+	void flib_gameconn_onConnect(GameconnPtr conn, VoidCallback callback, Pointer context);
+	void flib_gameconn_onDisconnect(GameconnPtr conn, IntCallback callback, Pointer context);
+	void flib_gameconn_onErrorMessage(GameconnPtr conn, StrCallback callback, Pointer context);
+	void flib_gameconn_onChat(GameconnPtr conn, StrBoolCallback callback, Pointer context);
+	void flib_gameconn_onGameRecorded(GameconnPtr conn, BytesBoolCallback callback, Pointer context);
+	void flib_gameconn_onEngineMessage(GameconnPtr conn, BytesCallback callback, Pointer context);
+	
+	// ipc/mapconn.h
+	public static final int MAPIMAGE_WIDTH = 256;
+	public static final int MAPIMAGE_HEIGHT = 128;
+	public static final int MAPIMAGE_BYTES = (MAPIMAGE_WIDTH/8*MAPIMAGE_HEIGHT);
+	
+	MapconnPtr flib_mapconn_create(MapRecipePtr mapdesc);
+	void flib_mapconn_destroy(MapconnPtr conn);
+	int flib_mapconn_getport(MapconnPtr conn);
+	void flib_mapconn_onSuccess(MapconnPtr conn, MapimageCallback callback, Pointer context);
+	void flib_mapconn_onFailure(MapconnPtr conn, StrCallback callback, Pointer context);
+	void flib_mapconn_tick(MapconnPtr conn);
+	
+	// model/map.h
+	public static final int MAPGEN_REGULAR = 0;
+	public static final int MAPGEN_MAZE = 1;
+	public static final int MAPGEN_DRAWN = 2;
+	public static final int MAPGEN_NAMED = 3;
+
+	public static final int TEMPLATEFILTER_ALL = 0;
+	public static final int TEMPLATEFILTER_SMALL = 1;
+	public static final int TEMPLATEFILTER_MEDIUM = 2;
+	public static final int TEMPLATEFILTER_LARGE = 3;
+	public static final int TEMPLATEFILTER_CAVERN = 4;
+	public static final int TEMPLATEFILTER_WACKY = 5;
+
+	public static final int MAZE_SIZE_SMALL_TUNNELS = 0;
+	public static final int MAZE_SIZE_MEDIUM_TUNNELS = 1;
+	public static final int MAZE_SIZE_LARGE_TUNNELS = 2;
+	public static final int MAZE_SIZE_SMALL_ISLANDS = 3;
+	public static final int MAZE_SIZE_MEDIUM_ISLANDS = 4;
+	public static final int MAZE_SIZE_LARGE_ISLANDS = 5;
+		
+	// model/schemelist.h
+	SchemelistPtr flib_schemelist_from_ini(String filename);
+	int flib_schemelist_to_ini(String filename, SchemelistPtr list);
+	void flib_schemelist_destroy(SchemelistPtr list);
+	
+	// model/team.h
+	TeamPtr flib_team_from_ini(String filename);
+	int flib_team_to_ini(String filename, TeamPtr team);
+	void flib_team_destroy(TeamPtr team);
+	
+	// model/weapon.h
+	WeaponsetListPtr flib_weaponsetlist_from_ini(String filename);
+	int flib_weaponsetlist_to_ini(String filename, WeaponsetListPtr weaponsets);
+	void flib_weaponsetlist_destroy(WeaponsetListPtr list);
+	
+	// model/gamesetup.h
+	void flib_gamesetup_destroy(GameSetupPtr gamesetup);
+	
+	// util/logging.h
+	public static final int FLIB_LOGLEVEL_ALL = -100;
+	public static final int FLIB_LOGLEVEL_DEBUG = -1;
+	public static final int FLIB_LOGLEVEL_INFO = 0;
+	public static final int FLIB_LOGLEVEL_WARNING = 1;
+	public static final int FLIB_LOGLEVEL_ERROR = 2;
+	public static final int FLIB_LOGLEVEL_NONE = 100;
+	
+    void flib_log_setLevel(int level);
+    void flib_log_setCallback(LogCallback callback);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/frontlib/FrontlibTypeMapper.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,27 @@
+package org.hedgewars.hedgeroid.frontlib;
+
+import com.sun.jna.DefaultTypeMapper;
+import com.sun.jna.FromNativeContext;
+import com.sun.jna.ToNativeContext;
+import com.sun.jna.TypeConverter;
+import com.sun.jna.TypeMapper;
+
+class FrontlibTypeMapper extends DefaultTypeMapper {
+    public static final TypeMapper INSTANCE = new FrontlibTypeMapper();
+    
+    protected FrontlibTypeMapper() {
+        addTypeConverter(Boolean.class, new BooleanConverter());
+    }
+}
+
+class BooleanConverter implements TypeConverter {
+    public Class<Byte> nativeType() {
+        return Byte.class;
+    }
+	public Object fromNative(Object value, FromNativeContext context) {
+		return ((Byte)value).intValue() != 0 ? Boolean.TRUE : Boolean.FALSE;
+	}
+	public Object toNative(Object value, ToNativeContext context) {
+		return Byte.valueOf((byte)(Boolean.TRUE.equals(value) ? 1 : 0));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/GameMessageListener.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,15 @@
+package org.hedgewars.hedgeroid.netplay;
+
+/**
+ * Interface with several event callbacks that represent network messages which are interesting
+ * for a running game, e.g. because they concern the lifecycle of the game or because they contain
+ * data that needs to be passed on.
+ * 
+ * These functions might be called on any thread.
+ */
+public interface GameMessageListener {
+	void onChatMessage(String nick, String message);
+	void onEngineMessage(byte[] em);
+	void onMessage(int type, String message);
+	void onNetDisconnected();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/MessageLog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,151 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
+
+public class MessageLog {
+	private static final int BACKLOG_LINES = 200;
+	
+	private static final int INFO_COLOR = Color.GRAY;
+	private static final int PLAYERINFO_COLOR = Color.GREEN;
+	private static final int CHAT_COLOR = Color.GREEN;
+	private static final int MECHAT_COLOR = Color.CYAN;
+	private static final int WARN_COLOR = Color.RED;
+	private static final int ERROR_COLOR = Color.RED;
+	
+	private final Context context;
+	private List<Observer> observers = new LinkedList<Observer>();
+	private List<CharSequence> log = new LinkedList<CharSequence>();
+	
+	public MessageLog(Context context) {
+		this.context = context;
+	}
+	
+	private Spanned makeLogTime() {
+		String time = DateFormat.getTimeFormat(context).format(new Date());
+		return withColor("[" + time + "] ", INFO_COLOR);
+	}
+	
+	private static Spanned span(CharSequence s, Object o) {
+		Spannable spannable = new SpannableString(s);
+		spannable.setSpan(o, 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+		return spannable;
+	}
+	
+	private static Spanned withColor(CharSequence s, int color) {
+		return span(s, new ForegroundColorSpan(color));
+	}
+	
+	private static Spanned bold(CharSequence s) {
+		return span(s, new StyleSpan(Typeface.BOLD));
+	}
+	
+	private void append(CharSequence msg) {
+		SpannableStringBuilder ssb = new SpannableStringBuilder();
+		ssb.append(makeLogTime()).append(msg);
+		appendRaw(ssb);
+	}
+	
+	private void appendRaw(CharSequence msg) {
+		if(log.size() > BACKLOG_LINES) {
+			log.remove(0);
+			for(Observer o : observers) {
+				o.lineRemoved();
+			}
+		}
+		log.add(msg);
+		for(Observer o : observers) {
+			o.lineAdded(msg);
+		}
+	}
+	
+	void appendPlayerJoin(String playername) {
+		append(withColor("***" + context.getResources().getString(R.string.log_player_join, playername), INFO_COLOR));
+	}
+	
+	void appendPlayerLeave(String playername, String partMsg) {
+		String msg = "***";
+		if(partMsg != null) {
+			msg += context.getResources().getString(R.string.log_player_leave_with_msg, playername, partMsg);
+		} else {
+			msg += context.getResources().getString(R.string.log_player_leave, playername);
+		}
+		append(withColor(msg, INFO_COLOR));
+	}
+	
+	void appendChat(String playerName, String msg) {
+		if(msg.startsWith("/me ")) {
+			append(withColor("*"+playerName+" "+msg.substring(4), MECHAT_COLOR));
+		} else {
+			Spanned name = bold(playerName+": ");
+			Spanned fullMessage = withColor(TextUtils.concat(name, msg), CHAT_COLOR);
+			append(fullMessage);			
+		}
+	}
+	
+	void appendMessage(int type, String msg) {
+		switch(type) {
+		case Frontlib.NETCONN_MSG_TYPE_ERROR:
+			append(withColor("***"+msg, ERROR_COLOR));
+			break;
+		case Frontlib.NETCONN_MSG_TYPE_WARNING:
+			append(withColor("***"+msg, WARN_COLOR));
+			break;
+		case Frontlib.NETCONN_MSG_TYPE_PLAYERINFO:
+			// TODO Display in popup?
+			append(withColor(msg.replace("\n", " "), PLAYERINFO_COLOR));
+			break;
+		case Frontlib.NETCONN_MSG_TYPE_SERVERMESSAGE:
+			appendRaw(span(TextUtils.concat("\n", Html.fromHtml(msg), "\n"), new RelativeSizeSpan(1.5f)));
+			break;
+		default:
+			Log.e("MessageLog", "Unknown messagetype "+type);
+		}
+	}
+	
+	void clear() {
+		for(Observer o : observers) {
+			o.clear();
+		}
+		log.clear();
+	}
+	
+	public void registerObserver(Observer o) {
+		observers.add(o);
+	}
+	
+	public void unregisterObserver(Observer o) {
+		observers.remove(o);
+	}
+	
+	public static interface Observer {
+		void lineAdded(CharSequence text);
+		void lineRemoved();
+		void clear();
+	}
+
+	public Collection<CharSequence> getLog() {
+		return Collections.unmodifiableList(log);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetRoomState.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,207 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_GAMESTYLE;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_DRAWDATA;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_GENERATOR;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_NAME;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_SEED;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_TEMPLATE;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_THEME;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAZE_SIZE;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_SCHEME;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_WEAPONSET;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.RoomStateManager;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType;
+
+/**
+ * This class manages the room state in a network game.
+ */
+class NetRoomState implements RoomStateManager {
+	private List<RoomStateManager.Observer> observers = new LinkedList<RoomStateManager.Observer>();
+	private Netplay netplay;
+	
+	boolean chief;
+	String gameStyle;
+	Scheme scheme;
+	MapRecipe map;
+	Weaponset weaponset;
+	
+	public NetRoomState(Netplay netplay) {
+		this.netplay = netplay;
+		this.map = MapRecipe.makeRandomMap(0, "seed", GameConfig.DEFAULT_THEME);
+	}
+
+	public MapRecipe getMapRecipe() {
+		return map;
+	}
+
+	public boolean getChiefStatus() {
+		return chief;
+	}
+
+	public Scheme getScheme() {
+		return scheme;
+	}
+
+	public String getGameStyle() {
+		return gameStyle;
+	}
+
+	public Weaponset getWeaponset() {
+		return weaponset;
+	}
+
+	public void changeWeaponset(Weaponset weaponset) {
+		if(chief && !weaponset.equals(this.weaponset)) {
+			sendToNet(MSG_SEND_WEAPONSET, weaponset);
+			setWeaponset(weaponset);
+		}
+	}
+	
+	public void changeMapRecipe(MapRecipe mapRecipe) {
+		if(chief && !mapRecipe.equals(this.map)) {
+			sendToNet(MSG_SEND_MAP, mapRecipe);
+			setMapRecipe(mapRecipe);
+		}
+	}
+	
+	public void changeMapNameAndGenerator(String mapName) {
+		if(chief && !mapName.equals(this.map.name)) {
+			int newGenerator = MapRecipe.generatorForMapname(mapName);
+			if(newGenerator != this.map.mapgen) {
+				sendToNet(MSG_SEND_MAP_GENERATOR, newGenerator, null);
+			}
+			sendToNet(MSG_SEND_MAP_NAME, mapName);
+			setMapRecipe(map.withName(mapName).withMapgen(newGenerator));
+		}
+	}
+	
+	public void changeMapTemplate(int template) {
+		if(chief && template != this.map.templateFilter) {
+			sendToNet(MSG_SEND_MAP_TEMPLATE, template, null);
+			setMapRecipe(map.withTemplateFilter(template));
+		}
+	}
+	
+	public void changeMazeSize(int mazeSize) {
+		if(chief && mazeSize != this.map.mazeSize) {
+			sendToNet(MSG_SEND_MAZE_SIZE, mazeSize, 0);
+			setMapRecipe(map.withMazeSize(mazeSize));
+		}
+	}
+	
+	public void changeMapSeed(String seed) {
+		if(chief && !seed.equals(this.map.seed)) {
+			sendToNet(MSG_SEND_MAP_SEED, seed);
+			setMapRecipe(map.withSeed(seed));
+		}
+	}
+	
+	public void changeMapTheme(String theme) {
+		if(chief && !theme.equals(this.map.theme)) {
+			sendToNet(MSG_SEND_MAP_THEME, theme);
+			setMapRecipe(map.withTheme(theme));
+		}
+	}
+	
+	public void changeMapDrawdata(byte[] drawdata) {
+		if(chief && !Arrays.equals(drawdata, this.map.getDrawData())) {
+			sendToNet(MSG_SEND_MAP_DRAWDATA, drawdata);
+			setMapRecipe(map.withDrawData(drawdata));
+		}
+	}
+	
+	public void changeGameStyle(String gameStyle) {
+		if(chief && !gameStyle.equals(this.gameStyle)) {
+			sendToNet(MSG_SEND_GAMESTYLE, gameStyle);
+			setGameStyle(gameStyle);
+		}
+	}
+	
+	public void changeScheme(Scheme scheme) {
+		if(chief && !scheme.equals(this.scheme)) {
+			sendToNet(MSG_SEND_SCHEME, scheme);
+			setScheme(scheme);
+		}
+	}
+	
+	void setWeaponset(Weaponset weaponset) {
+		if(!weaponset.equals(this.weaponset)) {
+			this.weaponset = weaponset;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onWeaponsetChanged(weaponset);
+			}
+		}
+	}
+	
+	void setMapRecipe(MapRecipe map) {
+		if(!map.equals(this.map)) { 
+			this.map = map;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onMapChanged(map);
+			}
+		}
+	}
+	
+	void setGameStyle(String gameStyle) {
+		if(!gameStyle.equals(this.gameStyle)) {
+			this.gameStyle = gameStyle;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onGameStyleChanged(gameStyle);
+			}
+		}
+	}
+	
+	void setScheme(Scheme scheme) {
+		if(!scheme.equals(this.scheme)) {
+			this.scheme = scheme;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onSchemeChanged(scheme);
+			}
+		}
+	}
+	
+	void setChief(boolean chief) {
+		if(chief != this.chief) {
+			this.chief = chief;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onChiefStatusChanged(chief);
+			}
+		}
+	}
+	
+	void sendFullConfig() {
+		if(chief) {
+			sendToNet(MSG_SEND_GAMESTYLE, gameStyle);
+			sendToNet(MSG_SEND_SCHEME, scheme);
+			sendToNet(MSG_SEND_WEAPONSET, weaponset);
+			sendToNet(MSG_SEND_MAP, map);
+		}
+	}
+	
+	public void registerObserver(Observer observer) {
+		observers.add(observer);
+	}
+
+	public void unregisterObserver(Observer observer) {
+		observers.remove(observer);
+	}
+	
+	private boolean sendToNet(ToNetMsgType what, Object obj) {
+		return netplay.sendToNet(what, 0, obj);
+	}
+	
+	private boolean sendToNet(ToNetMsgType what, int arg1, Object obj) {
+		return netplay.sendToNet(what, arg1, obj);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,514 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.hedgewars.hedgeroid.RoomStateManager;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Schemes;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType;
+import org.hedgewars.hedgeroid.util.ObservableTreeMap;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.util.Pair;
+
+
+/**
+ * This class manages the application's networking state.
+ */
+public class Netplay {
+	public static enum State { NOT_CONNECTED, CONNECTING, LOBBY, ROOM, INGAME }
+	
+	// Extras in broadcasts
+	public static final String EXTRA_PLAYERNAME = "playerName";
+	public static final String EXTRA_MESSAGE = "message";
+	public static final String EXTRA_HAS_ERROR = "hasError";
+	public static final String EXTRA_REASON = "reason";
+	
+	private static final String ACTIONPREFIX = "org.hedgewars.hedgeroid.netconn.";
+	public static final String ACTION_DISCONNECTED = ACTIONPREFIX+"DISCONNECTED";
+	public static final String ACTION_CONNECTED = ACTIONPREFIX+"CONNECTED";
+	public static final String ACTION_PASSWORD_REQUESTED = ACTIONPREFIX+"PASSWORD_REQUESTED";
+	public static final String ACTION_ENTERED_ROOM_FROM_LOBBY = ACTIONPREFIX+"ENTERED_ROOM";
+	public static final String ACTION_LEFT_ROOM = ACTIONPREFIX+"LEFT_ROOM";
+	public static final String ACTION_STATE_CHANGED = ACTIONPREFIX+"STATE_CHANGED";
+	
+	public static final String DEFAULT_SERVER = "netserver.hedgewars.org";
+	public static final int DEFAULT_PORT = 46631;
+		
+	private final Context appContext;
+	private final LocalBroadcastManager broadcastManager;
+	private final FromNetHandler fromNetHandler = new FromNetHandler();
+	
+	private State state = State.NOT_CONNECTED;
+	private String playerName;
+	
+	// null or stale if not in room state
+	private final NetRoomState netRoomState = new NetRoomState(this);
+	
+	// null if there is no running connection (==state is NOT_CONNECTED)
+	private ThreadedNetConnection connection;
+	
+	public final ObservableTreeMap<String, Player> lobbyPlayerlist = new ObservableTreeMap<String, Player>();
+	public final ObservableTreeMap<String, PlayerInRoom> roomPlayerlist = new ObservableTreeMap<String, PlayerInRoom>();
+	public final Roomlist roomList = new Roomlist();
+	public final MessageLog lobbyChatlog;
+	public final MessageLog roomChatlog;
+	public final ObservableTreeMap<String, TeamInGame> roomTeamlist = new ObservableTreeMap<String, TeamInGame>();
+	private final Map<String, Team> roomRequestedTeams = new TreeMap<String, Team>();
+	
+	private final List<GameMessageListener> gameMessageListeners = new LinkedList<GameMessageListener>();
+	private final List<RunGameListener> runGameListeners = new LinkedList<RunGameListener>();
+	
+	public Netplay(Context appContext) {
+		this.appContext = appContext;
+		broadcastManager = LocalBroadcastManager.getInstance(appContext);
+		lobbyChatlog = new MessageLog(appContext);
+		roomChatlog = new MessageLog(appContext);
+	}
+	
+	public RoomStateManager getRoomStateManager() {
+		return netRoomState;
+	}
+	
+	private void clearLobbyState() {
+		lobbyPlayerlist.clear();
+		roomList.clear();
+		lobbyChatlog.clear();
+	}
+	
+	private void initRoomState(boolean chief) {
+		roomChatlog.clear();
+		roomPlayerlist.clear();
+		roomTeamlist.clear();
+		roomRequestedTeams.clear();
+		
+		try {
+			netRoomState.setChief(chief);
+			netRoomState.setGameStyle(GameConfig.DEFAULT_STYLE);
+			List<Scheme> schemes = Schemes.loadBuiltinSchemes(appContext);
+			netRoomState.setScheme(schemes.get(Schemes.toNameList(schemes).indexOf(GameConfig.DEFAULT_SCHEME)));
+			netRoomState.setMapRecipe(MapRecipe.makeRandomMap(0, MapRecipe.makeRandomSeed(), GameConfig.DEFAULT_THEME));
+			List<Weaponset> weaponsets = Weaponsets.loadBuiltinWeaponsets(appContext);
+			netRoomState.setWeaponset(weaponsets.get(Weaponsets.toNameList(weaponsets).indexOf(GameConfig.DEFAULT_WEAPONSET)));
+			netRoomState.sendFullConfig();
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	public void registerGameMessageListener(GameMessageListener listener) {
+		gameMessageListeners.add(listener);
+	}
+	
+	public void unregisterGameMessageListener(GameMessageListener listener) {
+		gameMessageListeners.remove(listener);
+	}
+	
+	public void registerRunGameListener(RunGameListener listener) {
+		runGameListeners.add(listener);
+	}
+	
+	public void unregisterRunGameListener(RunGameListener listener) {
+		runGameListeners.remove(listener);
+	}
+	
+	public void connectToDefaultServer(String playerName) {
+		connect(playerName, DEFAULT_SERVER, DEFAULT_PORT);
+	}
+	
+	/**
+	 * Establish a new connection. Only call if the current state is NOT_CONNECTED.
+	 * 
+	 * The state will switch to CONNECTING immediately. After that, it can asynchronously change to any other state.
+	 * State changes are indicated by broadcasts. In particular, if an error occurs while trying to connect, the state
+	 * will change back to NOT_CONNECTED and an ACTION_DISCONNECTED broadcast is sent.
+	 */
+	public void connect(String name, String host, int port) {
+		playerName = name;
+		if(state != State.NOT_CONNECTED) {
+			throw new IllegalStateException("Attempt to start a new connection while the old one was still running.");
+		}
+		
+		clearLobbyState();
+		changeState(State.CONNECTING);
+		connection = ThreadedNetConnection.startConnection(appContext, fromNetHandler, name, host, port);
+		connection.setFastTickRate(true);
+	}
+	
+	public void sendNick(String nick) {
+		playerName = nick;
+		sendToNet(MSG_SEND_NICK, nick);
+	}
+	public void sendPassword(String password) { sendToNet(MSG_SEND_PASSWORD, password); }
+	public void sendQuit(String message) { sendToNet(MSG_SEND_QUIT, message); }
+	public void sendRoomlistRequest() { sendToNet(MSG_SEND_ROOMLIST_REQUEST); }
+	public void sendPlayerInfoQuery(String name) { sendToNet(MSG_SEND_PLAYER_INFO_REQUEST, name); }
+	public void sendChat(String s) { sendToNet(MSG_SEND_CHAT, s); }
+	public void sendTeamChat(String s) { sendToNet(MSG_SEND_TEAMCHAT, s); }
+	public void sendFollowPlayer(String nick) { sendToNet(MSG_SEND_FOLLOW_PLAYER, nick); }
+	public void sendJoinRoom(String name) { sendToNet(MSG_SEND_JOIN_ROOM, name); }
+	public void sendCreateRoom(String name) { sendToNet(MSG_SEND_CREATE_ROOM, name); }
+	public void sendLeaveRoom(String message) { sendToNet(MSG_SEND_LEAVE_ROOM, message); }
+	public void sendKick(String player) { sendToNet(MSG_SEND_KICK, player); }
+	public void sendAddTeam(Team newTeam) {
+		roomRequestedTeams.put(newTeam.name, newTeam);
+		sendToNet(MSG_SEND_ADD_TEAM, newTeam);
+	}
+	public void sendRemoveTeam(String teamName) { sendToNet(MSG_SEND_REMOVE_TEAM, teamName); }
+	public void sendTeamColorIndex(String teamName, int colorIndex) { sendToNet(MSG_SEND_TEAM_COLOR_INDEX, colorIndex, teamName); }
+	public void sendTeamHogCount(String teamName, int hogCount) { sendToNet(MSG_SEND_TEAM_HOG_COUNT, hogCount, teamName); }
+	public void sendEngineMessage(byte[] engineMessage) { sendToNet(MSG_SEND_ENGINE_MESSAGE, engineMessage); }
+	public void sendRoundFinished(boolean withoutError) { sendToNet(MSG_SEND_ROUND_FINISHED, Boolean.valueOf(withoutError)); }
+	public void sendToggleReady() { sendToNet(MSG_SEND_TOGGLE_READY); }
+	
+	public void disconnect() { sendToNet(MSG_DISCONNECT, "User Quit"); }
+	
+	private static Netplay instance;
+	
+	/**
+	 * Retrieve the single app-wide instance of the netplay interface, creating it if it
+	 * does not exist yet.
+	 * 
+	 * @param applicationContext
+	 * @return
+	 */
+	public static Netplay getAppInstance(Context applicationContext) {
+		if(instance == null) {
+			// We'll just do it here and never quit it again...
+			if(Flib.INSTANCE.flib_init() != 0) {
+				throw new RuntimeException("Unable to start frontlib");
+			}
+			instance = new Netplay(applicationContext);
+		}
+		return instance;
+	}
+
+	public State getState() {
+		return state;
+	}
+	
+	private void changeState(State newState) {
+		if(newState != state) {
+			state = newState;
+			broadcastManager.sendBroadcastSync(new Intent(ACTION_STATE_CHANGED));
+		}
+	}
+	
+	public boolean isChief() {
+		if(netRoomState != null) {
+			return netRoomState.chief;
+		} else {
+			return false;
+		}
+	}
+	
+	public String getPlayerName() {
+		return playerName;
+	}
+	
+	boolean sendToNet(ToNetMsgType what) {
+		return sendToNet(what, 0, null);
+	}
+	
+	boolean sendToNet(ToNetMsgType what, Object obj) {
+		return sendToNet(what, 0, obj);
+	}
+	
+	boolean sendToNet(ToNetMsgType what, int arg1, Object obj) {
+		if(connection != null) {
+			Handler handler = connection.toNetHandler;
+			return handler.sendMessage(handler.obtainMessage(what.ordinal(), arg1, 0, obj));
+		} else {
+			return false;
+		}
+	}
+	
+	private MessageLog getCurrentLog() {
+		if(state == State.ROOM || state == State.INGAME) {
+			return roomChatlog;
+		} else {
+			return lobbyChatlog;
+		}
+	}
+	
+	public static enum FromNetMsgType {
+		MSG_LOBBY_JOIN,
+		MSG_LOBBY_LEAVE,
+		MSG_ROOM_JOIN,
+		MSG_ROOM_LEAVE,
+		MSG_CHAT,
+		MSG_MESSAGE,
+		MSG_ROOM_ADD,
+		MSG_ROOM_UPDATE,
+		MSG_ROOM_DELETE,
+		MSG_ROOMLIST,
+		MSG_CONNECTED,
+		MSG_DISCONNECTED,
+		MSG_PASSWORD_REQUEST,
+		MSG_ENTER_ROOM_FROM_LOBBY,
+		MSG_LEAVE_ROOM,
+		MSG_READYSTATE,
+		MSG_TEAM_ADDED,
+		MSG_TEAM_DELETED,
+		MSG_TEAM_ACCEPTED,
+		MSG_TEAM_COLOR_CHANGED,
+		MSG_HOG_COUNT_CHANGED,
+		MSG_ENGINE_MESSAGE,
+		MSG_RUN_GAME,
+		MSG_SCHEME_CHANGED,
+		MSG_MAP_CHANGED,
+		MSG_ROOM_CHIEF_STATUS_CHANGED,
+		MSG_SCRIPT_CHANGED,
+		MSG_WEAPONSET_CHANGED;
+		
+		static final List<FromNetMsgType> values = Collections.unmodifiableList(Arrays.asList(FromNetMsgType.values()));
+	}
+	
+	/**
+	 * Processes messages from the networking system. Always runs on the main thread.
+	 */
+	@SuppressLint("HandlerLeak")
+	final class FromNetHandler extends Handler {
+		public FromNetHandler() {
+			super(Looper.getMainLooper());
+		}
+		
+		@SuppressWarnings("unchecked")
+		@Override
+		public void handleMessage(Message msg) {
+			switch(FromNetMsgType.values.get(msg.what)) {
+			case MSG_LOBBY_JOIN: {
+				String name = (String)msg.obj;
+				lobbyPlayerlist.put(name, new Player(name, false, false));
+				lobbyChatlog.appendPlayerJoin(name);
+				break;
+			}
+			case MSG_LOBBY_LEAVE: {
+				Pair<String, String> args = (Pair<String, String>)msg.obj;
+				lobbyPlayerlist.remove(args.first);
+				lobbyChatlog.appendPlayerLeave(args.first, args.second);
+				break;
+			}
+			case MSG_ROOM_JOIN: {
+				String name = (String)msg.obj;
+				Player p = lobbyPlayerlist.get(name);
+				if(p==null) {
+					Log.w("Netplay", "Unknown player joined room: "+name);
+					p = new Player(name, false, false);
+				}
+				roomPlayerlist.put(name, new PlayerInRoom(p, false));
+				roomChatlog.appendPlayerJoin(name);
+				break;
+			}
+			case MSG_ROOM_LEAVE: {
+				Pair<String, String> args = (Pair<String, String>)msg.obj;
+				roomPlayerlist.remove(args.first);
+				roomChatlog.appendPlayerLeave(args.first, args.second);
+				break;
+			}
+			case MSG_CHAT: {
+				Pair<String, String> args = (Pair<String, String>)msg.obj;
+				getCurrentLog().appendChat(args.first, args.second);
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onChatMessage(args.first, args.second);
+				}
+				break;
+			}
+			case MSG_MESSAGE: {
+				getCurrentLog().appendMessage(msg.arg1, (String)msg.obj);
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onMessage(1, (String)msg.obj);
+				}
+				break;
+			}
+			case MSG_ROOM_ADD: {
+				Room room = (Room)msg.obj;
+				roomList.addRoomWithNewId(room);
+				break;
+			}
+			case MSG_ROOM_UPDATE: {
+				Pair<String, Room> args = (Pair<String, Room>)msg.obj;
+				roomList.updateRoom(args.first, args.second);
+				break;
+			}
+			case MSG_ROOM_DELETE: {
+				roomList.remove((String)msg.obj);
+				break;
+			}
+			case MSG_ROOMLIST: {
+				Room[] rooms = (Room[])msg.obj;
+				roomList.updateList(rooms);
+				break;
+			}
+			case MSG_CONNECTED: {
+				playerName = (String)msg.obj;
+				changeState(State.LOBBY);
+				broadcastManager.sendBroadcast(new Intent(ACTION_CONNECTED));
+				break;
+			}
+			case MSG_DISCONNECTED: {
+				Pair<Boolean, String> args = (Pair<Boolean, String>)msg.obj;
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onNetDisconnected();
+				}
+				changeState(State.NOT_CONNECTED);
+				connection = null;
+				Intent intent = new Intent(ACTION_DISCONNECTED);
+				intent.putExtra(EXTRA_HAS_ERROR, args.first);
+				intent.putExtra(EXTRA_MESSAGE, args.second);
+				broadcastManager.sendBroadcastSync(intent);
+				break;
+			}
+			case MSG_PASSWORD_REQUEST: {
+				Intent intent = new Intent(ACTION_PASSWORD_REQUESTED);
+				intent.putExtra(EXTRA_PLAYERNAME, (String)msg.obj);
+				broadcastManager.sendBroadcast(intent);
+				break;
+			}
+			case MSG_ENTER_ROOM_FROM_LOBBY: {
+				initRoomState((Boolean)msg.obj);
+				changeState(State.ROOM);
+				Intent intent = new Intent(ACTION_ENTERED_ROOM_FROM_LOBBY);
+				broadcastManager.sendBroadcastSync(intent);
+				break;
+			}
+			case MSG_LEAVE_ROOM: {
+				changeState(State.LOBBY);
+				Intent intent = new Intent(ACTION_LEFT_ROOM);
+				intent.putExtra(EXTRA_MESSAGE, (String)msg.obj);
+				intent.putExtra(EXTRA_REASON, msg.arg1);
+				broadcastManager.sendBroadcastSync(intent);
+				break;
+			}
+			case MSG_READYSTATE: {
+				Pair<String, Boolean> args = (Pair<String, Boolean>)msg.obj;
+				String name = args.first;
+				Boolean newReadyState = args.second;
+				PlayerInRoom oldEntry = roomPlayerlist.get(name);
+				if(oldEntry==null) {
+					Log.e("Netplay", "Setting readystate for unknown player "+name);
+				} else {
+					roomPlayerlist.put(name, new PlayerInRoom(oldEntry.player, newReadyState));
+				}
+				break;
+			}
+			case MSG_TEAM_ADDED: {
+				Team newTeam = (Team)msg.obj;
+				int colorIndex = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values());
+				TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, true);
+				TeamInGame tig = new TeamInGame(newTeam, attrs);
+				roomTeamlist.put(newTeam.name, tig);
+				if(isChief()) {
+					sendTeamColorIndex(newTeam.name, attrs.colorIndex);
+					sendTeamHogCount(newTeam.name, attrs.hogCount);
+				}
+				break;
+			}
+			case MSG_TEAM_DELETED: {
+				roomTeamlist.remove((String)msg.obj);
+				break;
+			}
+			case MSG_TEAM_ACCEPTED: {
+				Team requestedTeam = roomRequestedTeams.remove(msg.obj);
+				if(requestedTeam!=null) {
+					int colorIndex = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values());
+					TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
+					TeamInGame tig = new TeamInGame(requestedTeam, attrs);
+					roomTeamlist.put(requestedTeam.name, tig);
+					if(isChief()) {
+						sendTeamColorIndex(requestedTeam.name, attrs.colorIndex);
+						sendTeamHogCount(requestedTeam.name, attrs.hogCount);
+					}
+				} else {
+					Log.e("Netplay", "Got accepted message for team that was never requested.");
+				}
+				break;
+			}
+			case MSG_TEAM_COLOR_CHANGED: {
+				TeamInGame oldEntry = roomTeamlist.get((String)msg.obj);
+				if(oldEntry != null) {
+					TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withColorIndex(msg.arg1);
+					roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs));
+				} else {
+					Log.e("Netplay", "Color update for unknown team "+msg.obj);
+				}
+				break;
+			}
+			case MSG_HOG_COUNT_CHANGED: {
+				TeamInGame oldEntry = roomTeamlist.get((String)msg.obj);
+				if(oldEntry != null) {
+					TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withHogCount(msg.arg1);
+					roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs));
+				} else {
+					Log.e("Netplay", "Hog count update for unknown team "+msg.obj);
+				}
+				break;
+			}
+			case MSG_ENGINE_MESSAGE: {
+				byte[] em = (byte[])msg.obj;
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onEngineMessage(em);
+				}
+				break;
+			}
+			case MSG_RUN_GAME: {
+				GameConfig config = (GameConfig)msg.obj;
+				for(RunGameListener listener : runGameListeners) {
+					listener.runGame(config);
+				}
+				break;
+			}
+			case MSG_MAP_CHANGED: {
+				netRoomState.setMapRecipe((MapRecipe)msg.obj);
+				break;
+			}
+			case MSG_ROOM_CHIEF_STATUS_CHANGED: {
+				netRoomState.setChief((Boolean)msg.obj);
+				break;
+			}
+			case MSG_SCHEME_CHANGED: {
+				netRoomState.setScheme((Scheme)msg.obj);
+				break;
+			}
+			case MSG_SCRIPT_CHANGED: {
+				netRoomState.setGameStyle((String)msg.obj);
+				break;
+			}
+			case MSG_WEAPONSET_CHANGED: {
+				netRoomState.setWeaponset((Weaponset)msg.obj);
+				break;
+			}
+			default: {
+				Log.e("FromNetHandler", "Unknown message type: "+msg.what);
+				break;
+			}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Roomlist.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,39 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.RoomWithId;
+import org.hedgewars.hedgeroid.util.ObservableTreeMap;
+
+public class Roomlist extends ObservableTreeMap<String, RoomWithId> {
+	private long nextId = 1;
+	
+	public void updateList(Room[] newRooms) {
+		Map<String, RoomWithId> newMap = new TreeMap<String, RoomWithId>();
+		for(Room room : newRooms) {
+			RoomWithId oldEntry = get(room.name);
+			if(oldEntry == null) {
+				newMap.put(room.name, new RoomWithId(room, nextId++));
+			} else {
+				newMap.put(room.name, new RoomWithId(room, oldEntry.id));
+			}
+		}
+		replaceContent(newMap);
+	}
+	
+	public void addRoomWithNewId(Room room) {
+		put(room.name, new RoomWithId(room, nextId++));
+	}
+	
+	public void updateRoom(String name, Room room) {
+		RoomWithId oldEntry = get(name);
+		if(oldEntry == null) {
+			addRoomWithNewId(room);
+		} else {
+			remove(name);
+			put(room.name, new RoomWithId(room, oldEntry.id));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RunGameListener.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,7 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+
+public interface RunGameListener {
+	void runGame(GameConfig config);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ThreadedNetConnection.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,566 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import static org.hedgewars.hedgeroid.netplay.Netplay.FromNetMsgType.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.BoolCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.BytesCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.IntStrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapIntCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapRecipePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.NetconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomArrayPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomListCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.SchemeCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.SchemePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrIntCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrRoomCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrStrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.WeaponsetCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.WeaponsetPtr;
+import org.hedgewars.hedgeroid.netplay.Netplay.FromNetHandler;
+import org.hedgewars.hedgeroid.netplay.Netplay.FromNetMsgType;
+import org.hedgewars.hedgeroid.util.FileUtils;
+import org.hedgewars.hedgeroid.util.TickHandler;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Pair;
+
+import com.sun.jna.Memory;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+
+/**
+ * This class handles the actual communication with the networking library, running on a separate thread.
+ * 
+ * In order to process net messages, this class regularly runs a tick() function on the frontlib. This
+ * usually happens several times per second, but it can be slowed down a lot if no fast reaction to
+ * events is required (e.g. to conserve battery if the application is in the background).
+ */
+class ThreadedNetConnection {
+	private static final long TICK_INTERVAL_FAST = 100;
+	private static final long TICK_INTERVAL_SLOW = 5000;
+	private static final Frontlib FLIB = Flib.INSTANCE;
+	
+	public final ToNetHandler toNetHandler;
+	
+	private final Context appContext;
+	private final FromNetHandler fromNetHandler;
+	private final TickHandler tickHandler;
+
+	/**
+	 * conn can only be null while connecting (the first thing in the thread), and directly after disconnecting,
+	 * in the same message (the looper is shut down on disconnect, so there will be no messages after that).
+	 */
+	private NetconnPtr conn;
+	private String playerName;
+	
+	private ThreadedNetConnection(Context appContext, FromNetHandler fromNetHandler) {
+		this.appContext = appContext;
+		this.fromNetHandler = fromNetHandler;
+		
+		HandlerThread thread = new HandlerThread("NetThread");
+		thread.start();
+		toNetHandler = new ToNetHandler(thread.getLooper());
+		tickHandler = new TickHandler(thread.getLooper(), TICK_INTERVAL_FAST, tickCb);
+	}
+	
+	private void connect(final String name, final String host, final int port) {
+		toNetHandler.post(new Runnable() {
+			public void run() {
+				playerName = name == null ? "Player" : name;
+				File dataPath;
+				try {
+					dataPath = FileUtils.getDataPathFile(appContext);
+				} catch (FileNotFoundException e) {
+					shutdown(true, appContext.getString(R.string.sdcard_not_mounted));
+					return;
+				}
+				conn = FLIB.flib_netconn_create(playerName, dataPath.getAbsolutePath()+"/", host, port);
+				if(conn == null) {
+					shutdown(true, appContext.getString(R.string.error_connection_failed));
+					return;
+				}
+
+				//FLIB.flib_netconn_onAdminAccess(conn, adminAccessCb, null)
+				FLIB.flib_netconn_onCfgScheme(conn, cfgSchemeCb, null);
+				FLIB.flib_netconn_onChat(conn, chatCb, null);
+				FLIB.flib_netconn_onConnected(conn, connectedCb, null);
+				FLIB.flib_netconn_onDisconnected(conn, disconnectCb, null);
+				FLIB.flib_netconn_onEngineMessage(conn, engineMessageCb, null);
+				FLIB.flib_netconn_onEnterRoom(conn, enterRoomCb, null);
+				FLIB.flib_netconn_onHogCountChanged(conn, hogCountChangedCb, null);
+				FLIB.flib_netconn_onLeaveRoom(conn, leaveRoomCb, null);
+				FLIB.flib_netconn_onLobbyJoin(conn, lobbyJoinCb, null);
+				FLIB.flib_netconn_onLobbyLeave(conn, lobbyLeaveCb, null);
+				FLIB.flib_netconn_onMapChanged(conn, mapChangedCb, null);
+				FLIB.flib_netconn_onMessage(conn, messageCb, null);
+				FLIB.flib_netconn_onPasswordRequest(conn, passwordRequestCb, null);
+				FLIB.flib_netconn_onReadyState(conn, readyStateCb, null);
+				FLIB.flib_netconn_onRoomAdd(conn, roomAddCb, null);
+				FLIB.flib_netconn_onRoomChiefStatus(conn, roomChiefStatusCb, null);
+				FLIB.flib_netconn_onRoomDelete(conn, roomDeleteCb, null);
+				FLIB.flib_netconn_onRoomJoin(conn, roomJoinCb, null);
+				FLIB.flib_netconn_onRoomLeave(conn, roomLeaveCb, null);
+				FLIB.flib_netconn_onRoomlist(conn, roomlistCb, null);
+				FLIB.flib_netconn_onRoomUpdate(conn, roomUpdateCb, null);
+				FLIB.flib_netconn_onRunGame(conn, runGameCb, null);
+				FLIB.flib_netconn_onScriptChanged(conn, scriptChangedCb, null);
+				// FLIB.flib_netconn_onServerVar(conn, serverVarCb, null);
+				FLIB.flib_netconn_onTeamAccepted(conn, teamAcceptedCb, null);
+				FLIB.flib_netconn_onTeamAdd(conn, teamAddedCb, null);
+				FLIB.flib_netconn_onTeamColorChanged(conn, teamColorChangedCb, null);
+				FLIB.flib_netconn_onTeamDelete(conn, teamDeletedCb, null);
+				FLIB.flib_netconn_onWeaponsetChanged(conn, weaponsetChangedCb, null);
+				
+				tickHandler.start();
+			}
+		});
+	}
+	
+	public static ThreadedNetConnection startConnection(Context appContext, FromNetHandler fromNetHandler, String playerName, String host, int port) {
+		ThreadedNetConnection result = new ThreadedNetConnection(appContext, fromNetHandler);
+		result.connect(playerName, host, port);
+		return result;
+	}
+	
+	public void setFastTickRate(boolean fastTickRate) {
+		tickHandler.setInterval(fastTickRate ? TICK_INTERVAL_FAST : TICK_INTERVAL_SLOW);
+	}
+	
+	private final Runnable tickCb = new Runnable() {
+		public void run() {
+			FLIB.flib_netconn_tick(conn);
+		}
+	};
+	
+	private final SchemeCallback cfgSchemeCb = new SchemeCallback() {
+		public void callback(Pointer context, SchemePtr schemePtr) {
+			sendFromNet(MSG_SCHEME_CHANGED, schemePtr.deref());
+		}
+	};
+	
+	private final MapIntCallback mapChangedCb = new MapIntCallback() {
+		public void callback(Pointer context, MapRecipePtr mapPtr, int updateType) {
+			sendFromNet(MSG_MAP_CHANGED, updateType, mapPtr.deref());
+		}
+	};
+	
+	private final BoolCallback roomChiefStatusCb = new BoolCallback() {
+		public void callback(Pointer context, boolean chief) {
+			sendFromNet(MSG_ROOM_CHIEF_STATUS_CHANGED, chief);
+		}
+	};
+	
+	private final StrCallback scriptChangedCb = new StrCallback() {
+		public void callback(Pointer context, String script) {
+			sendFromNet(MSG_SCRIPT_CHANGED, script);
+		}
+	};
+	
+	private final WeaponsetCallback weaponsetChangedCb = new WeaponsetCallback() {
+		public void callback(Pointer context, WeaponsetPtr weaponsetPtr) {
+			sendFromNet(MSG_WEAPONSET_CHANGED, weaponsetPtr.deref());				
+		}
+	};
+	
+	private final StrCallback lobbyJoinCb = new StrCallback() {
+		public void callback(Pointer context, String name) {
+			sendFromNet(MSG_LOBBY_JOIN, name);
+		}
+	};
+	
+	private final StrStrCallback lobbyLeaveCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			sendFromNet(MSG_LOBBY_LEAVE, Pair.create(name, msg));
+		}
+	};
+	
+	private final StrCallback roomJoinCb = new StrCallback() {
+		public void callback(Pointer context, String name) {
+			sendFromNet(MSG_ROOM_JOIN, name);
+		}
+	};
+	private final StrStrCallback roomLeaveCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String message) {
+			sendFromNet(MSG_ROOM_LEAVE, Pair.create(name, message));
+		}
+	};
+	private final StrStrCallback chatCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			sendFromNet(MSG_CHAT, Pair.create(name, msg));
+		}
+	};
+	
+	private final IntStrCallback messageCb = new IntStrCallback() {
+		public void callback(Pointer context, int type, String msg) {
+			sendFromNet(MSG_MESSAGE, type, msg);
+		}
+	};
+	
+	private final RoomCallback roomAddCb = new RoomCallback() {
+		public void callback(Pointer context, RoomPtr roomPtr) {
+			sendFromNet(MSG_ROOM_ADD, roomPtr.deref());
+		}
+	};
+	
+	private final StrRoomCallback roomUpdateCb = new StrRoomCallback() {
+		public void callback(Pointer context, String name, RoomPtr roomPtr) {
+			sendFromNet(MSG_ROOM_UPDATE, Pair.create(name, roomPtr.deref()));
+		}
+	};
+	
+	private final StrCallback roomDeleteCb = new StrCallback() {
+		public void callback(Pointer context, final String name) {
+			sendFromNet(MSG_ROOM_DELETE, name);
+		}
+	};
+	
+	private final RoomListCallback roomlistCb = new RoomListCallback() {
+		public void callback(Pointer context, RoomArrayPtr arg1, int count) {
+			sendFromNet(MSG_ROOMLIST, arg1.getRooms(count));
+		}
+	};
+	
+	private final VoidCallback connectedCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			FLIB.flib_netconn_send_request_roomlist(conn);
+			playerName = FLIB.flib_netconn_get_playername(conn);
+			sendFromNet(MSG_CONNECTED, playerName);
+		}
+	};
+	
+	private final StrCallback passwordRequestCb = new StrCallback() {
+		public void callback(Pointer context, String nickname) {
+			sendFromNet(MSG_PASSWORD_REQUEST, playerName);
+		}
+	};
+	
+	private final BoolCallback enterRoomCb = new BoolCallback() {
+		public void callback(Pointer context, boolean isChief) {
+			sendFromNet(MSG_ENTER_ROOM_FROM_LOBBY, isChief);
+		}
+	};
+	
+	private final IntStrCallback leaveRoomCb = new IntStrCallback() {
+		public void callback(Pointer context, int reason, String message) {
+			sendFromNet(MSG_LEAVE_ROOM, reason, message);
+		}
+	};
+	
+	private final StrBoolCallback readyStateCb = new StrBoolCallback() {
+		public void callback(Pointer context, String player, boolean ready) {
+			sendFromNet(MSG_READYSTATE, Pair.create(player, ready));
+		}
+	};
+	
+	private final TeamCallback teamAddedCb = new TeamCallback() {
+		public void callback(Pointer context, TeamPtr team) {
+			sendFromNet(MSG_TEAM_ADDED, team.deref().team);
+		}
+	};
+	
+	private final StrCallback teamDeletedCb = new StrCallback() {
+		public void callback(Pointer context, String teamName) {
+			sendFromNet(MSG_TEAM_DELETED, teamName);
+		}
+	};
+	
+	private final StrCallback teamAcceptedCb = new StrCallback() {
+		public void callback(Pointer context, String teamName) {
+			sendFromNet(MSG_TEAM_ACCEPTED, teamName);
+		}
+	};
+	
+	private final StrIntCallback teamColorChangedCb = new StrIntCallback() {
+		public void callback(Pointer context, String teamName, int colorIndex) {
+			sendFromNet(MSG_TEAM_COLOR_CHANGED, colorIndex, teamName);
+		}
+	};
+	
+	private final StrIntCallback hogCountChangedCb = new StrIntCallback() {
+		public void callback(Pointer context, String teamName, int hogCount) {
+			sendFromNet(MSG_HOG_COUNT_CHANGED, hogCount, teamName);
+		}
+	};
+	
+	private final BytesCallback engineMessageCb = new BytesCallback() {
+		public void callback(Pointer context, Pointer buffer, NativeLong size) {
+			sendFromNet(MSG_ENGINE_MESSAGE, buffer.getByteArray(0, size.intValue()));
+		}
+	};
+	
+	private final VoidCallback runGameCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			GameSetupPtr configPtr = FLIB.flib_netconn_create_gamesetup(conn);
+			sendFromNet(MSG_RUN_GAME, configPtr.deref());
+			FLIB.flib_gamesetup_destroy(configPtr);
+		}
+	};
+	
+	private void shutdown(boolean error, String message) {
+		if(conn != null) {
+			FLIB.flib_netconn_destroy(conn);
+			conn = null;
+		}
+		tickHandler.stop();
+		toNetHandler.getLooper().quit();
+		sendFromNet(MSG_DISCONNECTED, Pair.create(error, message));
+	}
+	
+	private final IntStrCallback disconnectCb = new IntStrCallback() {
+		public void callback(Pointer context, int reason, String message) {
+			Boolean error = reason != Frontlib.NETCONN_DISCONNECT_NORMAL;
+			String messageForUser = createDisconnectUserMessage(appContext.getResources(), reason, message);
+			shutdown(error, messageForUser);
+		}
+	};
+	
+	private static String createDisconnectUserMessage(Resources res, int reason, String message) {
+		switch(reason) {
+		case Frontlib.NETCONN_DISCONNECT_AUTH_FAILED:
+			return res.getString(R.string.error_auth_failed);
+		case Frontlib.NETCONN_DISCONNECT_CONNLOST:
+			return res.getString(R.string.error_connection_lost);
+		case Frontlib.NETCONN_DISCONNECT_INTERNAL_ERROR:
+			return res.getString(R.string.error_unexpected, message);
+		case Frontlib.NETCONN_DISCONNECT_SERVER_TOO_OLD:
+			return res.getString(R.string.error_server_too_old);
+		default:
+			return message;
+		}
+	}
+	
+	private boolean sendFromNet(FromNetMsgType what, Object obj) {
+		return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what.ordinal(), obj));
+	}
+	
+	private boolean sendFromNet(FromNetMsgType what, int arg1, Object obj) {
+		return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what.ordinal(), arg1, 0, obj));
+	}
+	
+	static enum ToNetMsgType {
+		MSG_SEND_NICK,
+		MSG_SEND_PASSWORD,
+		MSG_SEND_QUIT,
+		MSG_SEND_ROOMLIST_REQUEST,
+		MSG_SEND_PLAYER_INFO_REQUEST,
+		MSG_SEND_CHAT,
+		MSG_SEND_TEAMCHAT,
+		MSG_SEND_FOLLOW_PLAYER,
+		MSG_SEND_JOIN_ROOM,
+		MSG_SEND_CREATE_ROOM,
+		MSG_SEND_LEAVE_ROOM,
+		MSG_SEND_KICK,
+		MSG_SEND_ADD_TEAM,
+		MSG_SEND_REMOVE_TEAM,
+		MSG_DISCONNECT,
+		MSG_SEND_TEAM_COLOR_INDEX,
+		MSG_SEND_TEAM_HOG_COUNT,
+		MSG_SEND_ENGINE_MESSAGE,
+		MSG_SEND_ROUND_FINISHED,
+		MSG_SEND_TOGGLE_READY,
+		MSG_SEND_WEAPONSET,
+		MSG_SEND_MAP,
+		MSG_SEND_MAP_NAME,
+		MSG_SEND_MAP_GENERATOR,
+		MSG_SEND_MAP_TEMPLATE,
+		MSG_SEND_MAZE_SIZE,
+		MSG_SEND_MAP_SEED,
+		MSG_SEND_MAP_THEME,
+		MSG_SEND_MAP_DRAWDATA,
+		MSG_SEND_GAMESTYLE,
+		MSG_SEND_SCHEME;
+		
+		static final List<ThreadedNetConnection.ToNetMsgType> values = Collections.unmodifiableList(Arrays.asList(ToNetMsgType.values()));
+	}
+	
+	/**
+	 * Processes messages to the networking system. Runs on a non-main thread.
+	 */
+	@SuppressLint("HandlerLeak")
+	public final class ToNetHandler extends Handler {
+		
+		public ToNetHandler(Looper looper) {
+			super(looper);
+		}
+		
+		@Override
+		public void handleMessage(Message msg) {
+			switch(ToNetMsgType.values.get(msg.what)) {
+			case MSG_SEND_NICK: {
+				FLIB.flib_netconn_send_nick(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_PASSWORD: {
+				FLIB.flib_netconn_send_password(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_QUIT: {
+				FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_ROOMLIST_REQUEST: {
+				FLIB.flib_netconn_send_request_roomlist(conn);
+				break;
+			}
+			case MSG_SEND_PLAYER_INFO_REQUEST: {
+				FLIB.flib_netconn_send_playerInfo(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_CHAT: {
+				if(FLIB.flib_netconn_send_chat(conn, (String)msg.obj) == 0) {
+					sendFromNet(MSG_CHAT, Pair.create(playerName, (String)msg.obj));
+				}
+				break;
+			}
+			case MSG_SEND_TEAMCHAT: {
+				FLIB.flib_netconn_send_teamchat(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_FOLLOW_PLAYER: {
+				FLIB.flib_netconn_send_playerFollow(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_JOIN_ROOM: {
+				FLIB.flib_netconn_send_joinRoom(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_CREATE_ROOM: {
+				FLIB.flib_netconn_send_createRoom(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_LEAVE_ROOM: {
+				if(FLIB.flib_netconn_send_leaveRoom(conn, (String)msg.obj) == 0) {
+					sendFromNet(MSG_LEAVE_ROOM, -1, "");
+				}
+				break;
+			}
+			case MSG_SEND_KICK: {
+				FLIB.flib_netconn_send_kick(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_ADD_TEAM: {
+				FLIB.flib_netconn_send_addTeam(conn, TeamPtr.createJavaOwned((Team)msg.obj));
+				break;
+			}
+			case MSG_SEND_REMOVE_TEAM: {
+				if(FLIB.flib_netconn_send_removeTeam(conn, (String)msg.obj)==0) {
+					sendFromNet(MSG_TEAM_DELETED, msg.obj);
+				}
+				break;
+			}
+			case MSG_DISCONNECT: {
+				FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
+				shutdown(false, "User quit");
+				break;
+			}
+			case MSG_SEND_TEAM_COLOR_INDEX: {
+				if(FLIB.flib_netconn_send_teamColor(conn, (String)msg.obj, msg.arg1)==0) {
+					sendFromNet(MSG_TEAM_COLOR_CHANGED, msg.arg1, msg.obj);
+				}
+				break;
+			}
+			case MSG_SEND_TEAM_HOG_COUNT: {
+				if(FLIB.flib_netconn_send_teamHogCount(conn, (String)msg.obj, msg.arg1)==0) {
+					sendFromNet(MSG_HOG_COUNT_CHANGED, msg.arg1, msg.obj);
+				}
+				break;
+			}
+			case MSG_SEND_ENGINE_MESSAGE: {
+				byte[] message = (byte[])msg.obj;
+				Memory mem = new Memory(message.length);
+				mem.write(0, message, 0, message.length);
+				FLIB.flib_netconn_send_engineMessage(conn, mem, new NativeLong(message.length));
+				break;
+			}
+			case MSG_SEND_ROUND_FINISHED: {
+				FLIB.flib_netconn_send_roundfinished(conn, (Boolean)msg.obj);
+				break;
+			}
+			case MSG_SEND_TOGGLE_READY: {
+				FLIB.flib_netconn_send_toggleReady(conn);
+				break;
+			}
+			case MSG_SEND_WEAPONSET: {
+				FLIB.flib_netconn_send_weaponset(conn, WeaponsetPtr.createJavaOwned((Weaponset)msg.obj));
+				break;
+			}
+			case MSG_SEND_MAP: {
+				FLIB.flib_netconn_send_map(conn, MapRecipePtr.createJavaOwned((MapRecipe)msg.obj));
+				break;
+			}
+			case MSG_SEND_MAP_NAME: {
+				FLIB.flib_netconn_send_mapName(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_MAP_GENERATOR: {
+				FLIB.flib_netconn_send_mapGen(conn, msg.arg1);
+				break;
+			}
+			case MSG_SEND_MAP_TEMPLATE: {
+				FLIB.flib_netconn_send_mapTemplate(conn, msg.arg1);
+				break;
+			}
+			case MSG_SEND_MAZE_SIZE: {
+				FLIB.flib_netconn_send_mapMazeSize(conn, msg.arg1);
+				break;
+			}
+			case MSG_SEND_MAP_SEED: {
+				FLIB.flib_netconn_send_mapSeed(conn, (String) msg.obj);
+				break;
+			}
+			case MSG_SEND_MAP_THEME: {
+				FLIB.flib_netconn_send_mapTheme(conn, (String) msg.obj);
+				break;
+			}
+			case MSG_SEND_MAP_DRAWDATA: {
+				byte[] message = (byte[])msg.obj;
+				Memory mem = new Memory(message.length);
+				mem.write(0, message, 0, message.length);
+				FLIB.flib_netconn_send_mapDrawdata(conn, mem, new NativeLong(message.length));
+				break;
+			}
+			case MSG_SEND_GAMESTYLE: {
+				FLIB.flib_netconn_send_script(conn, (String) msg.obj);
+				break;
+			}
+			case MSG_SEND_SCHEME: {
+				FLIB.flib_netconn_send_scheme(conn, SchemePtr.createJavaOwned((Scheme) msg.obj));
+				break;
+			}
+			default: {
+				Log.e("ToNetHandler", "Unknown message type: "+msg.what);
+				break;
+			}
+			}
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/FileUtils.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,265 @@
+/*
+ * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
+ * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+
+package org.hedgewars.hedgeroid.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+public class FileUtils {
+	private static final String ROOT_DIR = "Data";
+	private static final String TAG = FileUtils.class.getSimpleName();
+
+	/**
+	 * @return true if the data path is currently available. However, it can vanish at any time so
+	 * normally you should just try to use it and rely on the exceptions.
+	 */
+	public static boolean isDataPathAvailable() {
+		return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
+	}
+	
+	/**
+	 * get the path to which we should download all the data files
+	 * @param c context 
+	 * @return The directory
+	 * @throws FileNotFoundException if external storage is not available at the moment
+	 */
+	public static File getCachePath(Context c) throws FileNotFoundException {
+		File cachePath = null;
+		if(Build.VERSION.SDK_INT < 8){//8 == Build.VERSION_CODES.FROYO
+			cachePath = PreFroyoSDCardDir.getDownloadPath(c);
+		} else {
+			cachePath = FroyoSDCardDir.getDownloadPath(c);
+		}
+		if(cachePath==null) {
+			throw new FileNotFoundException("External storage is currently unavailable");
+		} else {
+			return cachePath;
+		}
+	}
+
+	public static File getDataPathFile(Context c) throws FileNotFoundException {
+		return new File(getCachePath(c), ROOT_DIR);
+	}
+	
+	// TODO Several callers are unaware that this may fail, so it throws an RTE now.
+	// Should be handled better though.
+	@Deprecated
+	public static String getDataPath(Context c) {
+		try {
+			return getDataPathFile(c).getAbsolutePath()+"/";
+		} catch(FileNotFoundException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@TargetApi(8)
+	private static class FroyoSDCardDir{
+		public static File getDownloadPath(Context c){
+			return c.getExternalCacheDir();
+		}
+	}
+
+	private static class PreFroyoSDCardDir{
+		public static File getDownloadPath(Context c){
+			if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
+				File extStorageDir = Environment.getExternalStorageDirectory();
+				if(extStorageDir != null) {
+					return new File(extStorageDir, "Hedgewars");
+				}
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * Return a File array with all the files from dirName
+	 * @param c
+	 * @param dirName
+	 * @return
+	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "dirName" does not exist
+	 */
+	public static File[] getFilesFromRelativeDir(Context c, String dirName) throws FileNotFoundException {
+		File f = new File(getDataPathFile(c), dirName);
+
+		if(f.isDirectory()) {
+			return f.listFiles();
+		} else {
+			throw new FileNotFoundException("Directory "+dirName+" does not exist.");
+		}
+	}
+
+	/**
+	 * Checks if this directory has a file with suffix suffix
+	 * @param f - directory
+	 * @return
+	 */
+	public static boolean hasFileWithSuffix(File f, String suffix){
+		if(f.isDirectory()){
+			for(String s : f.list()){
+				if(s.endsWith(suffix)) return true;
+			}
+			return false;
+		}else{
+			return false;
+		}
+	}
+
+	/**
+	 * Gives back all dirs which contain a file with suffix fileSuffix
+	 * @param c
+	 * @param path
+	 * @param fileSuffix
+	 * @return
+	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "path" does not exist
+	 */
+	public static List<String> getDirsWithFileSuffix(Context c, String path, String fileSuffix) throws FileNotFoundException{
+		File[] files = getFilesFromRelativeDir(c,path);
+		ArrayList<String> ret = new ArrayList<String>();
+
+		for(File f : files){
+			if(hasFileWithSuffix(f, fileSuffix)) ret.add(f.getName());
+		}
+		return ret;
+	}
+
+	/**
+	 * Get all files from directory dir which have the given suffix
+	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "dir" does not exist
+	 */
+	public static ArrayList<String> getFileNamesFromDirWithSuffix(Context c, String dir, String suffix, boolean removeSuffix) throws FileNotFoundException{
+		File[] files = FileUtils.getFilesFromRelativeDir(c, dir);
+		ArrayList<String> ret = new ArrayList<String>();
+		for(File file : files){
+			String s = file.getName();
+			if(s.endsWith(suffix)){
+				if(removeSuffix) ret.add(s.substring(0, s.length()-suffix.length()));
+				else ret.add(s);
+			}
+		}
+		return ret;
+	}
+
+	/**
+	 * Close a resource (possibly null), ignoring any IOException.
+	 */
+	public static void closeQuietly(Closeable c) {
+		if(c!=null) {
+			try {
+				c.close();
+			} catch(IOException e) {
+				Log.w(TAG, e);
+			}
+		}
+	}
+	
+	/**
+	 * Write all data from the input stream to the file, creating or overwriting it.
+	 * The input stream will be closed.
+	 * 
+	 * @throws IOException
+	 */
+	public static void writeStreamToFile(InputStream is, File file) throws IOException {
+		OutputStream os = null;
+		byte[] buffer = new byte[8192];
+		try {
+			os = new FileOutputStream(file);
+			int size;
+			while((size=is.read(buffer)) != -1) {
+				os.write(buffer, 0, size);
+			}
+			os.close(); // Important to close this non-quietly, in case of exceptions when flushing
+		} finally {
+			FileUtils.closeQuietly(is);
+			FileUtils.closeQuietly(os);
+		}
+	}
+	
+	/**
+	 * Moves resources pointed to by sourceResId (from @res/raw/) to the app's private data directory
+	 * @param c
+	 * @param sourceResId
+	 * @param directory
+	 */
+	public static void resRawToFilesDir(Context c, int sourceResId, int targetFilenames, String directory) throws IOException {
+		File targetDir = new File(c.getFilesDir(), directory);
+		targetDir.mkdirs();
+
+		//Get an array with the resource files ID
+		Resources resources = c.getResources();
+		TypedArray ta = resources.obtainTypedArray(sourceResId);
+		TypedArray filenames = resources.obtainTypedArray(targetFilenames);
+		for(int i = 0; i < ta.length(); i++){
+			int resId =  ta.getResourceId(i, 0);
+			String fileName = filenames.getString(i);
+			File f = new File(targetDir, fileName);
+			writeStreamToFile(resources.openRawResource(resId), f);
+		}
+	}
+
+	public static String readToString(InputStream is) throws IOException {
+		try {
+			ByteArrayOutputStream os = new ByteArrayOutputStream();
+			byte[] buffer = new byte[8192];
+			int size;
+			while((size=is.read(buffer)) != -1) {
+				os.write(buffer, 0, size);
+			}
+			return new String(os.toByteArray());
+		} finally {
+			closeQuietly(is);
+		}
+	}
+	
+	private static final char[] badFilenameChars = new char[] { '/', '\\', ':', '*', '?', '\"', '<', '>', '|', '.', '\0' };
+	
+	/**
+	 * Modify the given String so that it can be used as part of a filename
+	 * without causing problems from illegal/special characters.
+	 * 
+	 * The result should be similar to the input, but isn't necessarily
+	 * reversible.
+	 */
+	public static String replaceBadChars(String name) {
+		if (name == null || name.trim().length()==0) {
+			return "_";
+		}
+		name = name.trim();
+		for (char badChar : badFilenameChars) {
+			name = name.replace(badChar, '_');
+		}
+		return name;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/ObservableTreeMap.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,43 @@
+package org.hedgewars.hedgeroid.util;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import android.database.DataSetObservable;
+
+public class ObservableTreeMap<K,V> extends DataSetObservable {
+	private final Map<K, V> map = new TreeMap<K, V>();
+	
+	public void replaceContent(Map<? extends K, ? extends V> newMap) {
+		map.clear();
+		map.putAll(newMap);
+		notifyChanged();
+	}
+	
+	public void put(K key, V value) {
+		map.put(key, value);
+		notifyChanged();
+	}
+	
+	public V get(K key) {
+		return map.get(key);
+	}
+	
+	public void remove(K key) {
+		if(map.remove(key) != null) {
+			notifyChanged();
+		}
+	}
+	
+	public void clear() {
+		if(!map.isEmpty()) {
+			map.clear();
+			notifyChanged();
+		}
+	}
+	
+	public Map<K, V> getMap() {
+		return Collections.unmodifiableMap(map);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/ObservableTreeMapAdapter.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,75 @@
+package org.hedgewars.hedgeroid.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.database.DataSetObserver;
+import android.widget.BaseAdapter;
+
+public abstract class ObservableTreeMapAdapter<K,V> extends BaseAdapter {
+	private boolean sourceChanged = true;
+	private List<V> entries = new ArrayList<V>();
+	private ObservableTreeMap<K, V> source;
+	
+	private DataSetObserver observer = new DataSetObserver() {
+		@Override
+		public void onChanged() {
+			sourceChanged = true;
+			notifyDataSetChanged();
+		}
+		
+		@Override
+		public void onInvalidated() {
+			invalidate();
+		}
+	};
+	
+	abstract protected Comparator<V> getEntryOrder();
+	
+	protected List<V> getEntries() {
+		if(sourceChanged) {
+			entries.clear();
+			entries.addAll(source.getMap().values());
+			Collections.sort(entries, getEntryOrder());
+			sourceChanged = false;
+		}
+		return entries;
+	}
+	
+	public int getCount() {
+		return getEntries().size();
+	}
+
+	public V getItem(int position) {
+		return getEntries().get(position);
+	}
+	
+	public long getItemId(int position) {
+		return position;
+	}
+	
+	@Override
+	public boolean hasStableIds() {
+		return false;
+	}
+	
+	public void setSource(ObservableTreeMap<K,V> source) {
+		if(this.source != null) {
+			this.source.unregisterObserver(observer);
+		}
+		this.source = source;
+		this.source.registerObserver(observer);
+		sourceChanged = true;
+		notifyDataSetChanged();
+	}
+	
+	public void invalidate() {
+		if(source != null) {
+			source.unregisterObserver(observer);
+		}
+		source = null;
+		notifyDataSetInvalidated();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/TextInputDialog.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,128 @@
+package org.hedgewars.hedgeroid.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * A generic text input dialog with configurable text. The Activity must implement the callback
+ * interface TextInputDialogListener, which will be called by the dialog if it is submitted or cancelled.
+ */
+public class TextInputDialog extends DialogFragment {
+	private static final String BUNDLE_DIALOG_ID = "dialogId";
+	private static final String BUNDLE_TITLE_TEXT = "title";
+	private static final String BUNDLE_MESSAGE_TEXT = "message";
+	private static final String BUNDLE_HINT_TEXT = "hint";
+	
+	private int dialogId, titleText, messageText, hintText;
+	private TextInputDialogListener listener;
+	
+	public interface TextInputDialogListener {
+		void onTextInputDialogSubmitted(int dialogId, String text);
+		void onTextInputDialogCancelled(int dialogId);
+	}
+	
+	/**
+	 * The dialogId is only used for passing back to the callback on the activity, the
+	 * other parameters are text resource IDs. Pass 0 for any of them to not use this
+	 * text.
+	 */
+	public TextInputDialog(int dialogId, int titleText, int messageText, int hintText) {
+		this.dialogId = dialogId;
+		this.titleText = titleText;
+		this.messageText = messageText;
+		this.hintText = hintText;
+	}
+	
+	public TextInputDialog() {
+		// Only for reflection-based instantiation by the framework
+	}
+	
+	@Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		try {
+			listener = (TextInputDialogListener) activity;
+		} catch(ClassCastException e) {
+			throw new ClassCastException("Activity " + activity + " must implement TextInputDialogListener to use TextInputDialog.");
+		}
+	}
+	
+	@Override
+	public void onDetach() {
+		super.onDetach();
+		listener = null;
+	}
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		if(savedInstanceState != null) {
+			dialogId = savedInstanceState.getInt(BUNDLE_DIALOG_ID, dialogId);
+			titleText = savedInstanceState.getInt(BUNDLE_TITLE_TEXT, titleText);
+			messageText = savedInstanceState.getInt(BUNDLE_MESSAGE_TEXT, messageText);
+			hintText = savedInstanceState.getInt(BUNDLE_HINT_TEXT, hintText);
+		}
+		
+		final EditText editText = new EditText(getActivity());
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		
+		if(titleText != 0) {
+			builder.setTitle(titleText);
+		}
+		if(messageText != 0) {
+			builder.setTitle(messageText);
+		}
+		if(hintText != 0) {
+			editText.setHint(hintText);
+		}
+		
+		editText.setId(android.R.id.text1);
+		editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
+		editText.setSingleLine();
+
+		builder.setView(editText);
+		builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				dialog.cancel();
+			}
+		});
+		
+		editText.setOnEditorActionListener(new OnEditorActionListener() {
+			public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+				listener.onTextInputDialogSubmitted(dialogId, v.getText().toString());
+				return true;
+			}
+		});
+		
+		builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				listener.onTextInputDialogSubmitted(dialogId, editText.getText().toString());
+			}
+		});
+
+		return builder.create();
+	}
+	
+	@Override
+	public void onSaveInstanceState(Bundle icicle) {
+		super.onSaveInstanceState(icicle);
+		icicle.putInt(BUNDLE_DIALOG_ID, dialogId);
+		icicle.putInt(BUNDLE_TITLE_TEXT, titleText);
+		icicle.putInt(BUNDLE_MESSAGE_TEXT, messageText);
+		icicle.putInt(BUNDLE_HINT_TEXT, hintText);
+	}
+	
+	@Override
+	public void onCancel(DialogInterface dialog) {
+		super.onCancel(dialog);
+		listener.onTextInputDialogCancelled(dialogId);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/TickHandler.java	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,54 @@
+package org.hedgewars.hedgeroid.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * This class handles regularly calling a specified runnable
+ * on the looper provided in the constructor. The first call
+ * occurs without delay (though still via the looper), all
+ * following calls are delayed by (approximately) the interval.
+ * The interval can be changed at any time, which will cause
+ * an immediate execution of the runnable again.
+ */
+public class TickHandler extends Handler {
+	private final Runnable callback;
+	private int messageId;
+	private long interval;
+	private boolean running;
+	
+	public TickHandler(Looper looper, long interval, Runnable callback) {
+		super(looper);
+		this.callback = callback;
+		this.interval = interval;
+	}
+	
+	public synchronized void stop() {
+		messageId++;
+		running = false;
+	}
+	
+	public synchronized void start() {
+		messageId++;
+		sendMessage(obtainMessage(messageId));
+		running = true;
+	}
+	
+	public synchronized void setInterval(long interval) {
+		this.interval = interval;
+		if(running) {
+			start();
+		}
+	}
+	
+	@Override
+	public synchronized void handleMessage(Message msg) {
+		if(msg.what == messageId) {
+			callback.run();
+		}
+		if(msg.what == messageId) {
+			sendMessageDelayed(obtainMessage(messageId), interval);
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/cmdlineClient/cmdlineClient.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,475 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <frontlib.h>
+#include <util/logging.h>
+#include <util/util.h>
+#include <base64/base64.h>
+#include <model/schemelist.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+#include <conio.h>
+#include <windows.h>
+
+#define ENGINE_DIR ".\\"
+#define CONFIG_DIR "..\\share\\hedgewars"
+#define DATA_DIR CONFIG_DIR"\\Data"
+
+static flib_netconn *netconn;
+static flib_gameconn *gameconn;
+static flib_mapconn *mapconn;
+static char nickname[128];
+static flib_metascheme *metacfg;
+static bool netConnected = false;
+
+// Callback function that will be called when the map is rendered
+static void handleMapGenerated(void *context, const uint8_t *bitmap, int numHedgehogs) {
+	printf("Drawing map for %i brave little hogs...", numHedgehogs);
+
+	// Draw the map as ASCII art
+	for(int y=0; y<MAPIMAGE_HEIGHT; y+=8) {
+		for(int x=0; x<MAPIMAGE_WIDTH; x+=6) {
+			int pixelnum = x + y*MAPIMAGE_WIDTH;
+			bool pixel = bitmap[pixelnum>>3] & (1<<(7-(pixelnum&7)));
+			printf(pixel ? "#" : " ");
+		}
+		printf("\n");
+	}
+
+	flib_mapconn_destroy(mapconn);
+	mapconn = NULL;
+}
+
+static void onGameDisconnect(void *context, int reason) {
+	flib_log_i("Connection closed. Reason: %i", reason);
+	flib_gameconn_destroy(gameconn);
+	gameconn = NULL;
+	if(netconn) {
+		flib_netconn_send_roundfinished(netconn, reason==GAME_END_FINISHED);
+	}
+}
+
+// Callback function that will be called on error
+static void handleMapFailure(void *context, const char *errormessage) {
+	flib_log_e("Map rendering failed: %s", errormessage);
+	flib_mapconn_destroy(mapconn);
+	mapconn = NULL;
+}
+
+static void startEngineMap(int port) {
+	char cmdbuffer[255];
+	char argbuffer[255];
+	snprintf(cmdbuffer, 255, "%shwengine.exe", ENGINE_DIR);
+	snprintf(argbuffer, 255, "%s %i landpreview", CONFIG_DIR, port);
+	ShellExecute(NULL, NULL, cmdbuffer, argbuffer, NULL, SW_HIDE);
+}
+
+static void startEngineGame(int port) {
+	char cmdbuffer[255];
+	char argbuffer[255];
+	char base64PlayerName[255];
+	base64_encode(nickname, strlen(nickname), base64PlayerName, sizeof(base64PlayerName));
+	snprintf(cmdbuffer, 255, "%shwengine.exe", ENGINE_DIR);
+	snprintf(argbuffer, 255, "%s 1024 768 32 %i 0 0 0 10 10 %s 0 0 %s 0 0 en.txt", CONFIG_DIR, port, DATA_DIR, base64PlayerName);
+	ShellExecute(NULL, NULL, cmdbuffer, argbuffer, NULL, SW_HIDE);
+}
+
+void handleNetDisconnect(void *context, int reason, const char *message) {
+	printf("Disconnected: %s", message);
+	flib_netconn_destroy(netconn);
+	netconn = NULL;
+}
+
+/*void printRoomList() {
+	const flib_roomlist *roomlist = flib_netconn_get_roomlist(netconn);
+	if(roomlist) {
+		if(roomlist->roomCount>0) {
+			for(int i=0; i<roomlist->roomCount; i++) {
+				if(i>0) {
+					printf(", ");
+				}
+				flib_room *room = roomlist->rooms[i];
+				printf("%s", room->name);
+			}
+		} else {
+			puts("Unfortunately, there are no rooms at the moment.");
+		}
+	} else {
+		puts("Sorry, due to an error the room list is not available.");
+	}
+	puts("\n");
+}*/
+
+void printTeamList() {
+	flib_gamesetup *setup = flib_netconn_create_gamesetup(netconn);
+	if(setup) {
+		puts("The following teams are in this room:");
+		for(int i=0; i<setup->teamlist->teamCount; i++) {
+			if(i>0) {
+				printf(", ");
+			}
+			printf("%s", setup->teamlist->teams[i]->name);
+		}
+		puts("\n");
+	} else {
+		puts("Sorry, due to an error the team list is not available.");
+	}
+	flib_gamesetup_destroy(setup);
+}
+
+void handleNetConnected(void *context) {
+	printf("You enter the lobby of a strange house inhabited by hedgehogs. Looking around, you see hallways branching off to these rooms:\n");
+	//printRoomList();
+	printf("\n\nNow, you can chat by just entering text, or join a room with /join <roomname>.");
+	printf(" You can also /quit or let me /describe <roomname>. Once in a room, you can /add <teamname> and set yourself /ready. You can also /list the available rooms (in the lobby) or the teams (in a room).\n");
+	netConnected = true;
+}
+
+void handleChat(void *context, const char *nick, const char *msg) {
+	if(gameconn) {
+		flib_gameconn_send_chatmsg(gameconn, nick, msg);
+	}
+	printf("%s: %s\n", nick, msg);
+}
+
+void handleEnterRoom(void *context, bool isChief) {
+	puts("You have entered the room.");
+}
+
+void handleRoomJoin(void *context, const char *nick) {
+	if(strcmp(nick, nickname)) {
+		printf("%s is here.\n", nick);
+	}
+}
+
+void handleRoomLeave(void *context, const char *nick, const char *partmsg) {
+	if(strcmp(nick, nickname)) {
+		printf("%s leaves.\n", nick);
+	}
+}
+
+void handleReady(void *context, const char *nick, bool ready) {
+	if(strcmp(nick, nickname)) {
+		if(ready) {
+			printf("%s is ready to go.\n", nick);
+		} else {
+			printf("%s is not ready.\n", nick);
+		}
+	} else {
+		if(ready) {
+			printf("You are ready to go.\n");
+		} else {
+			printf("You are not ready.\n");
+		}
+	}
+}
+
+void handleEmFromNet(void *context, const uint8_t *em, size_t size) {
+	if(gameconn) {
+		flib_gameconn_send_enginemsg(gameconn, em, size);
+	}
+}
+
+void handleEmFromEngine(void *context, const uint8_t *em, size_t size) {
+	if(netconn) {
+		flib_netconn_send_engineMessage(netconn, em, size);
+	}
+}
+
+void handleChatFromGame(void *context, const char *message, bool teamchat) {
+	if(netconn) {
+		if(teamchat) {
+			flib_netconn_send_teamchat(netconn, message);
+		} else {
+			flib_netconn_send_chat(netconn, message);
+		}
+	}
+}
+
+void handleRunGame(void *context) {
+	flib_gamesetup *gamesetup = flib_netconn_create_gamesetup(netconn);
+	if(gameconn) {
+		flib_log_e("Request to start game, but a game is already running.");
+	} else if(gamesetup) {
+		gameconn = flib_gameconn_create(nickname, gamesetup, true);
+		flib_gameconn_onEngineMessage(gameconn, handleEmFromEngine, NULL);
+		flib_gameconn_onDisconnect(gameconn, onGameDisconnect, NULL);
+		flib_gameconn_onChat(gameconn, handleChatFromGame, NULL);
+		startEngineGame(flib_gameconn_getport(gameconn));
+	}
+	flib_gamesetup_destroy(gamesetup);
+}
+
+void handleNickTaken(void *context, const char *nick) {
+	printf("The nickname %s is already in use, please choose a different one:\n", nick);
+	flib_gets(nickname, sizeof(nickname));
+	flib_netconn_send_nick(netconn, nickname);
+}
+
+void handlePwRequest(void *context, const char *nick) {
+	printf("A password is required to log in as %s, please enter (warning: shown in cleartext):\n", nick);
+	char password[256];
+	flib_gets(password, sizeof(password));
+	flib_netconn_send_password(netconn, password);
+}
+
+void handleMessage(void *context, int type, const char *msg) {
+	if(gameconn) {
+		flib_gameconn_send_textmsg(gameconn, 1, msg);
+	}
+	printf("*** %s\n", msg);
+}
+
+void handleTeamAccepted(void *context, const char *teamname) {
+	printf("The team %s has been accepted.\n", teamname);
+}
+
+void handleMapChanged(void *context, const flib_map *map, int changetype) {
+	if(map->mapgen != MAPGEN_NAMED && changetype != NETCONN_MAPCHANGE_THEME) {
+		if(mapconn) {
+			flib_mapconn_destroy(mapconn);
+			mapconn = NULL;
+		}
+		mapconn = flib_mapconn_create(map);
+		if(mapconn) {
+			flib_mapconn_onSuccess(mapconn, handleMapGenerated, NULL);
+			flib_mapconn_onFailure(mapconn, handleMapFailure, NULL);
+			startEngineMap(flib_mapconn_getport(mapconn));
+		}
+	} else if(map->mapgen == MAPGEN_NAMED) {
+		printf("The map %s has been selected.\n", map->name);
+	}
+}
+
+void handleLeaveRoom(void *context, int reason, const char *msg) {
+	if(reason == NETCONN_ROOMLEAVE_ABANDONED) {
+		printf("The chief has abandoned the room.");
+	} else if(reason == NETCONN_ROOMLEAVE_KICKED) {
+		printf("You have been kicked from the room.");
+	}
+	if(msg) {
+		printf(" (%s)", msg);
+	}
+	puts(" You are back in the lobby.");
+}
+
+void handleSchemeChanged(void *context, const flib_scheme *scheme) {
+	printf("Game scheme: %s.\n", scheme->name);
+}
+
+void handleWeaponsetChanged(void *context, const flib_weaponset *weaponset) {
+	printf("Weaponset: %s.\n", weaponset->name);
+}
+
+void handleHogcountChanged(void *context, const char *team, int count) {
+	printf("Team %s will send %i hogs into the fight.\n", team, count);
+}
+
+void handleRoomAdd(void *context, const flib_room *room) {
+	printf("%s created a new room called %s.\n", room->owner, room->name);
+}
+
+void handleRoomDelete(void *context, const char *roomName) {
+	printf("The room %s has collapsed.\n", roomName);
+}
+
+void handleScriptChanged(void *context, const char *script) {
+	printf("Game Type: %s\n", script);
+}
+
+void handleTeamAdd(void *context, const flib_team *team) {
+	printf("%s puts the team %s to the planning board.\n", team->ownerName, team->name);
+}
+
+void handleTeamDelete(void *context, const char *teamName) {
+	printf("The team %s decided not to fight this battle after all.\n", teamName);
+}
+
+void handleTeamColorChanged(void *context, const char *name, int colorIndex) {
+	static const char* colorNames[] = {"red", "blue", "teal", "purple", "pink", "green", "orange", "brown", "yellow"};
+	const char *colorName = "strange";
+	if(colorIndex>=0 && colorIndex < 9) {
+		colorName = colorNames[colorIndex];
+	}
+	printf("The team %s will wear %s uniforms today.\n", name, colorName);
+}
+
+void tick() {
+	if(gameconn) {
+		flib_gameconn_tick(gameconn);
+	}
+	if(netconn) {
+		flib_netconn_tick(netconn);
+	}
+	if(mapconn) {
+		flib_mapconn_tick(mapconn);
+	}
+}
+
+static HANDLE hStdin;
+
+static int init() {
+	hStdin = GetStdHandle(STD_INPUT_HANDLE);
+	if(hStdin == INVALID_HANDLE_VALUE) {
+		flib_log_e("Unable to get stdin handle");
+		return 1;
+	}
+	if(!flib_init(0)) {
+		flib_log_setLevel(FLIB_LOGLEVEL_WARNING);
+		freopen( "CON", "w", stdout );
+		freopen( "CON", "w", stderr );
+		metacfg = flib_metascheme_from_ini("metasettings.ini");
+		if(!metacfg) {
+			flib_quit();
+			return -1;
+		} else {
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int main(int argc, char *argv[]) {
+	if(init()) {
+		return -1;
+	}
+
+	puts("Please enter a nickname:");
+	flib_gets(nickname, sizeof(nickname));
+
+	netconn = flib_netconn_create(nickname, metacfg, DATA_DIR"\\", "140.247.62.101", 46631);
+	if(!netconn) {
+		flib_quit();
+		return -1;
+	}
+
+	flib_netconn_onConnected(netconn, handleNetConnected, NULL);
+	flib_netconn_onDisconnected(netconn, handleNetDisconnect, NULL);
+	flib_netconn_onChat(netconn, handleChat, NULL);
+	flib_netconn_onEnterRoom(netconn, handleEnterRoom, NULL);
+	flib_netconn_onRunGame(netconn, handleRunGame, NULL);
+	flib_netconn_onEngineMessage(netconn, handleEmFromNet, NULL);
+	flib_netconn_onRoomJoin(netconn, handleRoomJoin, NULL);
+	flib_netconn_onRoomLeave(netconn, handleRoomLeave, NULL);
+	flib_netconn_onReadyState(netconn, handleReady, NULL);
+	flib_netconn_onNickTaken(netconn, handleNickTaken, NULL);
+	flib_netconn_onPasswordRequest(netconn, handlePwRequest, NULL);
+	flib_netconn_onMessage(netconn, handleMessage, NULL);
+	flib_netconn_onTeamAccepted(netconn, handleTeamAccepted, NULL);
+	flib_netconn_onMapChanged(netconn, handleMapChanged, NULL);
+	flib_netconn_onLeaveRoom(netconn, handleLeaveRoom, NULL);
+	flib_netconn_onCfgScheme(netconn, handleSchemeChanged, NULL);
+	flib_netconn_onWeaponsetChanged(netconn, handleWeaponsetChanged, NULL);
+	flib_netconn_onHogCountChanged(netconn, handleHogcountChanged, NULL);
+	flib_netconn_onRoomAdd(netconn, handleRoomAdd, NULL);
+	flib_netconn_onRoomDelete(netconn, handleRoomDelete, NULL);
+	flib_netconn_onScriptChanged(netconn, handleScriptChanged, NULL);
+	flib_netconn_onTeamAdd(netconn, handleTeamAdd, NULL);
+	flib_netconn_onTeamDelete(netconn, handleTeamDelete, NULL);
+	flib_netconn_onTeamColorChanged(netconn, handleTeamColorChanged, NULL);
+
+	INPUT_RECORD inputRecord;
+	DWORD eventCount = 0;
+
+	while(netconn || gameconn) {
+		tick();
+		if(netconn && netConnected) {
+			while(PeekConsoleInput(hStdin, &inputRecord, 1, &eventCount) && eventCount>0) {
+				if(inputRecord.EventType != KEY_EVENT) {
+					ReadConsoleInput(hStdin, &inputRecord, 1, &eventCount);
+				} else {
+					printf("%s: ", nickname);
+					char input[256];
+					if(!flib_gets(input, sizeof(input))) {
+						if(!memcmp("/quit", input, strlen("/quit"))) {
+							flib_netconn_send_quit(netconn, "Player quit.");
+						} else if(!memcmp("/describe ", input, strlen("/describe "))) {
+							const char *roomname = input+strlen("/describe ");
+							/*const flib_roomlist *roomlist = flib_netconn_get_roomlist(netconn);
+							flib_room *room = flib_roomlist_find(roomlist, roomname);
+							if(!room) {
+								puts("Unknown room.");
+							} else {
+								char *text = flib_asprintf(
+										"%s is a room created by %s, where %i players (%i teams) are %s on %s%s, using the %s scheme and %s weaponset.",
+										room->name,
+										room->owner,
+										room->playerCount,
+										room->teamCount,
+										room->inProgress ? "fighting" : "preparing to fight",
+										room->map[0]=='+' ? "" : "the map ",
+										!strcmp("+rnd+", room->map) ? "a random map" :
+												!strcmp("+maze+", room->map) ? "a random maze" :
+												!strcmp("+drawn+", room->map) ? "a hand-drawn map" :
+												room->map,
+										room->scheme,
+										room->weapons);
+								if(text) {
+									puts(text);
+								}
+								free(text);
+							}*/
+						} else if(!memcmp("/join ", input, strlen("/join "))) {
+							const char *roomname = input+strlen("/join ");
+							flib_netconn_send_joinRoom(netconn, roomname);
+						} else if(!memcmp("/ready", input, strlen("/ready"))) {
+							flib_netconn_send_toggleReady(netconn);
+						} else if(!memcmp("/loglevel ", input, strlen("/loglevel "))) {
+							int loglevel = atoi(input+strlen("/loglevel "));
+							flib_log_setLevel(loglevel);
+						} else if(!memcmp("/list", input, strlen("/list"))) {
+							if(flib_netconn_is_in_room_context(netconn)) {
+								printTeamList();
+							} else {
+								puts("From this big and expansive lobby, hallways branch off to these rooms:");
+								//printRoomList();
+							}
+						} else if(!memcmp("/addteam ", input, strlen("/addteam "))) {
+							const char *teamname = input+strlen("/addteam ");
+							if(!flib_contains_dir_separator(teamname)) {
+								char *teamfilename = flib_asprintf("%s.hwt", teamname);
+								if(teamfilename) {
+									flib_team *team = flib_team_from_ini(teamfilename);
+									if(team) {
+										flib_netconn_send_addTeam(netconn, team);
+									} else {
+										printf("Teamfile %s not found.\n", teamfilename);
+									}
+									flib_team_destroy(team);
+								}
+								free(teamfilename);
+							}
+						} else if(strlen(input)>0) {
+							flib_netconn_send_chat(netconn, input);
+						}
+					}
+				}
+			}
+		}
+		fflush(stdout);
+		Sleep(10);
+	}
+
+
+	flib_metascheme_release(metacfg);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/Android.mk	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,22 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := frontlib
+
+LOCAL_CFLAGS := -I$(LOCAL_PATH)/../Android-build/SDL-android-project/jni/SDL_net -std=c99 -DWITHOUT_SDL
+
+LOCAL_SRC_FILES := base64/base64.c iniparser/iniparser.c \
+	iniparser/dictionary.c ipc/gameconn.c ipc/ipcbase.c \
+	ipc/ipcprotocol.c ipc/mapconn.c md5/md5.c model/scheme.c \
+	model/gamesetup.c model/map.c model/mapcfg.c model/room.c \
+	model/schemelist.c model/team.c model/teamlist.c model/weapon.c \
+	net/netbase.c net/netconn_callbacks.c net/netconn_send.c \
+	net/netconn.c net/netprotocol.c util/buffer.c util/inihelper.c \
+	util/logging.c util/util.c frontlib.c hwconsts.c socket.c \
+	extra/jnacontrol.c
+
+LOCAL_SHARED_LIBRARIES += SDL_net
+LOCAL_LDLIBS += -lz
+
+include $(BUILD_SHARED_LIBRARY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/base64/base64.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,572 @@
+/* base64.c -- Encode binary data using printable characters.
+   Copyright (C) 1999-2001, 2004-2006, 2009-2012 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Simon Josefsson.  Partially adapted from GNU MailUtils
+ * (mailbox/filter_trans.c, as of 2004-11-28).  Improved by review
+ * from Paul Eggert, Bruno Haible, and Stepan Kasal.
+ *
+ * See also RFC 4648 <http://www.ietf.org/rfc/rfc4648.txt>.
+ *
+ * Be careful with error checking.  Here is how you would typically
+ * use these functions:
+ *
+ * bool ok = base64_decode_alloc (in, inlen, &out, &outlen);
+ * if (!ok)
+ *   FAIL: input was not valid base64
+ * if (out == NULL)
+ *   FAIL: memory allocation error
+ * OK: data in OUT/OUTLEN
+ *
+ * size_t outlen = base64_encode_alloc (in, inlen, &out);
+ * if (out == NULL && outlen == 0 && inlen != 0)
+ *   FAIL: input too long
+ * if (out == NULL)
+ *   FAIL: memory allocation error
+ * OK: data in OUT/OUTLEN.
+ *
+ */
+
+/* Get prototype. */
+#include "base64.h"
+
+/* Get malloc. */
+#include <stdlib.h>
+
+/* Get UCHAR_MAX. */
+#include <limits.h>
+
+#include <string.h>
+
+/* C89 compliant way to cast 'char' to 'unsigned char'. */
+static inline unsigned char
+to_uchar (char ch)
+{
+  return ch;
+}
+
+/* Base64 encode IN array of size INLEN into OUT array of size OUTLEN.
+   If OUTLEN is less than BASE64_LENGTH(INLEN), write as many bytes as
+   possible.  If OUTLEN is larger than BASE64_LENGTH(INLEN), also zero
+   terminate the output buffer. */
+void
+base64_encode (const char *restrict in, size_t inlen,
+               char *restrict out, size_t outlen)
+{
+  static const char b64str[64] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+  while (inlen && outlen)
+    {
+      *out++ = b64str[(to_uchar (in[0]) >> 2) & 0x3f];
+      if (!--outlen)
+        break;
+      *out++ = b64str[((to_uchar (in[0]) << 4)
+                       + (--inlen ? to_uchar (in[1]) >> 4 : 0))
+                      & 0x3f];
+      if (!--outlen)
+        break;
+      *out++ =
+        (inlen
+         ? b64str[((to_uchar (in[1]) << 2)
+                   + (--inlen ? to_uchar (in[2]) >> 6 : 0))
+                  & 0x3f]
+         : '=');
+      if (!--outlen)
+        break;
+      *out++ = inlen ? b64str[to_uchar (in[2]) & 0x3f] : '=';
+      if (!--outlen)
+        break;
+      if (inlen)
+        inlen--;
+      if (inlen)
+        in += 3;
+    }
+
+  if (outlen)
+    *out = '\0';
+}
+
+/* Allocate a buffer and store zero terminated base64 encoded data
+   from array IN of size INLEN, returning BASE64_LENGTH(INLEN), i.e.,
+   the length of the encoded data, excluding the terminating zero.  On
+   return, the OUT variable will hold a pointer to newly allocated
+   memory that must be deallocated by the caller.  If output string
+   length would overflow, 0 is returned and OUT is set to NULL.  If
+   memory allocation failed, OUT is set to NULL, and the return value
+   indicates length of the requested memory block, i.e.,
+   BASE64_LENGTH(inlen) + 1. */
+size_t
+base64_encode_alloc (const char *in, size_t inlen, char **out)
+{
+  size_t outlen = 1 + BASE64_LENGTH (inlen);
+
+  /* Check for overflow in outlen computation.
+   *
+   * If there is no overflow, outlen >= inlen.
+   *
+   * If the operation (inlen + 2) overflows then it yields at most +1, so
+   * outlen is 0.
+   *
+   * If the multiplication overflows, we lose at least half of the
+   * correct value, so the result is < ((inlen + 2) / 3) * 2, which is
+   * less than (inlen + 2) * 0.66667, which is less than inlen as soon as
+   * (inlen > 4).
+   */
+  if (inlen > outlen)
+    {
+      *out = NULL;
+      return 0;
+    }
+
+  *out = malloc (outlen);
+  if (!*out)
+    return outlen;
+
+  base64_encode (in, inlen, *out, outlen);
+
+  return outlen - 1;
+}
+
+/* With this approach this file works independent of the charset used
+   (think EBCDIC).  However, it does assume that the characters in the
+   Base64 alphabet (A-Za-z0-9+/) are encoded in 0..255.  POSIX
+   1003.1-2001 require that char and unsigned char are 8-bit
+   quantities, though, taking care of that problem.  But this may be a
+   potential problem on non-POSIX C99 platforms.
+
+   IBM C V6 for AIX mishandles "#define B64(x) ...'x'...", so use "_"
+   as the formal parameter rather than "x".  */
+#define B64(_)                                  \
+  ((_) == 'A' ? 0                               \
+   : (_) == 'B' ? 1                             \
+   : (_) == 'C' ? 2                             \
+   : (_) == 'D' ? 3                             \
+   : (_) == 'E' ? 4                             \
+   : (_) == 'F' ? 5                             \
+   : (_) == 'G' ? 6                             \
+   : (_) == 'H' ? 7                             \
+   : (_) == 'I' ? 8                             \
+   : (_) == 'J' ? 9                             \
+   : (_) == 'K' ? 10                            \
+   : (_) == 'L' ? 11                            \
+   : (_) == 'M' ? 12                            \
+   : (_) == 'N' ? 13                            \
+   : (_) == 'O' ? 14                            \
+   : (_) == 'P' ? 15                            \
+   : (_) == 'Q' ? 16                            \
+   : (_) == 'R' ? 17                            \
+   : (_) == 'S' ? 18                            \
+   : (_) == 'T' ? 19                            \
+   : (_) == 'U' ? 20                            \
+   : (_) == 'V' ? 21                            \
+   : (_) == 'W' ? 22                            \
+   : (_) == 'X' ? 23                            \
+   : (_) == 'Y' ? 24                            \
+   : (_) == 'Z' ? 25                            \
+   : (_) == 'a' ? 26                            \
+   : (_) == 'b' ? 27                            \
+   : (_) == 'c' ? 28                            \
+   : (_) == 'd' ? 29                            \
+   : (_) == 'e' ? 30                            \
+   : (_) == 'f' ? 31                            \
+   : (_) == 'g' ? 32                            \
+   : (_) == 'h' ? 33                            \
+   : (_) == 'i' ? 34                            \
+   : (_) == 'j' ? 35                            \
+   : (_) == 'k' ? 36                            \
+   : (_) == 'l' ? 37                            \
+   : (_) == 'm' ? 38                            \
+   : (_) == 'n' ? 39                            \
+   : (_) == 'o' ? 40                            \
+   : (_) == 'p' ? 41                            \
+   : (_) == 'q' ? 42                            \
+   : (_) == 'r' ? 43                            \
+   : (_) == 's' ? 44                            \
+   : (_) == 't' ? 45                            \
+   : (_) == 'u' ? 46                            \
+   : (_) == 'v' ? 47                            \
+   : (_) == 'w' ? 48                            \
+   : (_) == 'x' ? 49                            \
+   : (_) == 'y' ? 50                            \
+   : (_) == 'z' ? 51                            \
+   : (_) == '0' ? 52                            \
+   : (_) == '1' ? 53                            \
+   : (_) == '2' ? 54                            \
+   : (_) == '3' ? 55                            \
+   : (_) == '4' ? 56                            \
+   : (_) == '5' ? 57                            \
+   : (_) == '6' ? 58                            \
+   : (_) == '7' ? 59                            \
+   : (_) == '8' ? 60                            \
+   : (_) == '9' ? 61                            \
+   : (_) == '+' ? 62                            \
+   : (_) == '/' ? 63                            \
+   : -1)
+
+static const signed char b64[0x100] = {
+  B64 (0), B64 (1), B64 (2), B64 (3),
+  B64 (4), B64 (5), B64 (6), B64 (7),
+  B64 (8), B64 (9), B64 (10), B64 (11),
+  B64 (12), B64 (13), B64 (14), B64 (15),
+  B64 (16), B64 (17), B64 (18), B64 (19),
+  B64 (20), B64 (21), B64 (22), B64 (23),
+  B64 (24), B64 (25), B64 (26), B64 (27),
+  B64 (28), B64 (29), B64 (30), B64 (31),
+  B64 (32), B64 (33), B64 (34), B64 (35),
+  B64 (36), B64 (37), B64 (38), B64 (39),
+  B64 (40), B64 (41), B64 (42), B64 (43),
+  B64 (44), B64 (45), B64 (46), B64 (47),
+  B64 (48), B64 (49), B64 (50), B64 (51),
+  B64 (52), B64 (53), B64 (54), B64 (55),
+  B64 (56), B64 (57), B64 (58), B64 (59),
+  B64 (60), B64 (61), B64 (62), B64 (63),
+  B64 (64), B64 (65), B64 (66), B64 (67),
+  B64 (68), B64 (69), B64 (70), B64 (71),
+  B64 (72), B64 (73), B64 (74), B64 (75),
+  B64 (76), B64 (77), B64 (78), B64 (79),
+  B64 (80), B64 (81), B64 (82), B64 (83),
+  B64 (84), B64 (85), B64 (86), B64 (87),
+  B64 (88), B64 (89), B64 (90), B64 (91),
+  B64 (92), B64 (93), B64 (94), B64 (95),
+  B64 (96), B64 (97), B64 (98), B64 (99),
+  B64 (100), B64 (101), B64 (102), B64 (103),
+  B64 (104), B64 (105), B64 (106), B64 (107),
+  B64 (108), B64 (109), B64 (110), B64 (111),
+  B64 (112), B64 (113), B64 (114), B64 (115),
+  B64 (116), B64 (117), B64 (118), B64 (119),
+  B64 (120), B64 (121), B64 (122), B64 (123),
+  B64 (124), B64 (125), B64 (126), B64 (127),
+  B64 (128), B64 (129), B64 (130), B64 (131),
+  B64 (132), B64 (133), B64 (134), B64 (135),
+  B64 (136), B64 (137), B64 (138), B64 (139),
+  B64 (140), B64 (141), B64 (142), B64 (143),
+  B64 (144), B64 (145), B64 (146), B64 (147),
+  B64 (148), B64 (149), B64 (150), B64 (151),
+  B64 (152), B64 (153), B64 (154), B64 (155),
+  B64 (156), B64 (157), B64 (158), B64 (159),
+  B64 (160), B64 (161), B64 (162), B64 (163),
+  B64 (164), B64 (165), B64 (166), B64 (167),
+  B64 (168), B64 (169), B64 (170), B64 (171),
+  B64 (172), B64 (173), B64 (174), B64 (175),
+  B64 (176), B64 (177), B64 (178), B64 (179),
+  B64 (180), B64 (181), B64 (182), B64 (183),
+  B64 (184), B64 (185), B64 (186), B64 (187),
+  B64 (188), B64 (189), B64 (190), B64 (191),
+  B64 (192), B64 (193), B64 (194), B64 (195),
+  B64 (196), B64 (197), B64 (198), B64 (199),
+  B64 (200), B64 (201), B64 (202), B64 (203),
+  B64 (204), B64 (205), B64 (206), B64 (207),
+  B64 (208), B64 (209), B64 (210), B64 (211),
+  B64 (212), B64 (213), B64 (214), B64 (215),
+  B64 (216), B64 (217), B64 (218), B64 (219),
+  B64 (220), B64 (221), B64 (222), B64 (223),
+  B64 (224), B64 (225), B64 (226), B64 (227),
+  B64 (228), B64 (229), B64 (230), B64 (231),
+  B64 (232), B64 (233), B64 (234), B64 (235),
+  B64 (236), B64 (237), B64 (238), B64 (239),
+  B64 (240), B64 (241), B64 (242), B64 (243),
+  B64 (244), B64 (245), B64 (246), B64 (247),
+  B64 (248), B64 (249), B64 (250), B64 (251),
+  B64 (252), B64 (253), B64 (254), B64 (255)
+};
+
+#if UCHAR_MAX == 255
+# define uchar_in_range(c) true
+#else
+# define uchar_in_range(c) ((c) <= 255)
+#endif
+
+/* Return true if CH is a character from the Base64 alphabet, and
+   false otherwise.  Note that '=' is padding and not considered to be
+   part of the alphabet.  */
+bool
+isbase64 (char ch)
+{
+  return uchar_in_range (to_uchar (ch)) && 0 <= b64[to_uchar (ch)];
+}
+
+/* Initialize decode-context buffer, CTX.  */
+void
+base64_decode_ctx_init (struct base64_decode_context *ctx)
+{
+  ctx->i = 0;
+}
+
+/* If CTX->i is 0 or 4, there are four or more bytes in [*IN..IN_END), and
+   none of those four is a newline, then return *IN.  Otherwise, copy up to
+   4 - CTX->i non-newline bytes from that range into CTX->buf, starting at
+   index CTX->i and setting CTX->i to reflect the number of bytes copied,
+   and return CTX->buf.  In either case, advance *IN to point to the byte
+   after the last one processed, and set *N_NON_NEWLINE to the number of
+   verified non-newline bytes accessible through the returned pointer.  */
+static inline char *
+get_4 (struct base64_decode_context *ctx,
+       char const *restrict *in, char const *restrict in_end,
+       size_t *n_non_newline)
+{
+  if (ctx->i == 4)
+    ctx->i = 0;
+
+  if (ctx->i == 0)
+    {
+      char const *t = *in;
+      if (4 <= in_end - *in && memchr (t, '\n', 4) == NULL)
+        {
+          /* This is the common case: no newline.  */
+          *in += 4;
+          *n_non_newline = 4;
+          return (char *) t;
+        }
+    }
+
+  {
+    /* Copy non-newline bytes into BUF.  */
+    char const *p = *in;
+    while (p < in_end)
+      {
+        char c = *p++;
+        if (c != '\n')
+          {
+            ctx->buf[ctx->i++] = c;
+            if (ctx->i == 4)
+              break;
+          }
+      }
+
+    *in = p;
+    *n_non_newline = ctx->i;
+    return ctx->buf;
+  }
+}
+
+#define return_false                            \
+  do                                            \
+    {                                           \
+      *outp = out;                              \
+      return false;                             \
+    }                                           \
+  while (false)
+
+/* Decode up to four bytes of base64-encoded data, IN, of length INLEN
+   into the output buffer, *OUT, of size *OUTLEN bytes.  Return true if
+   decoding is successful, false otherwise.  If *OUTLEN is too small,
+   as many bytes as possible are written to *OUT.  On return, advance
+   *OUT to point to the byte after the last one written, and decrement
+   *OUTLEN to reflect the number of bytes remaining in *OUT.  */
+static inline bool
+decode_4 (char const *restrict in, size_t inlen,
+          char *restrict *outp, size_t *outleft)
+{
+  char *out = *outp;
+  if (inlen < 2)
+    return false;
+
+  if (!isbase64 (in[0]) || !isbase64 (in[1]))
+    return false;
+
+  if (*outleft)
+    {
+      *out++ = ((b64[to_uchar (in[0])] << 2)
+                | (b64[to_uchar (in[1])] >> 4));
+      --*outleft;
+    }
+
+  if (inlen == 2)
+    return_false;
+
+  if (in[2] == '=')
+    {
+      if (inlen != 4)
+        return_false;
+
+      if (in[3] != '=')
+        return_false;
+    }
+  else
+    {
+      if (!isbase64 (in[2]))
+        return_false;
+
+      if (*outleft)
+        {
+          *out++ = (((b64[to_uchar (in[1])] << 4) & 0xf0)
+                    | (b64[to_uchar (in[2])] >> 2));
+          --*outleft;
+        }
+
+      if (inlen == 3)
+        return_false;
+
+      if (in[3] == '=')
+        {
+          if (inlen != 4)
+            return_false;
+        }
+      else
+        {
+          if (!isbase64 (in[3]))
+            return_false;
+
+          if (*outleft)
+            {
+              *out++ = (((b64[to_uchar (in[2])] << 6) & 0xc0)
+                        | b64[to_uchar (in[3])]);
+              --*outleft;
+            }
+        }
+    }
+
+  *outp = out;
+  return true;
+}
+
+/* Decode base64-encoded input array IN of length INLEN to output array
+   OUT that can hold *OUTLEN bytes.  The input data may be interspersed
+   with newlines.  Return true if decoding was successful, i.e. if the
+   input was valid base64 data, false otherwise.  If *OUTLEN is too
+   small, as many bytes as possible will be written to OUT.  On return,
+   *OUTLEN holds the length of decoded bytes in OUT.  Note that as soon
+   as any non-alphabet, non-newline character is encountered, decoding
+   is stopped and false is returned.  If INLEN is zero, then process
+   only whatever data is stored in CTX.
+
+   Initially, CTX must have been initialized via base64_decode_ctx_init.
+   Subsequent calls to this function must reuse whatever state is recorded
+   in that buffer.  It is necessary for when a quadruple of base64 input
+   bytes spans two input buffers.
+
+   If CTX is NULL then newlines are treated as garbage and the input
+   buffer is processed as a unit.  */
+
+bool
+base64_decode_ctx (struct base64_decode_context *ctx,
+                   const char *restrict in, size_t inlen,
+                   char *restrict out, size_t *outlen)
+{
+  size_t outleft = *outlen;
+  bool ignore_newlines = ctx != NULL;
+  bool flush_ctx = false;
+  unsigned int ctx_i = 0;
+
+  if (ignore_newlines)
+    {
+      ctx_i = ctx->i;
+      flush_ctx = inlen == 0;
+    }
+
+
+  while (true)
+    {
+      size_t outleft_save = outleft;
+      if (ctx_i == 0 && !flush_ctx)
+        {
+          while (true)
+            {
+              /* Save a copy of outleft, in case we need to re-parse this
+                 block of four bytes.  */
+              outleft_save = outleft;
+              if (!decode_4 (in, inlen, &out, &outleft))
+                break;
+
+              in += 4;
+              inlen -= 4;
+            }
+        }
+
+      if (inlen == 0 && !flush_ctx)
+        break;
+
+      /* Handle the common case of 72-byte wrapped lines.
+         This also handles any other multiple-of-4-byte wrapping.  */
+      if (inlen && *in == '\n' && ignore_newlines)
+        {
+          ++in;
+          --inlen;
+          continue;
+        }
+
+      /* Restore OUT and OUTLEFT.  */
+      out -= outleft_save - outleft;
+      outleft = outleft_save;
+
+      {
+        char const *in_end = in + inlen;
+        char const *non_nl;
+
+        if (ignore_newlines)
+          non_nl = get_4 (ctx, &in, in_end, &inlen);
+        else
+          non_nl = in;  /* Might have nl in this case. */
+
+        /* If the input is empty or consists solely of newlines (0 non-newlines),
+           then we're done.  Likewise if there are fewer than 4 bytes when not
+           flushing context and not treating newlines as garbage.  */
+        if (inlen == 0 || (inlen < 4 && !flush_ctx && ignore_newlines))
+          {
+            inlen = 0;
+            break;
+          }
+        if (!decode_4 (non_nl, inlen, &out, &outleft))
+          break;
+
+        inlen = in_end - in;
+      }
+    }
+
+  *outlen -= outleft;
+
+  return inlen == 0;
+}
+
+/* Allocate an output buffer in *OUT, and decode the base64 encoded
+   data stored in IN of size INLEN to the *OUT buffer.  On return, the
+   size of the decoded data is stored in *OUTLEN.  OUTLEN may be NULL,
+   if the caller is not interested in the decoded length.  *OUT may be
+   NULL to indicate an out of memory error, in which case *OUTLEN
+   contains the size of the memory block needed.  The function returns
+   true on successful decoding and memory allocation errors.  (Use the
+   *OUT and *OUTLEN parameters to differentiate between successful
+   decoding and memory error.)  The function returns false if the
+   input was invalid, in which case *OUT is NULL and *OUTLEN is
+   undefined. */
+bool
+base64_decode_alloc_ctx (struct base64_decode_context *ctx,
+                         const char *in, size_t inlen, char **out,
+                         size_t *outlen)
+{
+  /* This may allocate a few bytes too many, depending on input,
+     but it's not worth the extra CPU time to compute the exact size.
+     The exact size is 3 * (inlen + (ctx ? ctx->i : 0)) / 4, minus 1 if the
+     input ends with "=" and minus another 1 if the input ends with "==".
+     Dividing before multiplying avoids the possibility of overflow.  */
+  size_t needlen = 3 * (inlen / 4) + 3;
+
+  *out = malloc (needlen);
+  if (!*out)
+    return true;
+
+  if (!base64_decode_ctx (ctx, in, inlen, *out, &needlen))
+    {
+      free (*out);
+      *out = NULL;
+      return false;
+    }
+
+  if (outlen)
+    *outlen = needlen;
+
+  return true;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/base64/base64.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,60 @@
+/* base64.h -- Encode binary data using printable characters.
+   Copyright (C) 2004-2006, 2009-2012 Free Software Foundation, Inc.
+   Written by Simon Josefsson.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef BASE64_H
+# define BASE64_H
+
+/* Get size_t. */
+# include <stddef.h>
+
+/* Get bool. */
+# include <stdbool.h>
+
+/* This uses that the expression (n+(k-1))/k means the smallest
+   integer >= n/k, i.e., the ceiling of n/k.  */
+# define BASE64_LENGTH(inlen) ((((inlen) + 2) / 3) * 4)
+
+struct base64_decode_context
+{
+  unsigned int i;
+  char buf[4];
+};
+
+extern bool isbase64 (char ch);
+
+extern void base64_encode (const char *restrict in, size_t inlen,
+                           char *restrict out, size_t outlen);
+
+extern size_t base64_encode_alloc (const char *in, size_t inlen, char **out);
+
+extern void base64_decode_ctx_init (struct base64_decode_context *ctx);
+
+extern bool base64_decode_ctx (struct base64_decode_context *ctx,
+                               const char *restrict in, size_t inlen,
+                               char *restrict out, size_t *outlen);
+
+extern bool base64_decode_alloc_ctx (struct base64_decode_context *ctx,
+                                     const char *in, size_t inlen,
+                                     char **out, size_t *outlen);
+
+#define base64_decode(in, inlen, out, outlen) \
+        base64_decode_ctx (NULL, in, inlen, out, outlen)
+
+#define base64_decode_alloc(in, inlen, out, outlen) \
+        base64_decode_alloc_ctx (NULL, in, inlen, out, outlen)
+
+#endif /* BASE64_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/extra/jnacontrol.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,237 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * This file is not directly part of the frontlib and is not required to build it.
+ * However, it is recommended to include it in compilation when building for Android. The purpose of this file
+ * is to ensure consistency between the function signatures of the JNA Java bindings of the Android port and the
+ * frontlib functions.
+ *
+ * This file, in essence, consists only of function declarations. They are duplicates of function declarations
+ * from the frontlib headers that are referenced from JNA bindings. If the signature of one of these functions
+ * changes in the frontlib, it will no longer match the signature in this file, and the compiler will show an error.
+ * If that happens, you need to update the JNA bindings in Hedgeroid to match the new function signature, and then
+ * update this file.
+ *
+ * The reason for all this is that JNA does not actually know the function signatures of the functions it binds,
+ * it derives them from Java method declarations. If those do not match the actual function signatures, you will
+ * only notice when you suddenly get strange (and possibly hard to track down) problems at runtime. This file is
+ * an attempt to detect these problems at compile time instead. Notice that it will NOT detect changes to structs
+ * or constants though, which also require updates to the JNA bindings.
+ */
+
+/*
+ * Before we include the frontlib headers, we define away the const keyword. This is necessary because there is no
+ * distinction between const and non-const types on the JNA side, and we don't want the compiler to complain because
+ * of bad constness.
+ *
+ * This is so evil, but it works...
+ */
+#define const
+
+#include "../frontlib.h"
+
+/*
+ * Now we map the Java types to the corresponding C types...
+ */
+typedef flib_netconn *NetconnPtr;
+typedef flib_gameconn *GameconnPtr;
+typedef flib_mapconn *MapconnPtr;
+typedef flib_metascheme *MetaschemePtr;
+typedef flib_room **RoomArrayPtr;
+typedef flib_weaponset *WeaponsetPtr;
+typedef flib_weaponsetlist *WeaponsetListPtr;
+typedef flib_map *MapRecipePtr;
+typedef flib_scheme *SchemePtr;
+typedef flib_schemelist *SchemelistPtr;
+
+typedef flib_room *RoomPtr;
+typedef flib_team *TeamPtr;
+typedef flib_gamesetup *GameSetupPtr;
+typedef bool boolean;
+typedef size_t NativeLong;
+typedef void *Pointer;
+typedef char *String;
+typedef uint8_t *Buffer;
+
+/*
+ * Mapping callback types
+ */
+typedef void (*VoidCallback)(Pointer context);
+typedef void (*StrCallback)(Pointer context, String arg1);
+typedef void (*IntCallback)(Pointer context, int arg1);
+typedef void (*IntStrCallback)(Pointer context, int arg1, String arg2);
+typedef void (*StrIntCallback)(Pointer context, String arg1, int arg2);
+typedef void (*StrStrCallback)(Pointer context, String arg1, String arg2);
+typedef void (*RoomCallback)(Pointer context, RoomPtr arg1);
+typedef void (*RoomListCallback)(Pointer context, RoomArrayPtr arg1, int arg2);
+typedef void (*StrRoomCallback)(Pointer context, String arg1, RoomPtr arg2);
+typedef void (*BoolCallback)(Pointer context, boolean arg1);
+typedef void (*StrBoolCallback)(Pointer context, String arg1, boolean arg2);
+typedef void (*TeamCallback)(Pointer context, TeamPtr arg1);
+typedef void (*BytesCallback)(Pointer context, const uint8_t *buffer, NativeLong size);
+typedef void (*BytesBoolCallback)(Pointer context, const uint8_t *buffer, NativeLong size, boolean arg3);
+typedef void (*SchemeCallback)(Pointer context, SchemePtr arg1);
+typedef void (*MapIntCallback)(Pointer context, MapRecipePtr arg1, int arg2);
+typedef void (*WeaponsetCallback)(Pointer context, WeaponsetPtr arg1);
+typedef void (*MapimageCallback)(Pointer context, const uint8_t *mapimage, int hogs);
+typedef void (*LogCallback)(int arg1, String arg2);
+
+/*
+ * Below here are the copypasted method declarations from the JNA bindings
+ */
+
+// frontlib.h
+int flib_init();
+void flib_quit();
+
+// hwconsts.h
+int flib_get_teamcolor_count();
+int flib_get_hedgehogs_per_team();
+int flib_get_weapons_count();
+
+// net/netconn.h
+NetconnPtr flib_netconn_create(String playerName, String dataDirPath, String host, int port);
+void flib_netconn_destroy(NetconnPtr conn);
+
+void flib_netconn_tick(NetconnPtr conn);
+boolean flib_netconn_is_chief(NetconnPtr conn);
+String flib_netconn_get_playername(NetconnPtr conn);
+GameSetupPtr flib_netconn_create_gamesetup(NetconnPtr conn);
+int flib_netconn_send_quit(NetconnPtr conn, String quitmsg);
+int flib_netconn_send_chat(NetconnPtr conn, String chat);
+int flib_netconn_send_teamchat(NetconnPtr conn, String msg);
+int flib_netconn_send_password(NetconnPtr conn, String passwd);
+int flib_netconn_send_nick(NetconnPtr conn, String nick);
+int flib_netconn_send_request_roomlist(NetconnPtr conn);
+int flib_netconn_send_joinRoom(NetconnPtr conn, String room);
+int flib_netconn_send_createRoom(NetconnPtr conn, String room);
+int flib_netconn_send_renameRoom(NetconnPtr conn, String roomName);
+int flib_netconn_send_leaveRoom(NetconnPtr conn, String msg);
+int flib_netconn_send_toggleReady(NetconnPtr conn);
+int flib_netconn_send_addTeam(NetconnPtr conn, TeamPtr team);
+int flib_netconn_send_removeTeam(NetconnPtr conn, String teamname);
+int flib_netconn_send_teamHogCount(NetconnPtr conn, String teamname, int hogcount);
+int flib_netconn_send_teamColor(NetconnPtr conn, String teamname, int colorIndex);
+int flib_netconn_send_weaponset(NetconnPtr conn, WeaponsetPtr weaponset);
+int flib_netconn_send_map(NetconnPtr conn, MapRecipePtr map);
+int flib_netconn_send_mapName(NetconnPtr conn, String mapName);
+int flib_netconn_send_mapGen(NetconnPtr conn, int mapGen);
+int flib_netconn_send_mapTemplate(NetconnPtr conn, int templateFilter);
+int flib_netconn_send_mapMazeSize(NetconnPtr conn, int mazeSize);
+int flib_netconn_send_mapSeed(NetconnPtr conn, String seed);
+int flib_netconn_send_mapTheme(NetconnPtr conn, String theme);
+int flib_netconn_send_script(NetconnPtr conn, String scriptName);
+int flib_netconn_send_scheme(NetconnPtr conn, SchemePtr scheme);
+int flib_netconn_send_roundfinished(NetconnPtr conn, boolean withoutError);
+int flib_netconn_send_ban(NetconnPtr conn, String playerName);
+int flib_netconn_send_kick(NetconnPtr conn, String playerName);
+int flib_netconn_send_playerInfo(NetconnPtr conn, String playerName);
+int flib_netconn_send_playerFollow(NetconnPtr conn, String playerName);
+int flib_netconn_send_startGame(NetconnPtr conn);
+int flib_netconn_send_toggleRestrictJoins(NetconnPtr conn);
+int flib_netconn_send_toggleRestrictTeams(NetconnPtr conn);
+int flib_netconn_send_clearAccountsCache(NetconnPtr conn);
+int flib_netconn_send_setServerVar(NetconnPtr conn, String name, String value);
+int flib_netconn_send_getServerVars(NetconnPtr conn);
+
+void flib_netconn_onMessage(NetconnPtr conn, IntStrCallback callback, Pointer context);
+void flib_netconn_onChat(NetconnPtr conn, StrStrCallback callback, Pointer context);
+void flib_netconn_onConnected(NetconnPtr conn, VoidCallback callback, Pointer context);
+void flib_netconn_onDisconnected(NetconnPtr conn, IntStrCallback callback, Pointer context);
+void flib_netconn_onRoomlist(NetconnPtr conn, RoomListCallback callback, Pointer context);
+void flib_netconn_onRoomAdd(NetconnPtr conn, RoomCallback callback, Pointer context);
+void flib_netconn_onRoomDelete(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onRoomUpdate(NetconnPtr conn, StrRoomCallback callback, Pointer context);
+void flib_netconn_onLobbyJoin(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onLobbyLeave(NetconnPtr conn, StrStrCallback callback, Pointer context);
+void flib_netconn_onNickTaken(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onPasswordRequest(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onEnterRoom(NetconnPtr conn, BoolCallback callback, Pointer context);
+void flib_netconn_onRoomChiefStatus(NetconnPtr conn, BoolCallback callback, Pointer context);
+void flib_netconn_onReadyState(NetconnPtr conn, StrBoolCallback callback, Pointer context);
+void flib_netconn_onLeaveRoom(NetconnPtr conn, IntStrCallback callback, Pointer context);
+void flib_netconn_onTeamAdd(NetconnPtr conn, TeamCallback callback, Pointer context);
+void flib_netconn_onTeamDelete(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onRoomJoin(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onRoomLeave(NetconnPtr conn, StrStrCallback callback, Pointer context);
+void flib_netconn_onRunGame(NetconnPtr conn, VoidCallback callback, Pointer context);
+void flib_netconn_onTeamAccepted(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onHogCountChanged(NetconnPtr conn, StrIntCallback callback, Pointer context);
+void flib_netconn_onTeamColorChanged(NetconnPtr conn, StrIntCallback callback, Pointer context);
+void flib_netconn_onEngineMessage(NetconnPtr conn, BytesCallback callback, Pointer context);
+void flib_netconn_onCfgScheme(NetconnPtr conn, SchemeCallback callback, Pointer context);
+void flib_netconn_onMapChanged(NetconnPtr conn, MapIntCallback callback, Pointer context);
+void flib_netconn_onScriptChanged(NetconnPtr conn, StrCallback callback, Pointer context);
+void flib_netconn_onWeaponsetChanged(NetconnPtr conn, WeaponsetCallback callback, Pointer context);
+void flib_netconn_onAdminAccess(NetconnPtr conn, VoidCallback callback, Pointer context);
+void flib_netconn_onServerVar(NetconnPtr conn, StrStrCallback callback, Pointer context);
+
+// ipc/gameconn.h
+GameconnPtr flib_gameconn_create(String playerName, GameSetupPtr setup, boolean netgame);
+GameconnPtr flib_gameconn_create_playdemo(Buffer demo, NativeLong size);
+GameconnPtr flib_gameconn_create_loadgame(String playerName, Buffer save, NativeLong size);
+GameconnPtr flib_gameconn_create_campaign(String playerName, String seed, String script);
+
+void flib_gameconn_destroy(GameconnPtr conn);
+int flib_gameconn_getport(GameconnPtr conn);
+void flib_gameconn_tick(GameconnPtr conn);
+
+int flib_gameconn_send_textmsg(GameconnPtr conn, int msgtype, String msg);
+int flib_gameconn_send_chatmsg(GameconnPtr conn, String playername, String msg);
+int flib_gameconn_send_quit(GameconnPtr conn);
+
+void flib_gameconn_onConnect(GameconnPtr conn, VoidCallback callback, Pointer context);
+void flib_gameconn_onDisconnect(GameconnPtr conn, IntCallback callback, Pointer context);
+void flib_gameconn_onErrorMessage(GameconnPtr conn, StrCallback callback, Pointer context);
+void flib_gameconn_onChat(GameconnPtr conn, StrBoolCallback callback, Pointer context);
+void flib_gameconn_onGameRecorded(GameconnPtr conn, BytesBoolCallback callback, Pointer context);
+void flib_gameconn_onEngineMessage(GameconnPtr conn, BytesCallback callback, Pointer context);
+
+// ipc/mapconn.h
+MapconnPtr flib_mapconn_create(MapRecipePtr mapdesc);
+void flib_mapconn_destroy(MapconnPtr conn);
+int flib_mapconn_getport(MapconnPtr conn);
+void flib_mapconn_onSuccess(MapconnPtr conn, MapimageCallback callback, Pointer context);
+void flib_mapconn_onFailure(MapconnPtr conn, StrCallback callback, Pointer context);
+void flib_mapconn_tick(MapconnPtr conn);
+
+MetaschemePtr flib_get_metascheme();
+
+// model/schemelist.h
+SchemelistPtr flib_schemelist_from_ini(String filename);
+int flib_schemelist_to_ini(String filename, SchemelistPtr list);
+void flib_schemelist_destroy(SchemelistPtr list);
+
+// model/team.h
+TeamPtr flib_team_from_ini(String filename);
+int flib_team_to_ini(String filename, TeamPtr team);
+void flib_team_destroy(TeamPtr team);
+
+// model/weapon.h
+WeaponsetListPtr flib_weaponsetlist_from_ini(String filename);
+int flib_weaponsetlist_to_ini(String filename, WeaponsetListPtr weaponsets);
+void flib_weaponsetlist_destroy(WeaponsetListPtr list);
+
+// model/gamesetup.h
+void flib_gamesetup_destroy(GameSetupPtr gamesetup);
+
+// util/logging.h
+void flib_log_setLevel(int level);
+void flib_log_setCallback(LogCallback callback);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/frontlib.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,37 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "frontlib.h"
+#include "util/logging.h"
+#include <SDL_net.h>
+
+int flib_init() {
+	flib_log_d("Initializing frontlib");
+	if(SDLNet_Init()==-1) {
+		flib_log_e("Error in SDLNet_Init: %s", SDLNet_GetError());
+		return -1;
+	}
+
+	return 0;
+}
+
+void flib_quit() {
+	flib_log_d("Shutting down frontlib");
+	SDLNet_Quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/frontlib.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,48 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * Public header file for the hedgewars frontent networking library.
+ *
+ * This is the only header you should need to include from frontend code.
+ */
+
+#ifndef FRONTLIB_H_
+#define FRONTLIB_H_
+
+#include "ipc/gameconn.h"
+#include "ipc/mapconn.h"
+#include "net/netconn.h"
+#include "util/logging.h"
+#include "model/schemelist.h"
+
+/**
+ * Call this function before anything else in this library.
+ * Returns 0 on success, -1 on error.
+ */
+int flib_init();
+
+/**
+ * Free resources associated with the library. Call this function once
+ * the library is no longer needed. You can re-initialize the library by calling
+ * flib_init again.
+ */
+void flib_quit();
+
+#endif /* FRONTLIB_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/hwconsts.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,100 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "hwconsts.h"
+
+const uint32_t flib_teamcolors[] = HW_TEAMCOLOR_ARRAY;
+const size_t flib_teamcolor_count = sizeof(flib_teamcolors)/sizeof(uint32_t)-1;
+
+static const flib_metascheme_setting metaSchemeSettings[] = {
+	{ .name = "damagefactor",      .times1000 = false, .engineCommand = "e$damagepct",   .maxMeansInfinity = false, .min = 10, .max = 300,  .def = 100 },
+	{ .name = "turntime",          .times1000 = true,  .engineCommand = "e$turntime",    .maxMeansInfinity = true,  .min = 1,  .max = 9999, .def = 45  },
+	{ .name = "health",            .times1000 = false, .engineCommand = NULL,            .maxMeansInfinity = false, .min = 50, .max = 200,  .def = 100 },
+	{ .name = "suddendeath",       .times1000 = false, .engineCommand = "e$sd_turns",    .maxMeansInfinity = true,  .min = 0,  .max = 50,   .def = 15  },
+	{ .name = "caseprobability",   .times1000 = false, .engineCommand = "e$casefreq",    .maxMeansInfinity = false, .min = 0,  .max = 9,    .def = 5   },
+	{ .name = "minestime",         .times1000 = true,  .engineCommand = "e$minestime",   .maxMeansInfinity = false, .min = -1, .max = 5,    .def = 3   },
+	{ .name = "minesnum",          .times1000 = false, .engineCommand = "e$minesnum",    .maxMeansInfinity = false, .min = 0,  .max = 80,   .def = 4   },
+	{ .name = "minedudpct",        .times1000 = false, .engineCommand = "e$minedudpct",  .maxMeansInfinity = false, .min = 0,  .max = 100,  .def = 0   },
+	{ .name = "explosives",        .times1000 = false, .engineCommand = "e$explosives",  .maxMeansInfinity = false, .min = 0,  .max = 40,   .def = 2   },
+	{ .name = "healthprobability", .times1000 = false, .engineCommand = "e$healthprob",  .maxMeansInfinity = false, .min = 0,  .max = 100,  .def = 35  },
+	{ .name = "healthcaseamount",  .times1000 = false, .engineCommand = "e$hcaseamount", .maxMeansInfinity = false, .min = 0,  .max = 200,  .def = 25  },
+	{ .name = "waterrise",         .times1000 = false, .engineCommand = "e$waterrise",   .maxMeansInfinity = false, .min = 0,  .max = 100,  .def = 47  },
+	{ .name = "healthdecrease",    .times1000 = false, .engineCommand = "e$healthdec",   .maxMeansInfinity = false, .min = 0,  .max = 100,  .def = 5   },
+	{ .name = "ropepct",           .times1000 = false, .engineCommand = "e$ropepct",     .maxMeansInfinity = false, .min = 25, .max = 999,  .def = 100 },
+	{ .name = "getawaytime",       .times1000 = false, .engineCommand = "e$getawaytime", .maxMeansInfinity = false, .min = 0,  .max = 999,  .def = 100 }
+};
+
+static const flib_metascheme_mod metaSchemeMods[] = {
+	{ .name = "fortsmode",          .bitmaskIndex = 12 },
+	{ .name = "divteams",           .bitmaskIndex = 4  },
+	{ .name = "solidland",          .bitmaskIndex = 2  },
+	{ .name = "border",             .bitmaskIndex = 3  },
+	{ .name = "lowgrav",            .bitmaskIndex = 5  },
+	{ .name = "laser",              .bitmaskIndex = 6  },
+	{ .name = "invulnerability",    .bitmaskIndex = 7  },
+	{ .name = "resethealth",        .bitmaskIndex = 8  },
+	{ .name = "vampiric",           .bitmaskIndex = 9  },
+	{ .name = "karma",              .bitmaskIndex = 10 },
+	{ .name = "artillery",          .bitmaskIndex = 11 },
+	{ .name = "randomorder",        .bitmaskIndex = 13 },
+	{ .name = "king",               .bitmaskIndex = 14 },
+	{ .name = "placehog",           .bitmaskIndex = 15 },
+	{ .name = "sharedammo",         .bitmaskIndex = 16 },
+	{ .name = "disablegirders",     .bitmaskIndex = 17 },
+	{ .name = "disablelandobjects", .bitmaskIndex = 18 },
+	{ .name = "aisurvival",         .bitmaskIndex = 19 },
+	{ .name = "infattack",          .bitmaskIndex = 20 },
+	{ .name = "resetweps",          .bitmaskIndex = 21 },
+	{ .name = "perhogammo",         .bitmaskIndex = 22 },
+	{ .name = "disablewind",        .bitmaskIndex = 23 },
+	{ .name = "morewind",           .bitmaskIndex = 24 },
+	{ .name = "tagteam",            .bitmaskIndex = 25 },
+	{ .name = "bottomborder",       .bitmaskIndex = 26 }
+};
+
+const flib_metascheme flib_meta = {
+	.settingCount = sizeof(metaSchemeSettings)/sizeof(flib_metascheme_setting),
+	.modCount = sizeof(metaSchemeMods)/sizeof(flib_metascheme_mod),
+	.settings = metaSchemeSettings,
+	.mods = metaSchemeMods
+};
+
+uint32_t flib_get_teamcolor(int colorIndex) {
+	if(colorIndex>=0 && colorIndex < flib_teamcolor_count) {
+		return flib_teamcolors[colorIndex];
+	} else {
+		return 0;
+	}
+}
+
+int flib_get_teamcolor_count() {
+	return flib_teamcolor_count;
+}
+
+int flib_get_hedgehogs_per_team() {
+	return HEDGEHOGS_PER_TEAM;
+}
+
+int flib_get_weapons_count() {
+	return WEAPONS_COUNT;
+}
+
+const flib_metascheme *flib_get_metascheme() {
+	return &flib_meta;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/hwconsts.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,114 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ * Copyright (c) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/**
+ * This file contains important constants which might need to be changed to adapt to
+ * changes in the engine or protocols.
+ *
+ * It also contains getter functions for some constants (in particular for constants
+ * that are important for the layout of data structures), so that client code can
+ * query the constants that the library was built with.
+ */
+
+#ifndef HWCONSTS_H_
+#define HWCONSTS_H_
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#define HEDGEHOGS_PER_TEAM 8
+#define NETGAME_DEFAULT_PORT 46631
+#define PROTOCOL_VERSION 42
+#define MIN_SERVER_VERSION 1
+
+// Used for sending scripts to the engine
+#define MULTIPLAYER_SCRIPT_PATH "Scripts/Multiplayer/"
+
+#define WEAPONS_COUNT 55
+
+/* A merge of mikade/bugq colours w/ a bit of channel feedback */
+#define HW_TEAMCOLOR_ARRAY  { UINT32_C(0xffff0204), /* red    */ \
+                              UINT32_C(0xff4980c1), /* blue   */ \
+                              UINT32_C(0xff1de6ba), /* teal   */ \
+                              UINT32_C(0xffb541ef), /* purple */ \
+                              UINT32_C(0xffe55bb0), /* pink   */ \
+                              UINT32_C(0xff20bf00), /* green  */ \
+                              UINT32_C(0xfffe8b0e), /* orange */ \
+                              UINT32_C(0xff5f3605), /* brown  */ \
+                              UINT32_C(0xffffff01), /* yellow */ \
+                              /* add new colors here */ \
+                              0 } /* Keep this 0 at the end */
+
+extern const size_t flib_teamcolor_count;
+extern const uint32_t flib_teamcolors[];
+
+/**
+ * Returns the team color (ARGB) corresponding to the color index (0 if index out of bounds)
+ */
+uint32_t flib_get_teamcolor(int colorIndex);
+
+/**
+ * Returns the number of team colors (i.e. the length of the flib_teamcolors array)
+ */
+int flib_get_teamcolor_count();
+
+/**
+ * Returns the HEDGEHOGS_PER_TEAM constant
+ */
+int flib_get_hedgehogs_per_team();
+
+/**
+ * Returns the WEAPONS_COUNT constant
+ */
+int flib_get_weapons_count();
+
+/*
+ * These structs define the meaning of values in the flib_scheme struct, i.e. their correspondence to
+ * ini settings, engine commands and positions in the network protocol (the last is encoded in the
+ * order of settings/mods).
+ */
+typedef struct {
+    const char *name;				// A name identifying this setting (used as key in the schemes file)
+    const char *engineCommand;		// The command needed to send the setting to the engine. May be null if the setting is not sent to the engine (for the "health" setting)
+    const bool maxMeansInfinity;	// If true, send a very high number to the engine if the setting is equal to its maximum
+    const bool times1000;			// If true (for time-based settings), multiply the setting by 1000 before sending it to the engine.
+    const int min;					// The smallest allowed value
+    const int max;					// The highest allowed value
+    const int def;					// The default value
+} flib_metascheme_setting;
+
+typedef struct {
+    const char *name;				// A name identifying this mod (used as key in the schemes file)
+    const int bitmaskIndex;			// Mods are sent to the engine in a single integer, this field describes which bit of that integer is used
+    								// for this particular mod.
+} flib_metascheme_mod;
+
+typedef struct {
+	const int settingCount;
+	const int modCount;
+	const flib_metascheme_setting *settings;
+	const flib_metascheme_mod *mods;
+} flib_metascheme;
+
+extern const flib_metascheme flib_meta;
+
+const flib_metascheme *flib_get_metascheme();
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/iniparser/LICENSE	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,23 @@
+Copyright (c) 2000-2012 by Nicolas Devillard.
+MIT License
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/iniparser/VERSION	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,2 @@
+This is version 3.1 of the iniparser library developed by N. Devillard.
+See http://ndevilla.free.fr/iniparser/ for details and new versions.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/iniparser/dictionary.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,398 @@
+/*-------------------------------------------------------------------------*/
+/**
+   @file    dictionary.c
+   @author  N. Devillard
+   @brief   Implements a dictionary for string variables.
+
+   This module implements a simple dictionary object, i.e. a list
+   of string/string associations. This object is useful to store e.g.
+   informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------
+                                Includes
+ ---------------------------------------------------------------------------*/
+#include "dictionary.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/** Maximum value size for integers and doubles. */
+#define MAXVALSZ    1024
+
+/** Minimal allocated number of entries in a dictionary */
+#define DICTMINSZ   128
+
+/** Invalid key token */
+#define DICT_INVALID_KEY    ((char*)-1)
+
+/*---------------------------------------------------------------------------
+                            Private functions
+ ---------------------------------------------------------------------------*/
+
+/* Doubles the allocated size associated to a pointer */
+/* 'size' is the current allocated size. */
+static void * mem_double(void * ptr, int size)
+{
+    void * newptr ;
+ 
+    newptr = calloc(2*size, 1);
+    if (newptr==NULL) {
+        return NULL ;
+    }
+    memcpy(newptr, ptr, size);
+    free(ptr);
+    return newptr ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Duplicate a string
+  @param    s String to duplicate
+  @return   Pointer to a newly allocated string, to be freed with free()
+
+  This is a replacement for strdup(). This implementation is provided
+  for systems that do not have it.
+ */
+/*--------------------------------------------------------------------------*/
+static char * xstrdup(const char * s)
+{
+    char * t ;
+    if (!s)
+        return NULL ;
+    t = (char*)malloc(strlen(s)+1) ;
+    if (t) {
+        strcpy(t,s);
+    }
+    return t ;
+}
+
+/*---------------------------------------------------------------------------
+                            Function codes
+ ---------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Compute the hash key for a string.
+  @param    key     Character string to use for key.
+  @return   1 unsigned int on at least 32 bits.
+
+  This hash function has been taken from an Article in Dr Dobbs Journal.
+  This is normally a collision-free function, distributing keys evenly.
+  The key is stored anyway in the struct so that collision can be avoided
+  by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(const char * key)
+{
+    int         len ;
+    unsigned    hash ;
+    int         i ;
+
+    len = strlen(key);
+    for (hash=0, i=0 ; i<len ; i++) {
+        hash += (unsigned)key[i] ;
+        hash += (hash<<10);
+        hash ^= (hash>>6) ;
+    }
+    hash += (hash <<3);
+    hash ^= (hash >>11);
+    hash += (hash <<15);
+    return hash ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Create a new dictionary object.
+  @param    size    Optional initial size of the dictionary.
+  @return   1 newly allocated dictionary objet.
+
+  This function allocates a new dictionary object of given size and returns
+  it. If you do not know in advance (roughly) the number of entries in the
+  dictionary, give size=0.
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * dictionary_new(int size)
+{
+    dictionary  *   d ;
+
+    /* If no size was specified, allocate space for DICTMINSZ */
+    if (size<DICTMINSZ) size=DICTMINSZ ;
+
+    if (!(d = (dictionary *)calloc(1, sizeof(dictionary)))) {
+        return NULL;
+    }
+    d->size = size ;
+    d->val  = (char **)calloc(size, sizeof(char*));
+    d->key  = (char **)calloc(size, sizeof(char*));
+    d->hash = (unsigned int *)calloc(size, sizeof(unsigned));
+    return d ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Delete a dictionary object
+  @param    d   dictionary object to deallocate.
+  @return   void
+
+  Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary * d)
+{
+    int     i ;
+
+    if (d==NULL) return ;
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]!=NULL)
+            free(d->key[i]);
+        if (d->val[i]!=NULL)
+            free(d->val[i]);
+    }
+    free(d->val);
+    free(d->key);
+    free(d->hash);
+    free(d);
+    return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get a value from a dictionary.
+  @param    d       dictionary object to search.
+  @param    key     Key to look for in the dictionary.
+  @param    def     Default value to return if key not found.
+  @return   1 pointer to internally allocated character string.
+
+  This function locates a key in a dictionary and returns a pointer to its
+  value, or the passed 'def' pointer if no such key can be found in
+  dictionary. The returned character pointer points to data internal to the
+  dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * dictionary_get(dictionary * d, const char * key, char * def)
+{
+    unsigned    hash ;
+    int         i ;
+
+    hash = dictionary_hash(key);
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]==NULL)
+            continue ;
+        /* Compare hash */
+        if (hash==d->hash[i]) {
+            /* Compare string, to avoid hash collisions */
+            if (!strcmp(key, d->key[i])) {
+                return d->val[i] ;
+            }
+        }
+    }
+    return def ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Set a value in a dictionary.
+  @param    d       dictionary object to modify.
+  @param    key     Key to modify or add.
+  @param    val     Value to add.
+  @return   int     0 if Ok, anything else otherwise
+
+  If the given key is found in the dictionary, the associated value is
+  replaced by the provided one. If the key cannot be found in the
+  dictionary, it is added to it.
+
+  It is Ok to provide a NULL value for val, but NULL values for the dictionary
+  or the key are considered as errors: the function will return immediately
+  in such a case.
+
+  Notice that if you dictionary_set a variable to NULL, a call to
+  dictionary_get will return a NULL value: the variable will be found, and
+  its value (NULL) is returned. In other words, setting the variable
+  content to NULL is equivalent to deleting the variable from the
+  dictionary. It is not possible (in this implementation) to have a key in
+  the dictionary without value.
+
+  This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary * d, const char * key, const char * val)
+{
+    int         i ;
+    unsigned    hash ;
+
+    if (d==NULL || key==NULL) return -1 ;
+    
+    /* Compute hash for this key */
+    hash = dictionary_hash(key) ;
+    /* Find if value is already in dictionary */
+    if (d->n>0) {
+        for (i=0 ; i<d->size ; i++) {
+            if (d->key[i]==NULL)
+                continue ;
+            if (hash==d->hash[i]) { /* Same hash value */
+                if (!strcmp(key, d->key[i])) {   /* Same key */
+                    /* Found a value: modify and return */
+                    if (d->val[i]!=NULL)
+                        free(d->val[i]);
+                    d->val[i] = val ? xstrdup(val) : NULL ;
+                    /* Value has been modified: return */
+                    return 0 ;
+                }
+            }
+        }
+    }
+    /* Add a new value */
+    /* See if dictionary needs to grow */
+    if (d->n==d->size) {
+
+        /* Reached maximum size: reallocate dictionary */
+        d->val  = (char **)mem_double(d->val,  d->size * sizeof(char*)) ;
+        d->key  = (char **)mem_double(d->key,  d->size * sizeof(char*)) ;
+        d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ;
+        if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) {
+            /* Cannot grow dictionary */
+            return -1 ;
+        }
+        /* Double size */
+        d->size *= 2 ;
+    }
+
+    /* Insert key in the first empty slot. Start at d->n and wrap at
+       d->size. Because d->n < d->size this will necessarily
+       terminate. */
+    for (i=d->n ; d->key[i] ; ) {
+        if(++i == d->size) i = 0;
+    }
+    /* Copy key */
+    d->key[i]  = xstrdup(key);
+    d->val[i]  = val ? xstrdup(val) : NULL ;
+    d->hash[i] = hash;
+    d->n ++ ;
+    return 0 ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Delete a key in a dictionary
+  @param    d       dictionary object to modify.
+  @param    key     Key to remove.
+  @return   void
+
+  This function deletes a key in a dictionary. Nothing is done if the
+  key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary * d, const char * key)
+{
+    unsigned    hash ;
+    int         i ;
+
+    if (key == NULL) {
+        return;
+    }
+
+    hash = dictionary_hash(key);
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]==NULL)
+            continue ;
+        /* Compare hash */
+        if (hash==d->hash[i]) {
+            /* Compare string, to avoid hash collisions */
+            if (!strcmp(key, d->key[i])) {
+                /* Found key */
+                break ;
+            }
+        }
+    }
+    if (i>=d->size)
+        /* Key not found */
+        return ;
+
+    free(d->key[i]);
+    d->key[i] = NULL ;
+    if (d->val[i]!=NULL) {
+        free(d->val[i]);
+        d->val[i] = NULL ;
+    }
+    d->hash[i] = 0 ;
+    d->n -- ;
+    return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Dump a dictionary to an opened file pointer.
+  @param    d   Dictionary to dump
+  @param    f   Opened file pointer.
+  @return   void
+
+  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+  output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(dictionary * d, FILE * out)
+{
+    int     i ;
+
+    if (d==NULL || out==NULL) return ;
+    if (d->n<1) {
+        fprintf(out, "empty dictionary\n");
+        return ;
+    }
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]) {
+            fprintf(out, "%20s\t[%s]\n",
+                    d->key[i],
+                    d->val[i] ? d->val[i] : "UNDEF");
+        }
+    }
+    return ;
+}
+
+
+/* Test code */
+#ifdef TESTDIC
+#define NVALS 20000
+int main(int argc, char *argv[])
+{
+    dictionary  *   d ;
+    char    *   val ;
+    int         i ;
+    char        cval[90] ;
+
+    /* Allocate dictionary */
+    printf("allocating...\n");
+    d = dictionary_new(0);
+    
+    /* Set values in dictionary */
+    printf("setting %d values...\n", NVALS);
+    for (i=0 ; i<NVALS ; i++) {
+        sprintf(cval, "%04d", i);
+        dictionary_set(d, cval, "salut");
+    }
+    printf("getting %d values...\n", NVALS);
+    for (i=0 ; i<NVALS ; i++) {
+        sprintf(cval, "%04d", i);
+        val = dictionary_get(d, cval, DICT_INVALID_KEY);
+        if (val==DICT_INVALID_KEY) {
+            printf("cannot get value for key [%s]\n", cval);
+        }
+    }
+    printf("unsetting %d values...\n", NVALS);
+    for (i=0 ; i<NVALS ; i++) {
+        sprintf(cval, "%04d", i);
+        dictionary_unset(d, cval);
+    }
+    if (d->n != 0) {
+        printf("error deleting values\n");
+    }
+    printf("deallocating...\n");
+    dictionary_del(d);
+    return 0 ;
+}
+#endif
+/* vim: set ts=4 et sw=4 tw=75 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/iniparser/dictionary.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,165 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+   @file    dictionary.h
+   @author  N. Devillard
+   @brief   Implements a dictionary for string variables.
+
+   This module implements a simple dictionary object, i.e. a list
+   of string/string associations. This object is useful to store e.g.
+   informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+#ifndef _DICTIONARY_H_
+#define _DICTIONARY_H_
+
+/*---------------------------------------------------------------------------
+                                Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*---------------------------------------------------------------------------
+                                New types
+ ---------------------------------------------------------------------------*/
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Dictionary object
+
+  This object contains a list of string/string associations. Each
+  association is identified by a unique string key. Looking up values
+  in the dictionary is speeded up by the use of a (hopefully collision-free)
+  hash function.
+ */
+/*-------------------------------------------------------------------------*/
+typedef struct _dictionary_ {
+    int             n ;     /** Number of entries in dictionary */
+    int             size ;  /** Storage size */
+    char        **  val ;   /** List of string values */
+    char        **  key ;   /** List of string keys */
+    unsigned     *  hash ;  /** List of hash values for keys */
+} dictionary ;
+
+
+/*---------------------------------------------------------------------------
+                            Function prototypes
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Compute the hash key for a string.
+  @param    key     Character string to use for key.
+  @return   1 unsigned int on at least 32 bits.
+
+  This hash function has been taken from an Article in Dr Dobbs Journal.
+  This is normally a collision-free function, distributing keys evenly.
+  The key is stored anyway in the struct so that collision can be avoided
+  by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(const char * key);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Create a new dictionary object.
+  @param    size    Optional initial size of the dictionary.
+  @return   1 newly allocated dictionary objet.
+
+  This function allocates a new dictionary object of given size and returns
+  it. If you do not know in advance (roughly) the number of entries in the
+  dictionary, give size=0.
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * dictionary_new(int size);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Delete a dictionary object
+  @param    d   dictionary object to deallocate.
+  @return   void
+
+  Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary * vd);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get a value from a dictionary.
+  @param    d       dictionary object to search.
+  @param    key     Key to look for in the dictionary.
+  @param    def     Default value to return if key not found.
+  @return   1 pointer to internally allocated character string.
+
+  This function locates a key in a dictionary and returns a pointer to its
+  value, or the passed 'def' pointer if no such key can be found in
+  dictionary. The returned character pointer points to data internal to the
+  dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * dictionary_get(dictionary * d, const char * key, char * def);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Set a value in a dictionary.
+  @param    d       dictionary object to modify.
+  @param    key     Key to modify or add.
+  @param    val     Value to add.
+  @return   int     0 if Ok, anything else otherwise
+
+  If the given key is found in the dictionary, the associated value is
+  replaced by the provided one. If the key cannot be found in the
+  dictionary, it is added to it.
+
+  It is Ok to provide a NULL value for val, but NULL values for the dictionary
+  or the key are considered as errors: the function will return immediately
+  in such a case.
+
+  Notice that if you dictionary_set a variable to NULL, a call to
+  dictionary_get will return a NULL value: the variable will be found, and
+  its value (NULL) is returned. In other words, setting the variable
+  content to NULL is equivalent to deleting the variable from the
+  dictionary. It is not possible (in this implementation) to have a key in
+  the dictionary without value.
+
+  This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary * vd, const char * key, const char * val);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Delete a key in a dictionary
+  @param    d       dictionary object to modify.
+  @param    key     Key to remove.
+  @return   void
+
+  This function deletes a key in a dictionary. Nothing is done if the
+  key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary * d, const char * key);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Dump a dictionary to an opened file pointer.
+  @param    d   Dictionary to dump
+  @param    f   Opened file pointer.
+  @return   void
+
+  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+  output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(dictionary * d, FILE * out);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/iniparser/iniparser.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,748 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+   @file    iniparser.c
+   @author  N. Devillard
+   @brief   Parser for ini files.
+*/
+/*--------------------------------------------------------------------------*/
+/*---------------------------- Includes ------------------------------------*/
+#include <ctype.h>
+#include "iniparser.h"
+
+/*---------------------------- Defines -------------------------------------*/
+#define ASCIILINESZ         (1024)
+#define INI_INVALID_KEY     ((char*)-1)
+
+/*---------------------------------------------------------------------------
+                        Private to this module
+ ---------------------------------------------------------------------------*/
+/**
+ * This enum stores the status for each parsed line (internal use only).
+ */
+typedef enum _line_status_ {
+    LINE_UNPROCESSED,
+    LINE_ERROR,
+    LINE_EMPTY,
+    LINE_COMMENT,
+    LINE_SECTION,
+    LINE_VALUE
+} line_status ;
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Convert a string to lowercase.
+  @param    s   String to convert.
+  @return   ptr to statically allocated string.
+
+  This function returns a pointer to a statically allocated string
+  containing a lowercased version of the input string. Do not free
+  or modify the returned string! Since the returned string is statically
+  allocated, it will be modified at each function call (not re-entrant).
+ */
+/*--------------------------------------------------------------------------*/
+static char * strlwc(const char * s)
+{
+    static char l[ASCIILINESZ+1];
+    int i ;
+
+    if (s==NULL) return NULL ;
+    memset(l, 0, ASCIILINESZ+1);
+    i=0 ;
+    while (s[i] && i<ASCIILINESZ) {
+        l[i] = (char)tolower((int)s[i]);
+        i++ ;
+    }
+    l[ASCIILINESZ]=(char)0;
+    return l ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Remove blanks at the beginning and the end of a string.
+  @param    s   String to parse.
+  @return   ptr to statically allocated string.
+
+  This function returns a pointer to a statically allocated string,
+  which is identical to the input string, except that all blank
+  characters at the end and the beg. of the string have been removed.
+  Do not free or modify the returned string! Since the returned string
+  is statically allocated, it will be modified at each function call
+  (not re-entrant).
+ */
+/*--------------------------------------------------------------------------*/
+static char * strstrip(const char * s)
+{
+    static char l[ASCIILINESZ+1];
+    char * last ;
+    
+    if (s==NULL) return NULL ;
+    
+    while (isspace((int)*s) && *s) s++;
+    memset(l, 0, ASCIILINESZ+1);
+    strcpy(l, s);
+    last = l + strlen(l);
+    while (last > l) {
+        if (!isspace((int)*(last-1)))
+            break ;
+        last -- ;
+    }
+    *last = (char)0;
+    return (char*)l ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get number of sections in a dictionary
+  @param    d   Dictionary to examine
+  @return   int Number of sections found in dictionary
+
+  This function returns the number of sections found in a dictionary.
+  The test to recognize sections is done on the string stored in the
+  dictionary: a section name is given as "section" whereas a key is
+  stored as "section:key", thus the test looks for entries that do not
+  contain a colon.
+
+  This clearly fails in the case a section name contains a colon, but
+  this should simply be avoided.
+
+  This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getnsec(dictionary * d)
+{
+    int i ;
+    int nsec ;
+
+    if (d==NULL) return -1 ;
+    nsec=0 ;
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]==NULL)
+            continue ;
+        if (strchr(d->key[i], ':')==NULL) {
+            nsec ++ ;
+        }
+    }
+    return nsec ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get name for section n in a dictionary.
+  @param    d   Dictionary to examine
+  @param    n   Section number (from 0 to nsec-1).
+  @return   Pointer to char string
+
+  This function locates the n-th section in a dictionary and returns
+  its name as a pointer to a string statically allocated inside the
+  dictionary. Do not free or modify the returned string!
+
+  This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+char * iniparser_getsecname(dictionary * d, int n)
+{
+    int i ;
+    int foundsec ;
+
+    if (d==NULL || n<0) return NULL ;
+    foundsec=0 ;
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]==NULL)
+            continue ;
+        if (strchr(d->key[i], ':')==NULL) {
+            foundsec++ ;
+            if (foundsec>n)
+                break ;
+        }
+    }
+    if (foundsec<=n) {
+        return NULL ;
+    }
+    return d->key[i] ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Dump a dictionary to an opened file pointer.
+  @param    d   Dictionary to dump.
+  @param    f   Opened file pointer to dump to.
+  @return   void
+
+  This function prints out the contents of a dictionary, one element by
+  line, onto the provided file pointer. It is OK to specify @c stderr
+  or @c stdout as output files. This function is meant for debugging
+  purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(dictionary * d, FILE * f)
+{
+    int     i ;
+
+    if (d==NULL || f==NULL) return ;
+    for (i=0 ; i<d->size ; i++) {
+        if (d->key[i]==NULL)
+            continue ;
+        if (d->val[i]!=NULL) {
+            fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
+        } else {
+            fprintf(f, "[%s]=UNDEF\n", d->key[i]);
+        }
+    }
+    return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Save a dictionary to a loadable ini file
+  @param    d   Dictionary to dump
+  @param    f   Opened file pointer to dump to
+  @return   void
+
+  This function dumps a given dictionary into a loadable ini file.
+  It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump_ini(dictionary * d, FILE * f)
+{
+    int     i ;
+    int     nsec ;
+    char *  secname ;
+
+    if (d==NULL || f==NULL) return ;
+
+    nsec = iniparser_getnsec(d);
+    if (nsec<1) {
+        /* No section in file: dump all keys as they are */
+        for (i=0 ; i<d->size ; i++) {
+            if (d->key[i]==NULL)
+                continue ;
+            fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
+        }
+        return ;
+    }
+    for (i=0 ; i<nsec ; i++) {
+        secname = iniparser_getsecname(d, i) ;
+        iniparser_dumpsection_ini(d, secname, f) ;
+    }
+    fprintf(f, "\n");
+    return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Save a dictionary section to a loadable ini file
+  @param    d   Dictionary to dump
+  @param    s   Section name of dictionary to dump
+  @param    f   Opened file pointer to dump to
+  @return   void
+
+  This function dumps a given section of a given dictionary into a loadable ini
+  file.  It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f)
+{
+    int     j ;
+    char    keym[ASCIILINESZ+1];
+    int     seclen ;
+
+    if (d==NULL || f==NULL) return ;
+    if (! iniparser_find_entry(d, s)) return ;
+
+    seclen  = (int)strlen(s);
+    fprintf(f, "\n[%s]\n", s);
+    sprintf(keym, "%s:", s);
+    for (j=0 ; j<d->size ; j++) {
+        if (d->key[j]==NULL)
+            continue ;
+        if (!strncmp(d->key[j], keym, seclen+1)) {
+            fprintf(f,
+                    "%-30s = %s\n",
+                    d->key[j]+seclen+1,
+                    d->val[j] ? d->val[j] : "");
+        }
+    }
+    fprintf(f, "\n");
+    return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the number of keys in a section of a dictionary.
+  @param    d   Dictionary to examine
+  @param    s   Section name of dictionary to examine
+  @return   Number of keys in section
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getsecnkeys(dictionary * d, char * s)
+{
+    int     seclen, nkeys ;
+    char    keym[ASCIILINESZ+1];
+    int j ;
+
+    nkeys = 0;
+
+    if (d==NULL) return nkeys;
+    if (! iniparser_find_entry(d, s)) return nkeys;
+
+    seclen  = (int)strlen(s);
+    sprintf(keym, "%s:", s);
+
+    for (j=0 ; j<d->size ; j++) {
+        if (d->key[j]==NULL)
+            continue ;
+        if (!strncmp(d->key[j], keym, seclen+1)) 
+            nkeys++;
+    }
+
+    return nkeys;
+
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the number of keys in a section of a dictionary.
+  @param    d   Dictionary to examine
+  @param    s   Section name of dictionary to examine
+  @return   pointer to statically allocated character strings
+
+  This function queries a dictionary and finds all keys in a given section.
+  Each pointer in the returned char pointer-to-pointer is pointing to
+  a string allocated in the dictionary; do not free or modify them.
+  
+  This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+char ** iniparser_getseckeys(dictionary * d, char * s)
+{
+
+    char **keys;
+
+    int i, j ;
+    char    keym[ASCIILINESZ+1];
+    int     seclen, nkeys ;
+
+    keys = NULL;
+
+    if (d==NULL) return keys;
+    if (! iniparser_find_entry(d, s)) return keys;
+
+    nkeys = iniparser_getsecnkeys(d, s);
+
+    keys = (char**) malloc(nkeys*sizeof(char*));
+
+    seclen  = (int)strlen(s);
+    sprintf(keym, "%s:", s);
+    
+    i = 0;
+
+    for (j=0 ; j<d->size ; j++) {
+        if (d->key[j]==NULL)
+            continue ;
+        if (!strncmp(d->key[j], keym, seclen+1)) {
+            keys[i] = d->key[j];
+            i++;
+        }
+    }
+
+    return keys;
+
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key
+  @param    d       Dictionary to search
+  @param    key     Key string to look for
+  @param    def     Default value to return if key not found.
+  @return   pointer to statically allocated character string
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the pointer passed as 'def' is returned.
+  The returned char pointer is pointing to a string allocated in
+  the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * iniparser_getstring(dictionary * d, const char * key, char * def)
+{
+    char * lc_key ;
+    char * sval ;
+
+    if (d==NULL || key==NULL)
+        return def ;
+
+    lc_key = strlwc(key);
+    sval = dictionary_get(d, lc_key, def);
+    return sval ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key, convert to an int
+  @param    d Dictionary to search
+  @param    key Key string to look for
+  @param    notfound Value to return in case of error
+  @return   integer
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the notfound value is returned.
+
+  Supported values for integers include the usual C notation
+  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+  are supported. Examples:
+
+  "42"      ->  42
+  "042"     ->  34 (octal -> decimal)
+  "0x42"    ->  66 (hexa  -> decimal)
+
+  Warning: the conversion may overflow in various ways. Conversion is
+  totally outsourced to strtol(), see the associated man page for overflow
+  handling.
+
+  Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(dictionary * d, const char * key, int notfound)
+{
+    char    *   str ;
+
+    str = iniparser_getstring(d, key, INI_INVALID_KEY);
+    if (str==INI_INVALID_KEY) return notfound ;
+    return (int)strtol(str, NULL, 0);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key, convert to a double
+  @param    d Dictionary to search
+  @param    key Key string to look for
+  @param    notfound Value to return in case of error
+  @return   double
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(dictionary * d, const char * key, double notfound)
+{
+    char    *   str ;
+
+    str = iniparser_getstring(d, key, INI_INVALID_KEY);
+    if (str==INI_INVALID_KEY) return notfound ;
+    return atof(str);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key, convert to a boolean
+  @param    d Dictionary to search
+  @param    key Key string to look for
+  @param    notfound Value to return in case of error
+  @return   integer
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the notfound value is returned.
+
+  A true boolean is found if one of the following is matched:
+
+  - A string starting with 'y'
+  - A string starting with 'Y'
+  - A string starting with 't'
+  - A string starting with 'T'
+  - A string starting with '1'
+
+  A false boolean is found if one of the following is matched:
+
+  - A string starting with 'n'
+  - A string starting with 'N'
+  - A string starting with 'f'
+  - A string starting with 'F'
+  - A string starting with '0'
+
+  The notfound value returned if no boolean is identified, does not
+  necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(dictionary * d, const char * key, int notfound)
+{
+    char    *   c ;
+    int         ret ;
+
+    c = iniparser_getstring(d, key, INI_INVALID_KEY);
+    if (c==INI_INVALID_KEY) return notfound ;
+    if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
+        ret = 1 ;
+    } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
+        ret = 0 ;
+    } else {
+        ret = notfound ;
+    }
+    return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Finds out if a given entry exists in a dictionary
+  @param    ini     Dictionary to search
+  @param    entry   Name of the entry to look for
+  @return   integer 1 if entry exists, 0 otherwise
+
+  Finds out if a given entry exists in the dictionary. Since sections
+  are stored as keys with NULL associated values, this is the only way
+  of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(
+    dictionary  *   ini,
+    const char  *   entry
+)
+{
+    int found=0 ;
+    if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
+        found = 1 ;
+    }
+    return found ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Set an entry in a dictionary.
+  @param    ini     Dictionary to modify.
+  @param    entry   Entry to modify (entry name)
+  @param    val     New value to associate to the entry.
+  @return   int 0 if Ok, -1 otherwise.
+
+  If the given entry can be found in the dictionary, it is modified to
+  contain the provided value. If it cannot be found, -1 is returned.
+  It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary * ini, const char * entry, const char * val)
+{
+    return dictionary_set(ini, strlwc(entry), val) ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Delete an entry in a dictionary
+  @param    ini     Dictionary to modify
+  @param    entry   Entry to delete (entry name)
+  @return   void
+
+  If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary * ini, const char * entry)
+{
+    dictionary_unset(ini, strlwc(entry));
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Load a single line from an INI file
+  @param    input_line  Input line, may be concatenated multi-line input
+  @param    section     Output space to store section
+  @param    key         Output space to store key
+  @param    value       Output space to store value
+  @return   line_status value
+ */
+/*--------------------------------------------------------------------------*/
+static line_status iniparser_line(
+    const char * input_line,
+    char * section,
+    char * key,
+    char * value)
+{   
+    line_status sta ;
+    char        line[ASCIILINESZ+1];
+    int         len ;
+
+    strcpy(line, strstrip(input_line));
+    len = (int)strlen(line);
+
+    sta = LINE_UNPROCESSED ;
+    if (len<1) {
+        /* Empty line */
+        sta = LINE_EMPTY ;
+    } else if (line[0]=='#' || line[0]==';') {
+        /* Comment line */
+        sta = LINE_COMMENT ; 
+    } else if (line[0]=='[' && line[len-1]==']') {
+        /* Section name */
+        sscanf(line, "[%[^]]", section);
+        strcpy(section, strstrip(section));
+        strcpy(section, strlwc(section));
+        sta = LINE_SECTION ;
+    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
+           ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
+           ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
+        /* Usual key=value, with or without comments */
+        strcpy(key, strstrip(key));
+        strcpy(key, strlwc(key));
+        strcpy(value, strstrip(value));
+        /*
+         * sscanf cannot handle '' or "" as empty values
+         * this is done here
+         */
+        if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
+            value[0]=0 ;
+        }
+        sta = LINE_VALUE ;
+    } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
+           ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
+        /*
+         * Special cases:
+         * key=
+         * key=;
+         * key=#
+         */
+        strcpy(key, strstrip(key));
+        strcpy(key, strlwc(key));
+        value[0]=0 ;
+        sta = LINE_VALUE ;
+    } else {
+        /* Generate syntax error */
+        sta = LINE_ERROR ;
+    }
+    return sta ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Parse an ini file and return an allocated dictionary object
+  @param    ininame Name of the ini file to read.
+  @return   Pointer to newly allocated dictionary
+
+  This is the parser for ini files. This function is called, providing
+  the name of the file to be read. It returns a dictionary object that
+  should not be accessed directly, but through accessor functions
+  instead.
+
+  The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * iniparser_load(const char * ininame)
+{
+    FILE * in ;
+
+    char line    [ASCIILINESZ+1] ;
+    char section [ASCIILINESZ+1] ;
+    char key     [ASCIILINESZ+1] ;
+    char tmp     [ASCIILINESZ+1] ;
+    char val     [ASCIILINESZ+1] ;
+
+    int  last=0 ;
+    int  len ;
+    int  lineno=0 ;
+    int  errs=0;
+
+    dictionary * dict ;
+
+    if ((in=fopen(ininame, "r"))==NULL) {
+        fprintf(stderr, "iniparser: cannot open %s\n", ininame);
+        return NULL ;
+    }
+
+    dict = dictionary_new(0) ;
+    if (!dict) {
+        fclose(in);
+        return NULL ;
+    }
+
+    memset(line,    0, ASCIILINESZ);
+    memset(section, 0, ASCIILINESZ);
+    memset(key,     0, ASCIILINESZ);
+    memset(val,     0, ASCIILINESZ);
+    last=0 ;
+
+    while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
+        lineno++ ;
+        len = (int)strlen(line)-1;
+        if (len==0)
+            continue;
+        /* Safety check against buffer overflows */
+        if (line[len]!='\n') {
+            fprintf(stderr,
+                    "iniparser: input line too long in %s (%d)\n",
+                    ininame,
+                    lineno);
+            dictionary_del(dict);
+            fclose(in);
+            return NULL ;
+        }
+        /* Get rid of \n and spaces at end of line */
+        while ((len>=0) &&
+                ((line[len]=='\n') || (isspace(line[len])))) {
+            line[len]=0 ;
+            len-- ;
+        }
+        /* Detect multi-line */
+        if (line[len]=='\\') {
+            /* Multi-line value */
+            last=len ;
+            continue ;
+        } else {
+            last=0 ;
+        }
+        switch (iniparser_line(line, section, key, val)) {
+            case LINE_EMPTY:
+            case LINE_COMMENT:
+            break ;
+
+            case LINE_SECTION:
+            errs = dictionary_set(dict, section, NULL);
+            break ;
+
+            case LINE_VALUE:
+            sprintf(tmp, "%s:%s", section, key);
+            errs = dictionary_set(dict, tmp, val) ;
+            break ;
+
+            case LINE_ERROR:
+            fprintf(stderr, "iniparser: syntax error in %s (%d):\n",
+                    ininame,
+                    lineno);
+            fprintf(stderr, "-> %s\n", line);
+            errs++ ;
+            break;
+
+            default:
+            break ;
+        }
+        memset(line, 0, ASCIILINESZ);
+        last=0;
+        if (errs<0) {
+            fprintf(stderr, "iniparser: memory allocation failure\n");
+            break ;
+        }
+    }
+    if (errs) {
+        dictionary_del(dict);
+        dict = NULL ;
+    }
+    fclose(in);
+    return dict ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Free all memory associated to an ini dictionary
+  @param    d Dictionary to free
+  @return   void
+
+  Free all memory associated to an ini dictionary.
+  It is mandatory to call this function before the dictionary object
+  gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary * d)
+{
+    dictionary_del(d);
+}
+
+/* vim: set ts=4 et sw=4 tw=75 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/iniparser/iniparser.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,307 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+   @file    iniparser.h
+   @author  N. Devillard
+   @brief   Parser for ini files.
+*/
+/*--------------------------------------------------------------------------*/
+
+#ifndef _INIPARSER_H_
+#define _INIPARSER_H_
+
+/*---------------------------------------------------------------------------
+                                Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * The following #include is necessary on many Unixes but not Linux.
+ * It is not needed for Windows platforms.
+ * Uncomment it if needed.
+ */
+/* #include <unistd.h> */
+
+#include "dictionary.h"
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get number of sections in a dictionary
+  @param    d   Dictionary to examine
+  @return   int Number of sections found in dictionary
+
+  This function returns the number of sections found in a dictionary.
+  The test to recognize sections is done on the string stored in the
+  dictionary: a section name is given as "section" whereas a key is
+  stored as "section:key", thus the test looks for entries that do not
+  contain a colon.
+
+  This clearly fails in the case a section name contains a colon, but
+  this should simply be avoided.
+
+  This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+int iniparser_getnsec(dictionary * d);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get name for section n in a dictionary.
+  @param    d   Dictionary to examine
+  @param    n   Section number (from 0 to nsec-1).
+  @return   Pointer to char string
+
+  This function locates the n-th section in a dictionary and returns
+  its name as a pointer to a string statically allocated inside the
+  dictionary. Do not free or modify the returned string!
+
+  This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+char * iniparser_getsecname(dictionary * d, int n);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Save a dictionary to a loadable ini file
+  @param    d   Dictionary to dump
+  @param    f   Opened file pointer to dump to
+  @return   void
+
+  This function dumps a given dictionary into a loadable ini file.
+  It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dump_ini(dictionary * d, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Save a dictionary section to a loadable ini file
+  @param    d   Dictionary to dump
+  @param    s   Section name of dictionary to dump
+  @param    f   Opened file pointer to dump to
+  @return   void
+
+  This function dumps a given section of a given dictionary into a loadable ini
+  file.  It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Dump a dictionary to an opened file pointer.
+  @param    d   Dictionary to dump.
+  @param    f   Opened file pointer to dump to.
+  @return   void
+
+  This function prints out the contents of a dictionary, one element by
+  line, onto the provided file pointer. It is OK to specify @c stderr
+  or @c stdout as output files. This function is meant for debugging
+  purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(dictionary * d, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the number of keys in a section of a dictionary.
+  @param    d   Dictionary to examine
+  @param    s   Section name of dictionary to examine
+  @return   Number of keys in section
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getsecnkeys(dictionary * d, char * s);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the number of keys in a section of a dictionary.
+  @param    d   Dictionary to examine
+  @param    s   Section name of dictionary to examine
+  @return   pointer to statically allocated character strings
+
+  This function queries a dictionary and finds all keys in a given section.
+  Each pointer in the returned char pointer-to-pointer is pointing to
+  a string allocated in the dictionary; do not free or modify them.
+
+  This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+char ** iniparser_getseckeys(dictionary * d, char * s);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key
+  @param    d       Dictionary to search
+  @param    key     Key string to look for
+  @param    def     Default value to return if key not found.
+  @return   pointer to statically allocated character string
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the pointer passed as 'def' is returned.
+  The returned char pointer is pointing to a string allocated in
+  the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+char * iniparser_getstring(dictionary * d, const char * key, char * def);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key, convert to an int
+  @param    d Dictionary to search
+  @param    key Key string to look for
+  @param    notfound Value to return in case of error
+  @return   integer
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the notfound value is returned.
+
+  Supported values for integers include the usual C notation
+  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+  are supported. Examples:
+
+  - "42"      ->  42
+  - "042"     ->  34 (octal -> decimal)
+  - "0x42"    ->  66 (hexa  -> decimal)
+
+  Warning: the conversion may overflow in various ways. Conversion is
+  totally outsourced to strtol(), see the associated man page for overflow
+  handling.
+
+  Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(dictionary * d, const char * key, int notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key, convert to a double
+  @param    d Dictionary to search
+  @param    key Key string to look for
+  @param    notfound Value to return in case of error
+  @return   double
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(dictionary * d, const char * key, double notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Get the string associated to a key, convert to a boolean
+  @param    d Dictionary to search
+  @param    key Key string to look for
+  @param    notfound Value to return in case of error
+  @return   integer
+
+  This function queries a dictionary for a key. A key as read from an
+  ini file is given as "section:key". If the key cannot be found,
+  the notfound value is returned.
+
+  A true boolean is found if one of the following is matched:
+
+  - A string starting with 'y'
+  - A string starting with 'Y'
+  - A string starting with 't'
+  - A string starting with 'T'
+  - A string starting with '1'
+
+  A false boolean is found if one of the following is matched:
+
+  - A string starting with 'n'
+  - A string starting with 'N'
+  - A string starting with 'f'
+  - A string starting with 'F'
+  - A string starting with '0'
+
+  The notfound value returned if no boolean is identified, does not
+  necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(dictionary * d, const char * key, int notfound);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Set an entry in a dictionary.
+  @param    ini     Dictionary to modify.
+  @param    entry   Entry to modify (entry name)
+  @param    val     New value to associate to the entry.
+  @return   int 0 if Ok, -1 otherwise.
+
+  If the given entry can be found in the dictionary, it is modified to
+  contain the provided value. If it cannot be found, -1 is returned.
+  It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary * ini, const char * entry, const char * val);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Delete an entry in a dictionary
+  @param    ini     Dictionary to modify
+  @param    entry   Entry to delete (entry name)
+  @return   void
+
+  If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary * ini, const char * entry);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Finds out if a given entry exists in a dictionary
+  @param    ini     Dictionary to search
+  @param    entry   Name of the entry to look for
+  @return   integer 1 if entry exists, 0 otherwise
+
+  Finds out if a given entry exists in the dictionary. Since sections
+  are stored as keys with NULL associated values, this is the only way
+  of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(dictionary * ini, const char * entry) ;
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Parse an ini file and return an allocated dictionary object
+  @param    ininame Name of the ini file to read.
+  @return   Pointer to newly allocated dictionary
+
+  This is the parser for ini files. This function is called, providing
+  the name of the file to be read. It returns a dictionary object that
+  should not be accessed directly, but through accessor functions
+  instead.
+
+  The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * iniparser_load(const char * ininame);
+
+/*-------------------------------------------------------------------------*/
+/**
+  @brief    Free all memory associated to an ini dictionary
+  @param    d Dictionary to free
+  @return   void
+
+  Free all memory associated to an ini dictionary.
+  It is mandatory to call this function before the dictionary object
+  gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary * d);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/gameconn.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,441 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "gameconn.h"
+#include "ipcbase.h"
+#include "ipcprotocol.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../hwconsts.h"
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef enum {
+	AWAIT_CONNECTION,
+	CONNECTED,
+	FINISHED
+} gameconn_state;
+
+struct _flib_gameconn {
+	flib_ipcbase *ipcBase;
+	flib_vector *configBuffer;
+	flib_vector *demoBuffer;
+	char *playerName;
+
+	gameconn_state state;
+	bool netgame;
+
+	void (*onConnectCb)(void* context);
+	void *onConnectCtx;
+
+	void (*onDisconnectCb)(void* context, int reason);
+	void *onDisconnectCtx;
+
+	void (*onErrorMessageCb)(void* context, const char *msg);
+	void *onErrorMessageCtx;
+
+	void (*onChatCb)(void* context, const char *msg, bool teamchat);
+	void *onChatCtx;
+
+	void (*onGameRecordedCb)(void *context, const uint8_t *record, size_t size, bool isSavegame);
+	void *onGameRecordedCtx;
+
+	void (*onEngineMessageCb)(void *context, const uint8_t *em, size_t size);
+	void *onEngineMessageCtx;
+
+	bool running;
+	bool destroyRequested;
+};
+
+static void defaultCallback_onErrorMessage(void* context, const char *msg) {
+	flib_log_w("Error from engine (no callback set): %s", msg);
+}
+
+static void clearCallbacks(flib_gameconn *conn) {
+	flib_gameconn_onConnect(conn, NULL, NULL);
+	flib_gameconn_onDisconnect(conn, NULL, NULL);
+	flib_gameconn_onErrorMessage(conn, NULL, NULL);
+	flib_gameconn_onChat(conn, NULL, NULL);
+	flib_gameconn_onGameRecorded(conn, NULL, NULL);
+	flib_gameconn_onEngineMessage(conn, NULL, NULL);
+}
+
+static flib_gameconn *flib_gameconn_create_partial(bool record, const char *playerName, bool netGame) {
+	flib_gameconn *result = NULL;
+	flib_gameconn *tempConn = flib_calloc(1, sizeof(flib_gameconn));
+	if(tempConn) {
+		tempConn->ipcBase = flib_ipcbase_create();
+		tempConn->configBuffer = flib_vector_create();
+		tempConn->playerName = flib_strdupnull(playerName);
+		if(tempConn->ipcBase && tempConn->configBuffer && tempConn->playerName) {
+			if(record) {
+				tempConn->demoBuffer = flib_vector_create();
+			}
+			tempConn->state = AWAIT_CONNECTION;
+			tempConn->netgame = netGame;
+			clearCallbacks(tempConn);
+			result = tempConn;
+			tempConn = NULL;
+		}
+	}
+	flib_gameconn_destroy(tempConn);
+	return result;
+}
+
+flib_gameconn *flib_gameconn_create(const char *playerName, const flib_gamesetup *setup, bool netgame) {
+	if(log_badargs_if2(playerName==NULL, setup==NULL)) {
+		return NULL;
+	}
+	flib_gameconn *result = NULL;
+	flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, netgame);
+	if(tempConn) {
+		if(flib_ipc_append_fullconfig(tempConn->configBuffer, setup, netgame)) {
+			flib_log_e("Error generating full game configuration for the engine.");
+		} else {
+			result = tempConn;
+			tempConn = NULL;
+		}
+	}
+	flib_gameconn_destroy(tempConn);
+	return result;
+}
+
+flib_gameconn *flib_gameconn_create_playdemo(const uint8_t *demoFileContent, size_t size) {
+	if(log_badargs_if(demoFileContent==NULL && size>0)) {
+		return NULL;
+	}
+	flib_gameconn *result = NULL;
+	flib_gameconn *tempConn = flib_gameconn_create_partial(false, "Player", false);
+	if(tempConn) {
+		if(!flib_vector_append(tempConn->configBuffer, demoFileContent, size)) {
+			result = tempConn;
+			tempConn = NULL;
+		}
+	}
+	flib_gameconn_destroy(tempConn);
+	return result;
+}
+
+flib_gameconn *flib_gameconn_create_loadgame(const char *playerName, const uint8_t *saveFileContent, size_t size) {
+	if(log_badargs_if(saveFileContent==NULL && size>0)) {
+		return NULL;
+	}
+	flib_gameconn *result = NULL;
+	flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, false);
+	if(tempConn) {
+		if(!flib_vector_append(tempConn->configBuffer, saveFileContent, size)) {
+			result = tempConn;
+			tempConn = NULL;
+		}
+	}
+	flib_gameconn_destroy(tempConn);
+	return result;
+}
+
+flib_gameconn *flib_gameconn_create_campaign(const char *playerName, const char *seed, const char *script) {
+	if(log_badargs_if3(playerName==NULL, seed==NULL, script==NULL)) {
+		return NULL;
+	}
+	flib_gameconn *result = NULL;
+	flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, false);
+	if(tempConn) {
+		if(!flib_ipc_append_message(tempConn->configBuffer, "TL")
+				&& !flib_ipc_append_seed(tempConn->configBuffer, seed)
+				&& !flib_ipc_append_script(tempConn->configBuffer, script)
+				&& !flib_ipc_append_message(tempConn->configBuffer, "!")) {
+			result = tempConn;
+			tempConn = NULL;
+		}
+	}
+	flib_gameconn_destroy(tempConn);
+	return result;
+}
+
+void flib_gameconn_destroy(flib_gameconn *conn) {
+	if(conn) {
+		if(conn->running) {
+			/*
+			 * The function was called from a callback, so the tick function is still running
+			 * and we delay the actual destruction. We ensure no further callbacks will be
+			 * sent to prevent surprises.
+			 */
+			clearCallbacks(conn);
+			conn->destroyRequested = true;
+		} else {
+			flib_ipcbase_destroy(conn->ipcBase);
+			flib_vector_destroy(conn->configBuffer);
+			flib_vector_destroy(conn->demoBuffer);
+			free(conn->playerName);
+			free(conn);
+		}
+	}
+}
+
+int flib_gameconn_getport(flib_gameconn *conn) {
+	if(log_badargs_if(conn==NULL)) {
+		return 0;
+	}
+	return flib_ipcbase_port(conn->ipcBase);
+}
+
+static void demo_append(flib_gameconn *conn, const void *data, size_t len) {
+	if(conn->demoBuffer) {
+		if(flib_vector_append(conn->demoBuffer, data, len)) {
+			flib_log_e("Error recording demo: Out of memory.");
+			flib_vector_destroy(conn->demoBuffer);
+			conn->demoBuffer = NULL;
+		}
+	}
+}
+
+static int format_chatmessage(uint8_t buffer[257], const char *playerName, const char *message) {
+	size_t msglen = strlen(message);
+
+	// If the message starts with /me, it will be displayed differently.
+	bool meMessage = msglen >= 4 && !memcmp(message, "/me ", 4);
+	const char *template = meMessage ? "s\x02* %s %s  " : "s\x01%s: %s  ";
+	int size = snprintf((char*)buffer+1, 256, template, playerName, meMessage ? message+4 : message);
+	if(log_e_if(size<=0, "printf error")) {
+		return -1;
+	} else {
+		buffer[0] = size>255 ? 255 : size;
+		return 0;
+	}
+}
+
+static void demo_append_chatmessage(flib_gameconn *conn, const char *message) {
+	// Chat messages are reformatted to make them look as if they were received, not sent.
+	uint8_t converted[257];
+	if(!format_chatmessage(converted, conn->playerName, message)) {
+		demo_append(conn, converted, converted[0]+1);
+	}
+}
+
+static void demo_replace_gamemode(flib_buffer buf, char gamemode) {
+	size_t msgStart = 0;
+	uint8_t *data = (uint8_t*)buf.data;
+	while(msgStart+2 < buf.size) {
+		if(!memcmp(data+msgStart, "\x02T", 2)) {
+			data[msgStart+2] = gamemode;
+		}
+		msgStart += (uint8_t)data[msgStart]+1;
+	}
+}
+
+int flib_gameconn_send_enginemsg(flib_gameconn *conn, const uint8_t *data, size_t len) {
+	if(log_badargs_if2(conn==NULL, data==NULL && len>0)) {
+		return -1;
+	}
+	int result = flib_ipcbase_send_raw(conn->ipcBase, data, len);
+	if(!result) {
+		demo_append(conn, data, len);
+	}
+	return result;
+}
+
+int flib_gameconn_send_textmsg(flib_gameconn *conn, int msgtype, const char *msg) {
+	if(log_badargs_if2(conn==NULL, msg==NULL)) {
+		return -1;
+	}
+	int result = -1;
+	uint8_t converted[257];
+	int size = snprintf((char*)converted+1, 256, "s%c%s", (char)msgtype, msg);
+	if(size>0) {
+		converted[0] = size>255 ? 255 : size;
+		if(!flib_ipcbase_send_raw(conn->ipcBase, converted, converted[0]+1)) {
+			demo_append(conn, converted, converted[0]+1);
+			result = 0;
+		}
+	}
+	return result;
+}
+
+int flib_gameconn_send_chatmsg(flib_gameconn *conn, const char *playername, const char *msg) {
+	if(log_badargs_if3(conn==NULL, playername==NULL, msg==NULL)) {
+		return -1;
+	}
+	uint8_t converted[257];
+	if(!format_chatmessage(converted, playername, msg)
+			&& !flib_ipcbase_send_raw(conn->ipcBase, converted, converted[0]+1)) {
+		demo_append(conn, converted, converted[0]+1);
+		return 0;
+	}
+	return -1;
+}
+
+int flib_gameconn_send_quit(flib_gameconn *conn) {
+	if(log_badargs_if(conn==NULL)) {
+		return -1;
+	}
+	const char *msg = "\x07""efinish";
+	if(!flib_ipcbase_send_raw(conn->ipcBase, msg, msg[0]+1)) {
+		demo_append(conn, msg, msg[0]+1);
+		return 0;
+	}
+	return -1;
+}
+
+/**
+ * This macro generates a callback setter function. It uses the name of the callback to
+ * automatically generate the function name and the fields to set, so a consistent naming
+ * convention needs to be enforced (not that that is a bad thing). If null is passed as
+ * callback to the generated function, the defaultCb will be set instead (with conn
+ * as the context).
+ */
+#define GENERATE_CB_SETTER(cbName, cbParameterTypes, defaultCb) \
+	void flib_gameconn_##cbName(flib_gameconn *conn, void (*callback)cbParameterTypes, void *context) { \
+		if(!log_badargs_if(conn==NULL)) { \
+			conn->cbName##Cb = callback ? callback : &defaultCb; \
+			conn->cbName##Ctx = callback ? context : conn; \
+		} \
+	}
+
+/**
+ * Generate a callback setter function like GENERATE_CB_SETTER, and automatically generate a
+ * no-op callback function as well that is used as default.
+ */
+#define GENERATE_CB_SETTER_AND_DEFAULT(cbName, cbParameterTypes) \
+	static void _noop_callback_##cbName cbParameterTypes {} \
+	GENERATE_CB_SETTER(cbName, cbParameterTypes, _noop_callback_##cbName)
+
+GENERATE_CB_SETTER_AND_DEFAULT(onConnect, (void *context));
+GENERATE_CB_SETTER_AND_DEFAULT(onDisconnect, (void* context, int reason));
+GENERATE_CB_SETTER(onErrorMessage, (void* context, const char *msg), defaultCallback_onErrorMessage);
+GENERATE_CB_SETTER_AND_DEFAULT(onChat, (void* context, const char *msg, bool teamchat));
+GENERATE_CB_SETTER_AND_DEFAULT(onGameRecorded, (void *context, const uint8_t *record, size_t size, bool isSavegame));
+GENERATE_CB_SETTER_AND_DEFAULT(onEngineMessage, (void *context, const uint8_t *em, size_t size));
+
+#undef GENERATE_CB_SETTER_AND_DEFAULT
+#undef GENERATE_CB_SETTER
+
+static void flib_gameconn_wrappedtick(flib_gameconn *conn) {
+	if(conn->state == AWAIT_CONNECTION) {
+		flib_ipcbase_accept(conn->ipcBase);
+		switch(flib_ipcbase_state(conn->ipcBase)) {
+		case IPC_CONNECTED:
+			{
+				flib_constbuffer configBuffer = flib_vector_as_constbuffer(conn->configBuffer);
+				if(flib_ipcbase_send_raw(conn->ipcBase, configBuffer.data, configBuffer.size)) {
+					conn->state = FINISHED;
+					conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
+					return;
+				} else {
+					demo_append(conn, configBuffer.data, configBuffer.size);
+					conn->state = CONNECTED;
+					conn->onConnectCb(conn->onConnectCtx);
+					if(conn->destroyRequested) {
+						return;
+					}
+				}
+			}
+			break;
+		case IPC_NOT_CONNECTED:
+			conn->state = FINISHED;
+			conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
+			return;
+		default:
+			break;
+		}
+	}
+
+	if(conn->state == CONNECTED) {
+		uint8_t msgbuffer[257];
+		int len;
+		while(!conn->destroyRequested && (len = flib_ipcbase_recv_message(conn->ipcBase, msgbuffer))>=0) {
+			if(len<2) {
+				flib_log_w("Received short message from IPC (<2 bytes)");
+				continue;
+			}
+			switch(msgbuffer[1]) {
+			case '?':	// Ping
+				// The pong is already part of the config message
+				break;
+			case 'C':	// Config query
+				// And we already send the config message on connecting.
+				break;
+			case 'E':	// Error message
+				if(len>=3) {
+					msgbuffer[len-2] = 0;
+					conn->onErrorMessageCb(conn->onErrorMessageCtx, (char*)msgbuffer+2);
+				}
+				break;
+			case 'i':	// Statistics
+				// TODO stats
+				break;
+			case 'Q':	// Game interrupted
+			case 'H':	// Game halted
+			case 'q':	// game finished
+				{
+					int reason = msgbuffer[1]=='Q' ? GAME_END_INTERRUPTED : msgbuffer[1]=='H' ? GAME_END_HALTED : GAME_END_FINISHED;
+					bool savegame = (reason != GAME_END_FINISHED) && !conn->netgame;
+					if(conn->demoBuffer) {
+						flib_buffer demoBuffer = flib_vector_as_buffer(conn->demoBuffer);
+						demo_replace_gamemode(demoBuffer, savegame ? 'S' : 'D');
+						conn->onGameRecordedCb(conn->onGameRecordedCtx, demoBuffer.data, demoBuffer.size, savegame);
+						if(conn->destroyRequested) {
+							return;
+						}
+					}
+					conn->state = FINISHED;
+					conn->onDisconnectCb(conn->onDisconnectCtx, reason);
+					return;
+				}
+			case 's':	// Chat message
+				if(len>=3) {
+					msgbuffer[len-2] = 0;
+					demo_append_chatmessage(conn, (char*)msgbuffer+2);
+
+					conn->onChatCb(conn->onChatCtx, (char*)msgbuffer+2, false);
+				}
+				break;
+			case 'b':	// Teamchat message
+				if(len>=3) {
+					msgbuffer[len-2] = 0;
+					conn->onChatCb(conn->onChatCtx, (char*)msgbuffer+2, true);
+				}
+				break;
+			default:	// Engine message
+				demo_append(conn, msgbuffer, len);
+
+				conn->onEngineMessageCb(conn->onEngineMessageCtx, msgbuffer, len);
+				break;
+			}
+		}
+	}
+
+	if(flib_ipcbase_state(conn->ipcBase) == IPC_NOT_CONNECTED) {
+		conn->state = FINISHED;
+		conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
+	}
+}
+
+void flib_gameconn_tick(flib_gameconn *conn) {
+	if(!log_badargs_if(conn == NULL)
+			&& !log_w_if(conn->running, "Call to flib_gameconn_tick from a callback")
+			&& !log_w_if(conn->state == FINISHED, "We are already done.")) {
+		conn->running = true;
+		flib_gameconn_wrappedtick(conn);
+		conn->running = false;
+
+		if(conn->destroyRequested) {
+			flib_gameconn_destroy(conn);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/gameconn.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,172 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * This file contains functions for starting and interacting with a game run by the engine.
+ * The general usage is to first create a gameconn object by calling one of the flib_gameconn_create
+ * functions. That will cause the frontlib to listen on a random port which can be queried using
+ * flib_gameconn_getport(). You should also register your callback functions right at the start
+ * to ensure you don't miss any callbacks.
+ *
+ * Next, start the engine (that part is up to you) with the appropriate command line arguments
+ * for starting a game.
+ *
+ * In order to allow the gameconn to run, you should regularly call flib_gameconn_tick(), which
+ * performs network I/O and calls your callbacks on interesting events.
+ *
+ * Once the engine connects, the gameconn will send it the required commands for starting the
+ * game you requested in your flib_gameconn_create call.
+ *
+ * When the game is finished (or the connection is lost), you will receive the onDisconnect
+ * message. This is the signal to destroy the gameconn and stop calling tick().
+ */
+
+#ifndef GAMECONN_H_
+#define GAMECONN_H_
+
+#include "../model/gamesetup.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+/*
+ * Different reasons for a disconnect. Only GAME_END_FINISHED signals a correctly completed game.
+ */
+#define GAME_END_FINISHED 0
+#define GAME_END_INTERRUPTED 1
+#define GAME_END_HALTED 2
+#define GAME_END_ERROR 3
+
+typedef struct _flib_gameconn flib_gameconn;
+
+/**
+ * Create a gameconn that will start a local or network game with the indicated configuration.
+ */
+flib_gameconn *flib_gameconn_create(const char *playerName, const flib_gamesetup *setup, bool netgame);
+
+/**
+ * Create a gameconn that will play back a demo.
+ */
+flib_gameconn *flib_gameconn_create_playdemo(const uint8_t *demoFileContent, size_t size);
+
+/**
+ * Create a gameconn that will continue from a saved game.
+ */
+flib_gameconn *flib_gameconn_create_loadgame(const char *playerName, const uint8_t *saveFileContent, size_t size);
+
+/**
+ * Create a gameconn that will start a campaign or training mission with the indicated script.
+ * seed is the random seed to use as entropy source (any string).
+ * script is the path and filename of a Campaign or Training script, relative to the Data directory
+ * (e.g. "Missions/Training/Basic_Training_-_Bazooka.lua")
+ */
+flib_gameconn *flib_gameconn_create_campaign(const char *playerName, const char *seed, const char *script);
+
+/**
+ * Release all resources of this gameconn, including the network connection, and free its memory.
+ * It is safe to call this function from a callback.
+ */
+void flib_gameconn_destroy(flib_gameconn *conn);
+
+/**
+ * Returns the port on which the gameconn is listening. Only fails if you
+ * pass NULL (not allowed), in that case 0 is returned.
+ */
+int flib_gameconn_getport(flib_gameconn *conn);
+
+/**
+ * Perform I/O operations and call callbacks if something interesting happens.
+ * Should be called regularly.
+ */
+void flib_gameconn_tick(flib_gameconn *conn);
+
+/**
+ * Send an engine message to the engine. Only needed in net games, where you receive engine
+ * messages from the server and have to pass them here.
+ */
+int flib_gameconn_send_enginemsg(flib_gameconn *conn, const uint8_t *data, size_t len);
+
+/**
+ * Send an info message to the engine that will be displayed in the game's chatlog.
+ * The msgtype determines the color of the message;  in the QTFrontend, info messages and
+ * normal chat messages use 1, emote-messages (those starting with /me) use 2, and
+ * join/leave messages use 3. You should use flib_gameconn_send_chatmsg for chat messages
+ * though because it automatically formats /me messages.
+ *
+ * Generally only needed in net games.
+ */
+int flib_gameconn_send_textmsg(flib_gameconn *conn, int msgtype, const char *msg);
+
+/**
+ * Send a chat message to be displayed in the game's chatlog. Messages starting with /me are
+ * automatically formatted correctly.
+ *
+ * Generally only needed in net games.
+ */
+int flib_gameconn_send_chatmsg(flib_gameconn *conn, const char *playername, const char *msg);
+
+/**
+ * Request the engine to stop the game.
+ * You can use this to shut down a game early without directly killing the engine process.
+ */
+int flib_gameconn_send_quit(flib_gameconn *conn);
+
+/**
+ * Expected callback signature: void handleConnect(void *context)
+ * The engine has successfully connected. You don't have to react to this in any way.
+ */
+void flib_gameconn_onConnect(flib_gameconn *conn, void (*callback)(void* context), void* context);
+
+/**
+ * Expected callback signature: void handleDisconnect(void *context, int reason)
+ * The connection to the engine was closed, either because the game has ended normally, or
+ * because it was interrupted/halted, or because of an error. The reason is provided as one
+ * of the GAME_END_xxx constants.
+ *
+ * You should destroy the gameconn and - in a netgame - notify the server that the game has ended.
+ */
+void flib_gameconn_onDisconnect(flib_gameconn *conn, void (*callback)(void* context, int reason), void* context);
+
+/**
+ * Expected callback signature: void handleErrorMessage(void* context, const char *msg)
+ * The engine sent an error message, you should probably display it to the user or at least log it.
+ */
+void flib_gameconn_onErrorMessage(flib_gameconn *conn, void (*callback)(void* context, const char *msg), void* context);
+
+/**
+ * Expected callback signature: void handleChat(void* context, const char *msg, bool teamchat)
+ * The player entered a chat or teamchat message. In a netgame, you should send it on to the server.
+ */
+void flib_gameconn_onChat(flib_gameconn *conn, void (*callback)(void* context, const char *msg, bool teamchat), void* context);
+
+/**
+ * Expected callback signature: void handleGameRecorded(void *context, const uint8_t *record, size_t size, bool isSavegame)
+ * The game has stopped, and a demo or savegame is available. You can store it in a file and later pass it back
+ * to the engine to either watch a replay (if it's a demo) or to continue playing (if it's a savegame).
+ */
+void flib_gameconn_onGameRecorded(flib_gameconn *conn, void (*callback)(void *context, const uint8_t *record, size_t size, bool isSavegame), void* context);
+
+/**
+ * Expected callback signature: void handleEngineMessage(void *context, const uint8_t *em, size_t size)
+ * The engine has generated a message with player input. In a netgame, you should send it on to the server.
+ */
+void flib_gameconn_onEngineMessage(flib_gameconn *conn, void (*callback)(void *context, const uint8_t *em, size_t size), void* context);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/ipcbase.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,200 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "ipcbase.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../socket.h"
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/*
+ * The receive buffer has to be able to hold any message that might be received. Normally
+ * the messages are at most 256 bytes, but the map preview contains 4097 bytes (4096 for a
+ * bitmap, 1 for the number of hogs which fit on the map).
+ *
+ * We don't need to worry about wasting a few kb though, and I like powers of two...
+ */
+struct _flib_ipcbase {
+	uint8_t readBuffer[8192];
+	int readBufferSize;
+
+	flib_acceptor *acceptor;
+	uint16_t port;
+
+	flib_tcpsocket *sock;
+};
+
+flib_ipcbase *flib_ipcbase_create() {
+	flib_ipcbase *result = flib_calloc(1, sizeof(flib_ipcbase));
+	flib_acceptor *acceptor = flib_acceptor_create(0);
+
+	if(!result || !acceptor) {
+		free(result);
+		flib_acceptor_close(acceptor);
+		return NULL;
+	}
+
+	result->acceptor = acceptor;
+	result->sock = NULL;
+	result->readBufferSize = 0;
+	result->port = flib_acceptor_listenport(acceptor);
+
+	flib_log_i("Started listening for IPC connections on port %u", (unsigned)result->port);
+	return result;
+}
+
+uint16_t flib_ipcbase_port(flib_ipcbase *ipc) {
+	if(log_badargs_if(ipc==NULL)) {
+		return 0;
+	}
+	return ipc->port;
+}
+
+void flib_ipcbase_destroy(flib_ipcbase *ipc) {
+	if(ipc) {
+		flib_acceptor_close(ipc->acceptor);
+		flib_socket_close(ipc->sock);
+		free(ipc);
+	}
+}
+
+IpcState flib_ipcbase_state(flib_ipcbase *ipc) {
+	if(log_badargs_if(ipc==NULL)) {
+		return IPC_NOT_CONNECTED;
+	} else if(ipc->sock) {
+		return IPC_CONNECTED;
+	} else if(ipc->acceptor) {
+		return IPC_LISTENING;
+	} else {
+		return IPC_NOT_CONNECTED;
+	}
+}
+
+static void receiveToBuffer(flib_ipcbase *ipc) {
+	if(ipc->sock) {
+		int size = flib_socket_nbrecv(ipc->sock, ipc->readBuffer+ipc->readBufferSize, sizeof(ipc->readBuffer)-ipc->readBufferSize);
+		if(size>=0) {
+			ipc->readBufferSize += size;
+		} else {
+			flib_socket_close(ipc->sock);
+			ipc->sock = NULL;
+		}
+	}
+}
+
+static bool isMessageReady(flib_ipcbase *ipc) {
+	return ipc->readBufferSize >= ipc->readBuffer[0]+1;
+}
+
+int flib_ipcbase_recv_message(flib_ipcbase *ipc, void *data) {
+	if(log_badargs_if2(ipc==NULL, data==NULL)) {
+		return -1;
+	}
+
+	if(!isMessageReady(ipc)) {
+		receiveToBuffer(ipc);
+	}
+
+	if(isMessageReady(ipc)) {
+		int msgsize = ipc->readBuffer[0]+1;
+		memcpy(data, ipc->readBuffer, msgsize);
+		memmove(ipc->readBuffer, ipc->readBuffer+msgsize, ipc->readBufferSize-msgsize);
+		ipc->readBufferSize -= msgsize;
+		return msgsize;
+	} else if(!ipc->sock && ipc->readBufferSize>0) {
+		flib_log_w("Last message from engine data stream is incomplete (received %u of %u bytes)", (unsigned)ipc->readBufferSize, (unsigned)(ipc->readBuffer[0])+1);
+		ipc->readBufferSize = 0;
+		return -1;
+	} else {
+		return -1;
+	}
+}
+
+int flib_ipcbase_recv_map(flib_ipcbase *ipc, void *data) {
+	if(log_badargs_if2(ipc==NULL, data==NULL)) {
+		return -1;
+	}
+
+	receiveToBuffer(ipc);
+
+	if(ipc->readBufferSize >= IPCBASE_MAPMSG_BYTES) {
+		memcpy(data, ipc->readBuffer, IPCBASE_MAPMSG_BYTES);
+		memmove(ipc->readBuffer, ipc->readBuffer+IPCBASE_MAPMSG_BYTES, ipc->readBufferSize-IPCBASE_MAPMSG_BYTES);
+		return IPCBASE_MAPMSG_BYTES;
+	} else {
+		return -1;
+	}
+}
+
+static void logSentMsg(const uint8_t *data, size_t len) {
+	if(flib_log_isActive(FLIB_LOGLEVEL_DEBUG)) {
+		size_t msgStart = 0;
+		while(msgStart < len) {
+			uint8_t msglen = data[msgStart];
+			if(msgStart+msglen < len) {
+				flib_log_d("[IPC OUT][%03u]%*.*s",(unsigned)msglen, (unsigned)msglen, (unsigned)msglen, data+msgStart+1);
+			} else {
+				uint8_t msglen2 = len-msgStart-1;
+				flib_log_d("[IPC OUT][%03u/%03u]%*.*s",(unsigned)msglen2, (unsigned)msglen, (unsigned)msglen2, (unsigned)msglen2, data+msgStart+1);
+			}
+			msgStart += (uint8_t)data[msgStart]+1;
+		}
+	}
+}
+
+int flib_ipcbase_send_raw(flib_ipcbase *ipc, const void *data, size_t len) {
+	if(log_badargs_if2(ipc==NULL, data==NULL && len>0)
+			|| log_w_if(!ipc->sock, "flib_ipcbase_send_raw: Not connected.")) {
+		return -1;
+	}
+	if(flib_socket_send(ipc->sock, data, len) == len) {
+		logSentMsg(data, len);
+		return 0;
+	} else {
+		flib_log_w("Failed or incomplete ICP write: engine connection lost.");
+		flib_socket_close(ipc->sock);
+		ipc->sock = NULL;
+		return -1;
+	}
+}
+
+int flib_ipcbase_send_message(flib_ipcbase *ipc, void *data, size_t len) {
+	if(log_badargs_if3(ipc==NULL, data==NULL && len>0, len>255)) {
+		return -1;
+	}
+
+	uint8_t sendbuf[256];
+	sendbuf[0] = len;
+	memcpy(sendbuf+1, data, len);
+	return flib_ipcbase_send_raw(ipc, sendbuf, len+1);
+}
+
+void flib_ipcbase_accept(flib_ipcbase *ipc) {
+	if(!log_badargs_if(ipc==NULL) && !ipc->sock && ipc->acceptor) {
+		ipc->sock = flib_socket_accept(ipc->acceptor, true);
+		if(ipc->sock) {
+			flib_acceptor_close(ipc->acceptor);
+			ipc->acceptor = NULL;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/ipcbase.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,105 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * Low-level protocol support for the IPC connection to the engine.
+ */
+#ifndef IPCBASE_H_
+#define IPCBASE_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define IPCBASE_MAPMSG_BYTES 4097
+
+typedef enum {IPC_NOT_CONNECTED, IPC_LISTENING, IPC_CONNECTED} IpcState;
+
+typedef struct _flib_ipcbase flib_ipcbase;
+
+/**
+ * Start an engine connection by listening on a random port. The selected port can
+ * be queried with flib_ipcbase_port and has to be passed to the engine.
+ *
+ * Returns NULL on error. Destroy the created object with flib_ipcbase_destroy.
+ *
+ * We stop accepting new connections once a connection has been established, so you
+ * need to create a new ipcbase in order to start a new connection.
+ */
+flib_ipcbase *flib_ipcbase_create();
+
+/**
+ * Return the listening port
+ */
+uint16_t flib_ipcbase_port(flib_ipcbase *ipc);
+
+/**
+ * Free resources and close sockets. NULL safe.
+ */
+void flib_ipcbase_destroy(flib_ipcbase *ipc);
+
+/**
+ * Determine the current connection state
+ */
+IpcState flib_ipcbase_state(flib_ipcbase *ipc);
+
+/**
+ * Receive a single message (up to 256 bytes) and copy it into the data buffer.
+ * Returns the length of the received message, a negative value if no message could
+ * be read.
+ *
+ * The first byte of a message is its content length, which is one less than the returned
+ * value.
+ *
+ * Note: When a connection is closed, you probably want to call this function until
+ * no further message is returned, to ensure you see all messages that were sent
+ * before the connection closed.
+ */
+int flib_ipcbase_recv_message(flib_ipcbase *ipc, void *data);
+
+/**
+ * Try to receive 4097 bytes. This is the size of the reply the engine sends
+ * when successfully queried for map data. The first 4096 bytes are a bit-packed
+ * twocolor image of the map (256x128), the last byte is the number of hogs that
+ * fit on the map.
+ */
+int flib_ipcbase_recv_map(flib_ipcbase *ipc, void *data);
+
+/**
+ * Blocking send bytes over the socket. No message framing will be added.
+ * Returns 0 on success.
+ */
+int flib_ipcbase_send_raw(flib_ipcbase *ipc, const void *data, size_t len);
+
+/**
+ * Write a single message (up to 255 bytes) to the engine. This call blocks until the
+ * message is completely written or the connection is closed or an error occurs.
+ *
+ * Calling this function in a state other than IPC_CONNECTED will fail immediately.
+ * Returns 0 on success.
+ */
+int flib_ipcbase_send_message(flib_ipcbase *ipc, void *data, size_t len);
+
+/**
+ * Try to accept a connection. Only has an effect in state IPC_LISTENING.
+ */
+void flib_ipcbase_accept(flib_ipcbase *ipc);
+
+#endif /* IPCBASE_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/ipcprotocol.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,316 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "ipcprotocol.h"
+#include "../util/util.h"
+#include "../util/logging.h"
+#include "../md5/md5.h"
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+int flib_ipc_append_message(flib_vector *vec, const char *fmt, ...) {
+	int result = -1;
+	if(!log_badargs_if2(vec==NULL, fmt==NULL)) {
+		// 1 byte size prefix, 255 bytes max message length, 1 0-byte for vsnprintf
+		char msgbuffer[257];
+
+		// Format the message, leaving one byte at the start for the length
+		va_list argp;
+		va_start(argp, fmt);
+		int msgSize = vsnprintf(msgbuffer+1, 256, fmt, argp);
+		va_end(argp);
+
+		if(!log_e_if(msgSize > 255, "Message too long (%u bytes)", (unsigned)msgSize)
+				&& !log_e_if(msgSize < 0, "printf error")) {
+			// Add the length prefix
+			((uint8_t*)msgbuffer)[0] = msgSize;
+
+			// Append it to the vector
+			result = flib_vector_append(vec, msgbuffer, msgSize+1);
+		}
+	}
+	return result;
+}
+
+int flib_ipc_append_mapconf(flib_vector *vec, const flib_map *map, bool mappreview) {
+	int result = -1;
+	flib_vector *tempvector = flib_vector_create();
+	if(!log_badargs_if2(vec==NULL, map==NULL)) {
+		bool error = false;
+
+		if(map->mapgen == MAPGEN_NAMED) {
+			error |= log_e_if(!map->name, "Missing map name")
+					|| flib_ipc_append_message(tempvector, "emap %s", map->name);
+		}
+		if(!mappreview) {
+			error |= log_e_if(!map->theme, "Missing map theme")
+					|| flib_ipc_append_message(tempvector, "etheme %s", map->theme);
+		}
+		error |= flib_ipc_append_seed(tempvector, map->seed);
+		error |= flib_ipc_append_message(tempvector, "e$template_filter %i", map->templateFilter);
+		error |= flib_ipc_append_message(tempvector, "e$mapgen %i", map->mapgen);
+
+		if(map->mapgen == MAPGEN_MAZE) {
+			error |= flib_ipc_append_message(tempvector, "e$maze_size %i", map->mazeSize);
+		}
+		if(map->mapgen == MAPGEN_DRAWN) {
+			/*
+			 * We have to split the drawn map data into several edraw messages here because
+			 * it can be longer than the maximum message size.
+			 */
+			const char *edraw = "edraw ";
+			int edrawlen = strlen(edraw);
+			for(size_t offset=0; offset<map->drawDataSize; offset+=200) {
+				size_t bytesRemaining = map->drawDataSize-offset;
+				int fragmentsize = bytesRemaining < 200 ? bytesRemaining : 200;
+				uint8_t messagesize = edrawlen + fragmentsize;
+				error |= flib_vector_append(tempvector, &messagesize, 1);
+				error |= flib_vector_append(tempvector, edraw, edrawlen);
+				error |= flib_vector_append(tempvector, map->drawData+offset, fragmentsize);
+			}
+		}
+
+		if(!log_e_if(error, "Error generating map config")) {
+			// Message created, now we can copy everything.
+			flib_constbuffer constbuf = flib_vector_as_constbuffer(tempvector);
+			if(!flib_vector_append(vec, constbuf.data, constbuf.size)) {
+				result = 0;
+			}
+		}
+	}
+	flib_vector_destroy(tempvector);
+	return result;
+}
+
+int flib_ipc_append_seed(flib_vector *vec, const char *seed) {
+	if(log_badargs_if2(vec==NULL, seed==NULL)) {
+		return -1;
+	}
+	return flib_ipc_append_message(vec, "eseed %s", seed);
+}
+
+int flib_ipc_append_script(flib_vector *vec, const char *script) {
+	int result = -1;
+	if(!log_badargs_if2(vec==NULL, script==NULL)) {
+		result = flib_ipc_append_message(vec, "escript %s", script);
+	}
+	return result;
+}
+
+int flib_ipc_append_style(flib_vector *vec, const char *style) {
+	int result = -1;
+	char *copy = flib_strdupnull(style);
+	if(!log_badargs_if(vec==NULL) && copy) {
+		if(!strcmp("Normal", copy)) {
+			// "Normal" means no gametype script
+			// TODO if an empty script called "Normal" is added to the scripts directory this can be removed
+			result = 0;
+		} else {
+			size_t len = strlen(copy);
+			for(size_t i=0; i<len; i++) {
+				if(copy[i] == ' ') {
+					copy[i] = '_';
+				}
+			}
+
+			result = flib_ipc_append_message(vec, "escript %s%s.lua", MULTIPLAYER_SCRIPT_PATH, copy);
+		}
+	}
+	free(copy);
+	return result;
+}
+
+static uint32_t buildModFlags(const flib_scheme *scheme) {
+	uint32_t result = 0;
+	for(int i=0; i<flib_meta.modCount; i++) {
+		if(scheme->mods[i]) {
+			int bitmaskIndex = flib_meta.mods[i].bitmaskIndex;
+			result |= (UINT32_C(1) << bitmaskIndex);
+		}
+	}
+	return result;
+}
+
+int flib_ipc_append_gamescheme(flib_vector *vec, const flib_scheme *scheme) {
+	int result = -1;
+	flib_vector *tempvector = flib_vector_create();
+	if(!log_badargs_if2(vec==NULL, scheme==NULL) && tempvector) {
+		bool error = false;
+		error |= flib_ipc_append_message(tempvector, "e$gmflags %"PRIu32, buildModFlags(scheme));
+		for(int i=0; i<flib_meta.settingCount; i++) {
+			if(flib_meta.settings[i].engineCommand) {
+				int value = scheme->settings[i];
+				if(flib_meta.settings[i].maxMeansInfinity) {
+					value = value>=flib_meta.settings[i].max ? 9999 : value;
+				}
+				if(flib_meta.settings[i].times1000) {
+					value *= 1000;
+				}
+				error |= flib_ipc_append_message(tempvector, "%s %i", flib_meta.settings[i].engineCommand, value);
+			}
+		}
+
+		if(!error) {
+			// Message created, now we can copy everything.
+			flib_constbuffer constbuf = flib_vector_as_constbuffer(tempvector);
+			if(!flib_vector_append(vec, constbuf.data, constbuf.size)) {
+				result = 0;
+			}
+		}
+	}
+	flib_vector_destroy(tempvector);
+	return result;
+}
+
+static int appendWeaponSet(flib_vector *vec, flib_weaponset *set) {
+	return flib_ipc_append_message(vec, "eammloadt %s", set->loadout)
+		|| flib_ipc_append_message(vec, "eammprob %s", set->crateprob)
+		|| flib_ipc_append_message(vec, "eammdelay %s", set->delay)
+		|| flib_ipc_append_message(vec, "eammreinf %s", set->crateammo);
+}
+
+static void calculateMd5Hex(const char *in, char out[33]) {
+	md5_state_t md5state;
+	uint8_t md5bytes[16];
+	md5_init(&md5state);
+	md5_append(&md5state, (unsigned char*)in, strlen(in));
+	md5_finish(&md5state, md5bytes);
+	for(int i=0;i<sizeof(md5bytes); i++) {
+		snprintf(out+i*2, 3, "%02x", (unsigned)md5bytes[i]);
+	}
+}
+
+static int flib_ipc_append_addteam(flib_vector *vec, const flib_team *team, bool perHogAmmo, bool noAmmoStore) {
+	int result = -1;
+	flib_vector *tempvector = flib_vector_create();
+	if(!log_badargs_if2(vec==NULL, team==NULL) && tempvector) {
+		bool error = false;
+
+		if(!perHogAmmo && !noAmmoStore) {
+			error = error
+					|| appendWeaponSet(tempvector, team->hogs[0].weaponset)
+					|| flib_ipc_append_message(tempvector, "eammstore");
+		}
+
+		char md5Hex[33];
+		calculateMd5Hex(team->ownerName ? team->ownerName : "", md5Hex);
+		if(team->colorIndex<0 || team->colorIndex>=flib_teamcolor_count) {
+			flib_log_e("Color index out of bounds for team %s: %i", team->name, team->colorIndex);
+			error = true;
+		} else {
+			error |= flib_ipc_append_message(tempvector, "eaddteam %s %"PRIu32" %s", md5Hex, flib_teamcolors[team->colorIndex], team->name);
+		}
+
+		if(team->remoteDriven) {
+			error |= flib_ipc_append_message(tempvector, "erdriven");
+		}
+
+		error |= flib_ipc_append_message(tempvector, "egrave %s", team->grave);
+		error |= flib_ipc_append_message(tempvector, "efort %s", team->fort);
+		error |= flib_ipc_append_message(tempvector, "evoicepack %s", team->voicepack);
+		error |= flib_ipc_append_message(tempvector, "eflag %s", team->flag);
+
+		for(int i=0; i<team->bindingCount; i++) {
+			error |= flib_ipc_append_message(tempvector, "ebind %s %s", team->bindings[i].binding, team->bindings[i].action);
+		}
+
+		for(int i=0; i<team->hogsInGame; i++) {
+			if(perHogAmmo && !noAmmoStore) {
+				error |= appendWeaponSet(tempvector, team->hogs[i].weaponset);
+			}
+			error |= flib_ipc_append_message(tempvector, "eaddhh %i %i %s", team->hogs[i].difficulty, team->hogs[i].initialHealth, team->hogs[i].name);
+			error |= flib_ipc_append_message(tempvector, "ehat %s", team->hogs[i].hat);
+		}
+
+		if(!error) {
+			// Message created, now we can copy everything.
+			flib_constbuffer constbuf = flib_vector_as_constbuffer(tempvector);
+			if(!flib_vector_append(vec, constbuf.data, constbuf.size)) {
+				result = 0;
+			}
+		}
+	}
+	flib_vector_destroy(tempvector);
+	return result;
+}
+
+int flib_ipc_append_fullconfig(flib_vector *vec, const flib_gamesetup *setup, bool netgame) {
+	int result = -1;
+	flib_vector *tempvector = flib_vector_create();
+	if(!log_badargs_if2(vec==NULL, setup==NULL) && tempvector) {
+		bool error = false;
+		bool perHogAmmo = false;
+		bool sharedAmmo = false;
+
+		error |= flib_ipc_append_message(vec, netgame ? "TN" : "TL");
+		if(setup->map) {
+			error |= flib_ipc_append_mapconf(tempvector, setup->map, false);
+		}
+		if(setup->style) {
+			error |= flib_ipc_append_style(tempvector, setup->style);
+		}
+		if(setup->gamescheme) {
+			error |= flib_ipc_append_gamescheme(tempvector, setup->gamescheme);
+			sharedAmmo = flib_scheme_get_mod(setup->gamescheme, "sharedammo");
+			// Shared ammo has priority over per-hog ammo
+			perHogAmmo = !sharedAmmo && flib_scheme_get_mod(setup->gamescheme, "perhogammo");
+		}
+		if(setup->teamlist->teams && setup->teamlist->teamCount>0) {
+			int *clanColors = flib_calloc(setup->teamlist->teamCount, sizeof(int));
+			if(!clanColors) {
+				error = true;
+			} else {
+				int clanCount = 0;
+				for(int i=0; !error && i<setup->teamlist->teamCount; i++) {
+					flib_team *team = setup->teamlist->teams[i];
+					// Find the clan index of this team (clans are identified by color).
+					bool newClan = false;
+					int clan = 0;
+					while(clan<clanCount && clanColors[clan] != team->colorIndex) {
+						clan++;
+					}
+					if(clan==clanCount) {
+						newClan = true;
+						clanCount++;
+						clanColors[clan] = team->colorIndex;
+					}
+
+					// If shared ammo is active, only add an ammo store for the first team in each clan.
+					bool noAmmoStore = sharedAmmo&&!newClan;
+					error |= flib_ipc_append_addteam(tempvector, setup->teamlist->teams[i], perHogAmmo, noAmmoStore);
+				}
+			}
+			free(clanColors);
+		}
+		error |= flib_ipc_append_message(tempvector, "!");
+
+		if(!error) {
+			// Message created, now we can copy everything.
+			flib_constbuffer constbuf = flib_vector_as_constbuffer(tempvector);
+			if(!flib_vector_append(vec, constbuf.data, constbuf.size)) {
+				result = 0;
+			}
+		}
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/ipcprotocol.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,93 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef IPCPROTOCOL_H_
+#define IPCPROTOCOL_H_
+
+#include "../util/buffer.h"
+#include "../model/map.h"
+#include "../model/team.h"
+#include "../model/scheme.h"
+#include "../model/gamesetup.h"
+
+#include <stdbool.h>
+
+/**
+ * Create a message in the IPC protocol format and add it to
+ * the vector. Use a format string and extra parameters as with printf.
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_message(flib_vector *vec, const char *fmt, ...);
+
+/**
+ * Append IPC messages to the buffer that configure the engine for
+ * this map.
+ *
+ * Unfortunately the engine needs a slightly different configuration
+ * for generating a map preview.
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_mapconf(flib_vector *vec, const flib_map *map, bool mappreview);
+
+/**
+ * Append a seed message to the buffer.
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_seed(flib_vector *vec, const char *seed);
+
+/**
+ * Append a script to the buffer (e.g. "Missions/Training/Basic_Training_-_Bazooka.lua")
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_script(flib_vector *vec, const char *script);
+
+/**
+ * Append a game style to the buffer. (e.g. "Capture the Flag")
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_style(flib_vector *vec, const char *style);
+
+/**
+ * Append the game scheme to the buffer.
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_gamescheme(flib_vector *vec, const flib_scheme *scheme);
+
+/**
+ * Append the entire game config to the buffer (including the final "!" that marks the
+ * end of configuration data for the engine)
+ *
+ * Returns nonzero if something goes wrong. In that case the buffer
+ * contents are unaffected.
+ */
+int flib_ipc_append_fullconfig(flib_vector *vec, const flib_gamesetup *setup, bool netgame);
+
+#endif /* IPCPROTOCOL_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/mapconn.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,186 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "mapconn.h"
+#include "ipcbase.h"
+#include "ipcprotocol.h"
+
+#include "../util/logging.h"
+#include "../util/buffer.h"
+#include "../util/util.h"
+
+#include <stdlib.h>
+
+typedef enum {
+	AWAIT_CONNECTION,
+	AWAIT_REPLY,
+	FINISHED
+} mapconn_state;
+
+struct _flib_mapconn {
+	uint8_t mapBuffer[IPCBASE_MAPMSG_BYTES];
+	flib_ipcbase *ipcBase;
+	flib_vector *configBuffer;
+
+	mapconn_state progress;
+
+	void (*onSuccessCb)(void*, const uint8_t*, int);
+	void *onSuccessCtx;
+
+	void (*onFailureCb)(void*, const char*);
+	void *onFailureCtx;
+
+	bool running;
+	bool destroyRequested;
+};
+
+static void noop_handleSuccess(void *context, const uint8_t *bitmap, int numHedgehogs) {}
+static void noop_handleFailure(void *context, const char *errormessage) {}
+
+static void clearCallbacks(flib_mapconn *conn) {
+	conn->onSuccessCb = &noop_handleSuccess;
+	conn->onFailureCb = &noop_handleFailure;
+}
+
+static flib_vector *createConfigBuffer(const flib_map *mapdesc) {
+	flib_vector *result = NULL;
+	flib_vector *tempbuffer = flib_vector_create();
+	if(tempbuffer) {
+		bool error = false;
+		error |= flib_ipc_append_mapconf(tempbuffer, mapdesc, true);
+		error |= flib_ipc_append_message(tempbuffer, "!");
+		if(!error) {
+			result = tempbuffer;
+			tempbuffer = NULL;
+		}
+	}
+	flib_vector_destroy(tempbuffer);
+	return result;
+}
+
+flib_mapconn *flib_mapconn_create(const flib_map *mapdesc) {
+	if(log_badargs_if(mapdesc==NULL)) {
+		return NULL;
+	}
+	flib_mapconn *result = NULL;
+	flib_mapconn *tempConn = flib_calloc(1, sizeof(flib_mapconn));
+	if(tempConn) {
+		tempConn->ipcBase = flib_ipcbase_create();
+		tempConn->configBuffer = createConfigBuffer(mapdesc);
+		if(tempConn->ipcBase && tempConn->configBuffer) {
+			tempConn->progress = AWAIT_CONNECTION;
+			clearCallbacks(tempConn);
+			result = tempConn;
+			tempConn = NULL;
+		}
+	}
+	flib_mapconn_destroy(tempConn);
+	return result;
+}
+
+void flib_mapconn_destroy(flib_mapconn *conn) {
+	if(conn) {
+		if(conn->running) {
+			/*
+			 * The function was called from a callback, so the tick function is still running
+			 * and we delay the actual destruction. We ensure no further callbacks will be
+			 * sent to prevent surprises.
+			 */
+			clearCallbacks(conn);
+			conn->destroyRequested = true;
+		} else {
+			flib_ipcbase_destroy(conn->ipcBase);
+			flib_vector_destroy(conn->configBuffer);
+			free(conn);
+		}
+	}
+}
+
+int flib_mapconn_getport(flib_mapconn *conn) {
+	if(log_badargs_if(conn==NULL)) {
+		return 0;
+	}
+	return flib_ipcbase_port(conn->ipcBase);
+}
+
+void flib_mapconn_onSuccess(flib_mapconn *conn, void (*callback)(void* context, const uint8_t *bitmap, int numHedgehogs), void *context) {
+	if(!log_badargs_if(conn==NULL)) {
+		conn->onSuccessCb = callback ? callback : &noop_handleSuccess;
+		conn->onSuccessCtx = context;
+	}
+}
+
+void flib_mapconn_onFailure(flib_mapconn *conn, void (*callback)(void* context, const char *errormessage), void *context) {
+	if(!log_badargs_if(conn==NULL)) {
+		conn->onFailureCb = callback ? callback : &noop_handleFailure;
+		conn->onFailureCtx = context;
+	}
+}
+
+static void flib_mapconn_wrappedtick(flib_mapconn *conn) {
+	if(conn->progress == AWAIT_CONNECTION) {
+		flib_ipcbase_accept(conn->ipcBase);
+		switch(flib_ipcbase_state(conn->ipcBase)) {
+		case IPC_CONNECTED:
+			{
+				flib_constbuffer configBuffer = flib_vector_as_constbuffer(conn->configBuffer);
+				if(flib_ipcbase_send_raw(conn->ipcBase, configBuffer.data, configBuffer.size)) {
+					conn->progress = FINISHED;
+					conn->onFailureCb(conn->onFailureCtx, "Error sending map information to the engine.");
+					return;
+				} else {
+					conn->progress = AWAIT_REPLY;
+				}
+			}
+			break;
+		case IPC_NOT_CONNECTED:
+			conn->progress = FINISHED;
+			conn->onFailureCb(conn->onFailureCtx, "Engine connection closed unexpectedly.");
+			return;
+		default:
+			break;
+		}
+	}
+
+	if(conn->progress == AWAIT_REPLY) {
+		if(flib_ipcbase_recv_map(conn->ipcBase, conn->mapBuffer) >= 0) {
+			conn->progress = FINISHED;
+			conn->onSuccessCb(conn->onSuccessCtx, conn->mapBuffer, conn->mapBuffer[IPCBASE_MAPMSG_BYTES-1]);
+			return;
+		} else if(flib_ipcbase_state(conn->ipcBase) != IPC_CONNECTED) {
+			conn->progress = FINISHED;
+			conn->onFailureCb(conn->onSuccessCtx, "Engine connection closed unexpectedly.");
+			return;
+		}
+	}
+}
+
+void flib_mapconn_tick(flib_mapconn *conn) {
+	if(!log_badargs_if(conn==NULL)
+			&& !log_w_if(conn->running, "Call to flib_mapconn_tick from a callback")
+			&& !log_w_if(conn->progress == FINISHED, "We are already done.")) {
+		conn->running = true;
+		flib_mapconn_wrappedtick(conn);
+		conn->running = false;
+
+		if(conn->destroyRequested) {
+			flib_mapconn_destroy(conn);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/ipc/mapconn.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,116 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Functions for querying a map preview from the engine, which includes both a two-color image
+ * and the number of hogs this map is suitable for.
+ *
+ * The general usage is to first create a mapconn object by calling flib_mapconn_create.
+ * That will cause the frontlib to listen on a random port which can be queried using
+ * flib_mapconn_getport(). You should also register your callback functions right at the start
+ * to ensure you don't miss any callbacks.
+ *
+ * Next, start the engine (that part is up to you) with the appropriate command line arguments
+ * for a map preview request.
+ *
+ * In order to allow the mapconn to run, you should regularly call flib_mapconn_tick(), which
+ * performs network I/O and calls your callbacks if the map has been generated or an error
+ * has occurred. Once either the onSuccess or onFailure callback is called, you should destroy
+ * the mapconn and stop calling tick().
+ */
+
+#ifndef IPC_MAPCONN_H_
+#define IPC_MAPCONN_H_
+
+#include "../model/map.h"
+
+#include <stdint.h>
+
+#define MAPIMAGE_WIDTH 256
+#define MAPIMAGE_HEIGHT 128
+#define MAPIMAGE_BYTES (MAPIMAGE_WIDTH/8*MAPIMAGE_HEIGHT)
+
+typedef struct _flib_mapconn flib_mapconn;
+
+/**
+ * Start a new map rendering connection (mapconn). This means a listening socket
+ * will be started on a random unused port, waiting for a connection from the
+ * engine process. Once this connection is established, the required information
+ * will be sent to the engine, and the reply is read.
+ *
+ * The map must be a regular, maze or drawn map - for a preview of a named map,
+ * use the preview images in the map's directory, and for the hog count read the
+ * map information (e.g. using flib_mapcfg_read).
+ *
+ * No NULL parameters allowed, returns NULL on failure.
+ * Use flib_mapconn_destroy to free the returned object.
+ */
+flib_mapconn *flib_mapconn_create(const flib_map *mapdesc);
+
+/**
+ * Destroy the mapconn object. Passing NULL is allowed and does nothing.
+ * flib_mapconn_destroy may be called from inside a callback function.
+ */
+void flib_mapconn_destroy(flib_mapconn *conn);
+
+/**
+ * Returns the port on which the mapconn is listening. Only fails if you
+ * pass NULL (not allowed), in that case 0 is returned.
+ */
+int flib_mapconn_getport(flib_mapconn *conn);
+
+/**
+ * Set a callback which will receive the rendered map if the rendering succeeds.
+ *
+ * Expected callback signature:
+ * void handleSuccess(void *context, const uint8_t *bitmap, int numHedgehogs)
+ *
+ * The context passed to the callback is the same pointer you provided when
+ * registering the callback. bitmap is a pointer to a buffer of size MAPIMAGE_BYTES
+ * containing a bit-packed image of size MAPIMAGE_WIDTH * MAPIMAGE_HEIGHT.
+ * numHedgehogs is the number of hogs that fit on this map.
+ *
+ * The bitmap pointer passed to the callback belongs to the caller,
+ * so it should not be stored elsewhere. Note that it remains valid
+ * inside the callback method even if flib_mapconn_destroy is called.
+ */
+void flib_mapconn_onSuccess(flib_mapconn *conn, void (*callback)(void* context, const uint8_t *bitmap, int numHedgehogs), void *context);
+
+/**
+ * Set a callback which will receive an error message if rendering fails.
+ *
+ * Expected callback signature:
+ * void handleFailure(void *context, const char *errormessage)
+ *
+ * The context passed to the callback is the same pointer you provided when
+ * registering the callback.
+ *
+ * The error message passed to the callback belongs to the caller,
+ * so it should not be stored elsewhere. Note that it remains valid
+ * inside the callback method even if flib_mapconn_destroy is called.
+ */
+void flib_mapconn_onFailure(flib_mapconn *conn, void (*callback)(void* context, const char *errormessage), void *context);
+
+/**
+ * Perform I/O operations and call callbacks if something interesting happens.
+ * Should be called regularly.
+ */
+void flib_mapconn_tick(flib_mapconn *conn);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/md5/md5.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+	either statically or dynamically; added missing #include <string.h>
+	in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+	type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+	unsigned in ANSI C, signed in traditional"; made test program
+	self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+	/*
+	 * Determine dynamically whether this is a big-endian or
+	 * little-endian machine, since we can use a more efficient
+	 * algorithm on the latter.
+	 */
+	static const int w = 1;
+
+	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0		/* little-endian */
+	{
+	    /*
+	     * On little-endian machines, we can process properly aligned
+	     * data without copying it.
+	     */
+	    if (!((data - (const md5_byte_t *)0) & 3)) {
+		/* data are properly aligned */
+		X = (const md5_word_t *)data;
+	    } else {
+		/* not aligned */
+		memcpy(xbuf, data, 64);
+		X = xbuf;
+	    }
+	}
+#endif
+#if BYTE_ORDER == 0
+	else			/* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0		/* big-endian */
+	{
+	    /*
+	     * On big-endian machines, we must arrange the bytes in the
+	     * right order.
+	     */
+	    const md5_byte_t *xp = data;
+	    int i;
+
+#  if BYTE_ORDER == 0
+	    X = xbuf;		/* (dynamic only) */
+#  else
+#    define xbuf X		/* (static only) */
+#  endif
+	    for (i = 0; i < 16; ++i, xp += 4)
+		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+	}
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/md5/md5.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+	references to Ghostscript; clarified derivation from RFC 1321;
+	now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/gamesetup.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,55 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "gamesetup.h"
+#include "../util/util.h"
+
+#include <stdlib.h>
+
+void flib_gamesetup_destroy(flib_gamesetup *gamesetup) {
+	if(gamesetup) {
+		free(gamesetup->style);
+		flib_scheme_destroy(gamesetup->gamescheme);
+		flib_map_destroy(gamesetup->map);
+		flib_teamlist_destroy(gamesetup->teamlist);
+		free(gamesetup);
+	}
+}
+
+flib_gamesetup *flib_gamesetup_copy(const flib_gamesetup *setup) {
+	if(!setup) {
+		return NULL;
+	}
+
+	flib_gamesetup *result = flib_calloc(1, sizeof(flib_gamesetup));
+	if(result) {
+		result->style = flib_strdupnull(setup->style);
+		result->gamescheme = flib_scheme_copy(setup->gamescheme);
+		result->map = flib_map_copy(setup->map);
+		result->teamlist = flib_teamlist_copy(setup->teamlist);
+		if((setup->style && !result->style)
+				|| (setup->gamescheme && !result->gamescheme)
+				|| (setup->map && !result->map)
+				|| (setup->teamlist && !result->teamlist)) {
+			flib_gamesetup_destroy(result);
+			result = NULL;
+		}
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/gamesetup.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,47 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * A complete game configuration that contains all settings the engine needs to start a
+ * local or networked game.
+ */
+
+#ifndef MODEL_GAMESETUP_H_
+#define MODEL_GAMESETUP_H_
+
+#include "scheme.h"
+#include "weapon.h"
+#include "map.h"
+#include "teamlist.h"
+
+typedef struct {
+    char *style;				// e.g. "Capture the Flag"
+    flib_scheme *gamescheme;
+    flib_map *map;
+	flib_teamlist *teamlist;
+} flib_gamesetup;
+
+void flib_gamesetup_destroy(flib_gamesetup *gamesetup);
+
+/**
+ * Deep-copy of the flib_gamesetup.
+ */
+flib_gamesetup *flib_gamesetup_copy(const flib_gamesetup *gamesetup);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/map.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,110 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "map.h"
+
+#include "../util/inihelper.h"
+#include "../util/util.h"
+#include "../util/logging.h"
+
+#include <stdlib.h>
+
+flib_map *flib_map_create_regular(const char *seed, const char *theme, int templateFilter) {
+	if(log_badargs_if2(seed==NULL, theme==NULL)) {
+		return NULL;
+	}
+	flib_map newmap = {0};
+	newmap.mapgen = MAPGEN_REGULAR;
+	newmap.name = "+rnd+";
+	newmap.seed = (char*)seed;
+	newmap.theme = (char*)theme;
+	newmap.templateFilter = templateFilter;
+	return flib_map_copy(&newmap);
+}
+
+flib_map *flib_map_create_maze(const char *seed, const char *theme, int mazeSize) {
+	if(log_badargs_if2(seed==NULL, theme==NULL)) {
+		return NULL;
+	}
+	flib_map newmap = {0};
+	newmap.mapgen = MAPGEN_MAZE;
+	newmap.name = "+maze+";
+	newmap.seed = (char*)seed;
+	newmap.theme = (char*)theme;
+	newmap.mazeSize = mazeSize;
+	return flib_map_copy(&newmap);
+}
+
+flib_map *flib_map_create_named(const char *seed, const char *name) {
+	if(log_badargs_if2(seed==NULL, name==NULL)) {
+		return NULL;
+	}
+	flib_map newmap = {0};
+	newmap.mapgen = MAPGEN_NAMED;
+	newmap.name = (char*)name;
+	newmap.seed = (char*)seed;
+	return flib_map_copy(&newmap);
+}
+
+flib_map *flib_map_create_drawn(const char *seed, const char *theme, const uint8_t *drawData, size_t drawDataSize) {
+	if(log_badargs_if3(seed==NULL, theme==NULL, drawData==NULL)) {
+		return NULL;
+	}
+	flib_map newmap = {0};
+	newmap.mapgen = MAPGEN_DRAWN;
+	newmap.name = "+drawn+";
+	newmap.seed = (char*)seed;
+	newmap.theme = (char*)theme;
+	newmap.drawData = (uint8_t*) drawData;
+	newmap.drawDataSize = drawDataSize;
+	return flib_map_copy(&newmap);
+}
+
+flib_map *flib_map_copy(const flib_map *map) {
+	flib_map *result = NULL;
+	if(map) {
+		flib_map *newmap = flib_calloc(1, sizeof(flib_map));
+		if(newmap) {
+			newmap->mapgen = map->mapgen;
+			newmap->drawDataSize = map->drawDataSize;
+			newmap->drawData = flib_bufdupnull(map->drawData, map->drawDataSize);
+			newmap->mazeSize = map->mazeSize;
+			newmap->name = flib_strdupnull(map->name);
+			newmap->seed = flib_strdupnull(map->seed);
+			newmap->templateFilter = map->templateFilter;
+			newmap->theme = flib_strdupnull(map->theme);
+			if((newmap->drawData || !map->drawData) && (newmap->name || !map->name) && (newmap->seed || !map->seed) && (newmap->theme || !map->theme)) {
+				result = newmap;
+				newmap = NULL;
+			}
+		}
+		flib_map_destroy(newmap);
+	}
+	return result;
+}
+
+void flib_map_destroy(flib_map *map) {
+	if(map) {
+		free(map->seed);
+		free(map->drawData);
+		free(map->name);
+		free(map->theme);
+		free(map);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/map.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,114 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef MODEL_MAP_H_
+#define MODEL_MAP_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#define MAPGEN_REGULAR 0
+#define MAPGEN_MAZE 1
+#define MAPGEN_DRAWN 2
+#define MAPGEN_NAMED 3
+
+#define TEMPLATEFILTER_ALL 0
+#define TEMPLATEFILTER_SMALL 1
+#define TEMPLATEFILTER_MEDIUM 2
+#define TEMPLATEFILTER_LARGE 3
+#define TEMPLATEFILTER_CAVERN 4
+#define TEMPLATEFILTER_WACKY 5
+
+#define MAZE_SIZE_SMALL_TUNNELS 0
+#define MAZE_SIZE_MEDIUM_TUNNELS 1
+#define MAZE_SIZE_LARGE_TUNNELS 2
+#define MAZE_SIZE_SMALL_ISLANDS 3
+#define MAZE_SIZE_MEDIUM_ISLANDS 4
+#define MAZE_SIZE_LARGE_ISLANDS 5
+
+/**
+ * Data structure for defining a map. This contains the whole recipe to
+ * exactly recreate a particular map.
+ *
+ * The required fields depend on the map generator, see the comments
+ * at the struct for details.
+ */
+typedef struct {
+	int mapgen;				// Always one of the MAPGEN_ constants
+	char *name;				// The name of the map for MAPGEN_NAMED (e.g. "Cogs"), otherwise one of "+rnd+", "+maze+" or "+drawn+".
+	char *seed;				// Used for all maps. This is a random seed for all (non-AI) entropy in the round. Typically a random UUID, but can be any string.
+	char *theme;			// Used for all maps. This is the name of a directory in Data/Themes (e.g. "Beach")
+	uint8_t *drawData;		// Used for MAPGEN_DRAWN
+	size_t drawDataSize;	// Used for MAPGEN_DRAWN
+	int templateFilter;		// Used for MAPGEN_REGULAR. One of the TEMPLATEFILTER_xxx constants.
+	int mazeSize;			// Used for MAPGEN_MAZE. One of the MAZE_SIZE_xxx constants.
+} flib_map;
+
+/**
+ * Create a generated map. theme should be the name of a
+ * directory in "Themes" and templateFilter should be one of the
+ * TEMPLATEFILTER_* constants, but this is not checked before
+ * passing it to the engine.
+ *
+ * Use flib_map_destroy to free the returned object.
+ * No NULL parameters allowed, returns NULL on failure.
+ */
+flib_map *flib_map_create_regular(const char *seed, const char *theme, int templateFilter);
+
+/**
+ * Create a generated maze-type map. theme should be the name of a
+ * directory in "Themes" and mazeSize should be one of the
+ * MAZE_SIZE_* constants, but this is not checked before
+ * passing it to the engine.
+ *
+ * Use flib_map_destroy to free the returned object.
+ * No NULL parameters allowed, returns NULL on failure.
+ */
+flib_map *flib_map_create_maze(const char *seed, const char *theme, int mazeSize);
+
+/**
+ * Create a map from the Maps-Directory. name should be the name of a
+ * directory in "Maps", but this is not checked before
+ * passing it to the engine. If this is a mission, the corresponding
+ * script is used automatically.
+ *
+ * Use flib_map_destroy to free the returned object.
+ * No NULL parameters allowed, returns NULL on failure.
+ */
+flib_map *flib_map_create_named(const char *seed, const char *name);
+
+/**
+ * Create a hand-drawn map. Use flib_map_destroy to free the returned object.
+ * No NULL parameters allowed, returns NULL on failure.
+ */
+flib_map *flib_map_create_drawn(const char *seed, const char *theme, const uint8_t *drawData, size_t drawDataSize);
+
+/**
+ * Create a deep copy of the map. Returns NULL on failure or if NULL was passed.
+ */
+flib_map *flib_map_copy(const flib_map *map);
+
+/**
+ * Decrease the reference count of the object and free it if this was the last reference.
+ */
+void flib_map_destroy(flib_map *map);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/mapcfg.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,64 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "mapcfg.h"
+
+#include "../util/util.h"
+#include "../util/logging.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+void removeNewline(char *str) {
+	for(;*str;str++) {
+		if(*str=='\n' || *str=='\r') {
+			*str = 0;
+			return;
+		}
+	}
+}
+
+int flib_mapcfg_read(const char *dataDirPath, const char *mapname, flib_mapcfg *out) {
+	int result = -1;
+	if(!log_badargs_if4(dataDirPath==NULL, mapname==NULL, out==NULL, flib_contains_dir_separator(mapname))) {
+		char *path = flib_asprintf("%sMaps/%s/map.cfg", dataDirPath, mapname);
+		if(path) {
+			FILE *file = fopen(path, "rb");
+			if(!log_e_if(!file, "Unable to open map config file %s", path)) {
+				if(!log_e_if(!fgets(out->theme, sizeof(out->theme), file), "Error reading theme from %s", path)) {
+					removeNewline(out->theme);
+					char buf[64];
+					if(fgets(buf, sizeof(buf), file)) {
+						removeNewline(buf);
+						errno = 0;
+						out->hogLimit = strtol(buf, NULL, 10);
+						result = !log_e_if(errno, "Invalid hoglimit in %s: %i", path, buf);
+					} else {
+						result = 0;
+					}
+				}
+				fclose(file);
+			}
+		}
+		free(path);
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/mapcfg.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,38 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * Data structure and functions for accessing the map.cfg of named maps.
+ */
+
+#ifndef MAPCFG_H_
+#define MAPCFG_H_
+
+typedef struct {
+	char theme[256];
+	int hogLimit;
+} flib_mapcfg;
+
+/**
+ * Read the map configuration for the map with this name.
+ * The dataDirPath must end in a path separator.
+ */
+int flib_mapcfg_read(const char *dataDirPath, const char *mapname, flib_mapcfg *out);
+
+#endif /* MAPCFG_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/room.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,34 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "room.h"
+#include "../util/logging.h"
+
+#include <stdlib.h>
+
+void flib_room_destroy(flib_room *room) {
+	if(room) {
+		free(room->map);
+		free(room->name);
+		free(room->owner);
+		free(room->scheme);
+		free(room->weapons);
+		free(room);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/room.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,42 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Models the room information for the lobby roomlist.
+ */
+
+#ifndef ROOM_H_
+#define ROOM_H_
+
+#include <stdbool.h>
+
+typedef struct {
+    bool inProgress;	// true if the game is running
+    char *name;
+    int playerCount;
+    int teamCount;
+    char *owner;
+    char *map;			// This is either a map name, or one of +rnd+, +maze+ or +drawn+.
+    char *scheme;
+    char *weapons;
+} flib_room;
+
+void flib_room_destroy();
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/scheme.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,95 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "scheme.h"
+
+#include "../util/inihelper.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+
+flib_scheme *flib_scheme_create(const char *schemeName) {
+	flib_scheme *result = flib_calloc(1, sizeof(flib_scheme));
+	if(log_badargs_if(schemeName==NULL) || result==NULL) {
+		return NULL;
+	}
+
+	result->name = flib_strdupnull(schemeName);
+	result->mods = flib_calloc(flib_meta.modCount, sizeof(*result->mods));
+	result->settings = flib_calloc(flib_meta.settingCount, sizeof(*result->settings));
+
+	if(!result->mods || !result->settings || !result->name) {
+		flib_scheme_destroy(result);
+		return NULL;
+	}
+
+	for(int i=0; i<flib_meta.settingCount; i++) {
+		result->settings[i] = flib_meta.settings[i].def;
+	}
+	return result;
+}
+
+flib_scheme *flib_scheme_copy(const flib_scheme *scheme) {
+	flib_scheme *result = NULL;
+	if(scheme) {
+		result = flib_scheme_create(scheme->name);
+		if(result) {
+			memcpy(result->mods, scheme->mods, flib_meta.modCount * sizeof(*scheme->mods));
+			memcpy(result->settings, scheme->settings, flib_meta.settingCount * sizeof(*scheme->settings));
+		}
+	}
+	return result;
+}
+
+void flib_scheme_destroy(flib_scheme* scheme) {
+	if(scheme) {
+		free(scheme->mods);
+		free(scheme->settings);
+		free(scheme->name);
+		free(scheme);
+	}
+}
+
+bool flib_scheme_get_mod(const flib_scheme *scheme, const char *name) {
+	if(!log_badargs_if2(scheme==NULL, name==NULL)) {
+		for(int i=0; i<flib_meta.modCount; i++) {
+			if(!strcmp(flib_meta.mods[i].name, name)) {
+				return scheme->mods[i];
+			}
+		}
+		flib_log_e("Unable to find game mod %s", name);
+	}
+	return false;
+}
+
+int flib_scheme_get_setting(const flib_scheme *scheme, const char *name, int def) {
+	if(!log_badargs_if2(scheme==NULL, name==NULL)) {
+		for(int i=0; i<flib_meta.settingCount; i++) {
+			if(!strcmp(flib_meta.settings[i].name, name)) {
+				return scheme->settings[i];
+			}
+		}
+		flib_log_e("Unable to find game setting %s", name);
+	}
+	return def;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/scheme.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,72 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Data structures for game scheme information.
+ *
+ * The scheme consists of settings (integers) and mods (booleans). These are not fixed, but
+ * described in a "metascheme", which describes how each setting and mod is sent to the
+ * engine, and in which order they appear in the network protocol. The metascheme is defined
+ * in hwconsts.h
+ */
+
+#ifndef SCHEME_H_
+#define SCHEME_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "../hwconsts.h"
+
+/**
+ * The settings and mods arrays have the same number and order of elements
+ * as the corresponding arrays in the metascheme.
+ */
+typedef struct {
+    char *name;
+    int *settings;
+    bool *mods;
+} flib_scheme;
+
+/**
+ * Create a new configuration with everything set to default or false
+ * Returns NULL on error.
+ */
+flib_scheme *flib_scheme_create(const char *schemeName);
+
+/**
+ * Create a copy of the scheme. Returns NULL on error or if NULL was passed.
+ */
+flib_scheme *flib_scheme_copy(const flib_scheme *scheme);
+
+/**
+ * Decrease the reference count of the object and free it if this was the last reference.
+ */
+void flib_scheme_destroy(flib_scheme* scheme);
+
+/**
+ * Retrieve a mod setting by its name. If the mod is not found, logs an error and returns false.
+ */
+bool flib_scheme_get_mod(const flib_scheme *scheme, const char *name);
+
+/**
+ * Retrieve a game setting by its name. If the setting is not found, logs an error and returns def.
+ */
+int flib_scheme_get_setting(const flib_scheme *scheme, const char *name, int def);
+
+#endif /* SCHEME_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/schemelist.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,216 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "schemelist.h"
+
+#include "../util/inihelper.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../util/list.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+
+static char *makePrefixedName(int schemeIndex, const char *settingName) {
+	return flib_asprintf("%i\\%s", schemeIndex, settingName);
+}
+
+static int readSettingsFromIni(flib_ini *ini, flib_scheme *scheme, int index) {
+	bool error = false;
+	for(int i=0; i<flib_meta.settingCount && !error; i++) {
+		char *key = makePrefixedName(index, flib_meta.settings[i].name);
+		if(!key) {
+			error = true;
+		} else if(flib_ini_get_int_opt(ini, &scheme->settings[i], key, flib_meta.settings[i].def)) {
+			flib_log_e("Error reading setting %s in schemes file.", key);
+			error = true;
+		}
+		free(key);
+	}
+	return error;
+}
+
+static int readModsFromIni(flib_ini *ini, flib_scheme *scheme, int index) {
+	bool error = false;
+	for(int i=0; i<flib_meta.modCount && !error; i++) {
+		char *key = makePrefixedName(index, flib_meta.mods[i].name);
+		if(!key) {
+			error = true;
+		} else if(flib_ini_get_bool_opt(ini, &scheme->mods[i], key, false)) {
+			flib_log_e("Error reading mod %s in schemes file.", key);
+			error = true;
+		}
+		free(key);
+	}
+	return error;
+}
+
+static flib_scheme *readSchemeFromIni(flib_ini *ini, int index) {
+	flib_scheme *result = NULL;
+	char *schemeNameKey = makePrefixedName(index+1, "name");
+	if(schemeNameKey) {
+		char *schemeName = NULL;
+		if(!flib_ini_get_str_opt(ini, &schemeName, schemeNameKey, "Unnamed")) {
+			flib_scheme *tmpScheme = flib_scheme_create(schemeName);
+			if(tmpScheme) {
+				if(!readSettingsFromIni(ini, tmpScheme, index) && !readModsFromIni(ini, tmpScheme, index)) {
+					result = tmpScheme;
+					tmpScheme = NULL;
+				}
+			}
+			flib_scheme_destroy(tmpScheme);
+		}
+		free(schemeName);
+	}
+	free(schemeNameKey);
+	return result;
+}
+
+static flib_schemelist *fromIniHandleError(flib_schemelist *result, flib_ini *ini) {
+	flib_ini_destroy(ini);
+	flib_schemelist_destroy(result);
+	return NULL;
+}
+
+flib_schemelist *flib_schemelist_from_ini(const char *filename) {
+	if(log_badargs_if(filename==NULL)) {
+		return NULL;
+	}
+
+	flib_schemelist *list = NULL;
+	flib_ini *ini = flib_ini_load(filename);
+	if(!ini || flib_ini_enter_section(ini, "schemes")) {
+		flib_log_e("Missing file or missing section \"schemes\" in file %s.", filename);
+		return fromIniHandleError(list, ini);
+	}
+
+	list = flib_schemelist_create();
+	if(!list) {
+		return fromIniHandleError(list, ini);
+	}
+
+	int schemeCount = 0;
+	if(flib_ini_get_int(ini, &schemeCount, "size")) {
+		flib_log_e("Missing or malformed scheme count in config file %s.", filename);
+		return fromIniHandleError(list, ini);
+	}
+
+	for(int i=0; i<schemeCount; i++) {
+		flib_scheme *scheme = readSchemeFromIni(ini, i);
+		if(!scheme || flib_schemelist_insert(list, scheme, i)) {
+			flib_scheme_destroy(scheme);
+			flib_log_e("Error reading scheme %i from config file %s.", i, filename);
+			return fromIniHandleError(list, ini);
+		}
+	}
+
+
+	flib_ini_destroy(ini);
+	return list;
+}
+
+static int writeSchemeToIni(const flib_scheme *scheme, flib_ini *ini, int index) {
+	bool error = false;
+
+	char *key = makePrefixedName(index+1, "name");
+	error |= !key || flib_ini_set_str(ini, key, scheme->name);
+	free(key);
+
+	for(int i=0; i<flib_meta.modCount && !error; i++) {
+		char *key = makePrefixedName(index+1, flib_meta.mods[i].name);
+		error |= !key || flib_ini_set_bool(ini, key, scheme->mods[i]);
+		free(key);
+	}
+
+	for(int i=0; i<flib_meta.settingCount && !error; i++) {
+		char *key = makePrefixedName(index+1, flib_meta.settings[i].name);
+		error |= !key || flib_ini_set_int(ini, key, scheme->settings[i]);
+		free(key);
+	}
+	return error;
+}
+
+int flib_schemelist_to_ini(const char *filename, const flib_schemelist *schemes) {
+	int result = -1;
+	if(!log_badargs_if2(filename==NULL, schemes==NULL)) {
+		flib_ini *ini = flib_ini_create(NULL);
+		if(ini && !flib_ini_create_section(ini, "schemes")) {
+			bool error = false;
+			error |= flib_ini_set_int(ini, "size", schemes->schemeCount);
+			for(int i=0; i<schemes->schemeCount && !error; i++) {
+				error |= writeSchemeToIni(schemes->schemes[i], ini, i);
+			}
+
+			if(!error) {
+				result = flib_ini_save(ini, filename);
+			}
+		}
+		flib_ini_destroy(ini);
+	}
+	return result;
+}
+
+flib_schemelist *flib_schemelist_create() {
+	return flib_calloc(1, sizeof(flib_schemelist));
+}
+
+void flib_schemelist_destroy(flib_schemelist *list) {
+	if(list) {
+		for(int i=0; i<list->schemeCount; i++) {
+			flib_scheme_destroy(list->schemes[i]);
+		}
+		free(list->schemes);
+		free(list);
+	}
+}
+
+flib_scheme *flib_schemelist_find(flib_schemelist *list, const char *name) {
+	if(!log_badargs_if2(list==NULL, name==NULL)) {
+		for(int i=0; i<list->schemeCount; i++) {
+			if(!strcmp(name, list->schemes[i]->name)) {
+				return list->schemes[i];
+			}
+		}
+	}
+	return NULL;
+}
+
+GENERATE_STATIC_LIST_INSERT(insertScheme, flib_scheme*)
+GENERATE_STATIC_LIST_DELETE(deleteScheme, flib_scheme*)
+
+int flib_schemelist_insert(flib_schemelist *list, flib_scheme *cfg, int pos) {
+	if(!log_badargs_if2(list==NULL, cfg==NULL)
+			&& !insertScheme(&list->schemes, &list->schemeCount, cfg, pos)) {
+		return 0;
+	}
+	return -1;
+}
+
+int flib_schemelist_delete(flib_schemelist *list, int pos) {
+	if(!log_badargs_if(list==NULL)) {
+		flib_scheme *elem = list->schemes[pos];
+		if(!deleteScheme(&list->schemes, &list->schemeCount, pos)) {
+			flib_scheme_destroy(elem);
+			return 0;
+		}
+	}
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/schemelist.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,79 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Functions for managing a list of schemes.
+ * This is in here because the scheme config file of the QtFrontend (which we are staying compatible with) contains
+ * all the schemes at once, so we need functions to work with a list like that.
+ */
+
+#ifndef SCHEMELIST_H_
+#define SCHEMELIST_H_
+
+#include "scheme.h"
+
+typedef struct {
+	int schemeCount;
+	flib_scheme **schemes;
+} flib_schemelist;
+
+/**
+ * Load a list of configurations from the ini file.
+ * Returns NULL on error.
+ */
+flib_schemelist *flib_schemelist_from_ini(const char *filename);
+
+/**
+ * Store the list of configurations to an ini file.
+ * Returns NULL on error.
+ */
+int flib_schemelist_to_ini(const char *filename, const flib_schemelist *config);
+
+/**
+ * Create an empty scheme list. Returns NULL on error.
+ */
+flib_schemelist *flib_schemelist_create();
+
+/**
+ * Insert a new scheme into the list at position pos, moving all higher schemes to make place.
+ * pos must be at least 0 (insert at the start) and at most list->schemeCount (insert at the end).
+ * Ownership of the scheme is transferred to the list.
+ * Returns 0 on success.
+ */
+int flib_schemelist_insert(flib_schemelist *list, flib_scheme *cfg, int pos);
+
+/**
+ * Delete a scheme from the list at position pos, moving down all higher schemes.
+ * The scheme is destroyed.
+ * Returns 0 on success.
+ */
+int flib_schemelist_delete(flib_schemelist *list, int pos);
+
+/**
+ * Find the scheme with a specific name
+ */
+flib_scheme *flib_schemelist_find(flib_schemelist *list, const char *name);
+
+/**
+ * Free this schemelist and all contained schemes
+ */
+void flib_schemelist_destroy(flib_schemelist *list);
+
+
+#endif /* SCHEMELIST_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/team.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,323 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "team.h"
+
+#include "../util/inihelper.h"
+#include "../util/util.h"
+#include "../util/logging.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+static flib_team *from_ini_handleError(flib_team *result, flib_ini *settingfile) {
+	flib_ini_destroy(settingfile);
+	flib_team_destroy(result);
+	return NULL;
+}
+
+flib_team *flib_team_from_ini(const char *filename) {
+	if(log_badargs_if(filename==NULL)) {
+		return NULL;
+	}
+
+	flib_team *result = flib_calloc(1, sizeof(flib_team));
+	flib_ini *ini = NULL;
+
+	if(!result) {
+		return from_ini_handleError(result, ini);
+	}
+
+	ini = flib_ini_load(filename);
+	if(!ini) {
+		flib_log_e("Error loading team file %s", filename);
+		return from_ini_handleError(result, ini);
+	}
+
+	if(flib_ini_enter_section(ini, "team")) {
+		flib_log_e("Missing section \"Team\" in team file %s", filename);
+		return from_ini_handleError(result, ini);
+	}
+	bool error = false;
+	error |= flib_ini_get_str(ini, &result->name, "name");
+	error |= flib_ini_get_str(ini, &result->grave, "grave");
+	error |= flib_ini_get_str(ini, &result->fort, "fort");
+	error |= flib_ini_get_str(ini, &result->voicepack, "voicepack");
+	error |= flib_ini_get_str(ini, &result->flag, "flag");
+	error |= flib_ini_get_int(ini, &result->rounds, "rounds");
+	error |= flib_ini_get_int(ini, &result->wins, "wins");
+	error |= flib_ini_get_int(ini, &result->campaignProgress, "campaignprogress");
+
+	int difficulty = 0;
+	error |= flib_ini_get_int(ini, &difficulty, "difficulty");
+
+	if(error) {
+		flib_log_e("Missing or malformed entry in section \"Team\" in file %s", filename);
+		return from_ini_handleError(result, ini);
+	}
+
+	for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+		char sectionName[32];
+		if(snprintf(sectionName, sizeof(sectionName), "hedgehog%i", i) <= 0) {
+			return from_ini_handleError(result, ini);
+		}
+		if(flib_ini_enter_section(ini, sectionName)) {
+			flib_log_e("Missing section \"%s\" in team file %s", sectionName, filename);
+			return from_ini_handleError(result, ini);
+		}
+		flib_hog *hog = &result->hogs[i];
+		error |= flib_ini_get_str(ini, &hog->name, "name");
+		error |= flib_ini_get_str(ini, &hog->hat, "hat");
+		error |= flib_ini_get_int(ini, &hog->rounds, "rounds");
+		error |= flib_ini_get_int(ini, &hog->kills, "kills");
+		error |= flib_ini_get_int(ini, &hog->deaths, "deaths");
+		error |= flib_ini_get_int(ini, &hog->suicides, "suicides");
+		result->hogs[i].difficulty = difficulty;
+		result->hogs[i].initialHealth = TEAM_DEFAULT_HEALTH;
+
+		if(error) {
+			flib_log_e("Missing or malformed entry in section \"%s\" in file %s", sectionName, filename);
+			return from_ini_handleError(result, ini);
+		}
+	}
+
+	if(!flib_ini_enter_section(ini, "binds")) {
+		result->bindingCount = flib_ini_get_keycount(ini);
+		if(result->bindingCount<0) {
+			flib_log_e("Error reading bindings from file %s", filename);
+			result->bindingCount = 0;
+		}
+		result->bindings = flib_calloc(result->bindingCount, sizeof(flib_binding));
+		if(!result->bindings) {
+			return from_ini_handleError(result, ini);
+		}
+		for(int i=0; i<result->bindingCount; i++) {
+			char *keyname = flib_ini_get_keyname(ini, i);
+			if(!keyname) {
+				error = true;
+			} else {
+				result->bindings[i].action = flib_urldecode(keyname);
+				error |= !result->bindings[i].action;
+				error |= flib_ini_get_str(ini, &result->bindings[i].binding, keyname);
+			}
+			free(keyname);
+		}
+	}
+
+	if(error) {
+		flib_log_e("Error reading team file %s", filename);
+		return from_ini_handleError(result, ini);
+	}
+
+	flib_ini_destroy(ini);
+	return result;
+}
+
+void flib_team_destroy(flib_team *team) {
+	if(team) {
+		for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+			free(team->hogs[i].name);
+			free(team->hogs[i].hat);
+			flib_weaponset_destroy(team->hogs[i].weaponset);
+		}
+		free(team->name);
+		free(team->grave);
+		free(team->fort);
+		free(team->voicepack);
+		free(team->flag);
+		if(team->bindings) {
+			for(int i=0; i<team->bindingCount; i++) {
+				free(team->bindings[i].action);
+				free(team->bindings[i].binding);
+			}
+		}
+		free(team->bindings);
+		free(team->ownerName);
+		free(team);
+	}
+}
+
+static int writeTeamSection(const flib_team *team, flib_ini *ini) {
+	if(flib_ini_create_section(ini, "team")) {
+		return -1;
+	}
+	bool error = false;
+	error |= flib_ini_set_str(ini, "name",  team->name);
+	error |= flib_ini_set_str(ini, "grave", team->grave);
+	error |= flib_ini_set_str(ini, "fort", team->fort);
+	error |= flib_ini_set_str(ini, "voicepack", team->voicepack);
+	error |= flib_ini_set_str(ini, "flag", team->flag);
+	error |= flib_ini_set_int(ini, "rounds", team->rounds);
+	error |= flib_ini_set_int(ini, "wins", team->wins);
+	error |= flib_ini_set_int(ini, "campaignprogress", team->campaignProgress);
+	error |= flib_ini_set_int(ini, "difficulty", team->hogs[0].difficulty);
+	return error;
+}
+
+static int writeHogSections(const flib_team *team, flib_ini *ini) {
+	for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+		const flib_hog *hog = &team->hogs[i];
+		char sectionName[32];
+		if(snprintf(sectionName, sizeof(sectionName), "hedgehog%i", i) <= 0) {
+			return -1;
+		}
+		if(flib_ini_create_section(ini, sectionName)) {
+			return -1;
+		}
+		bool error = false;
+		error |= flib_ini_set_str(ini, "name", hog->name);
+		error |= flib_ini_set_str(ini, "hat", hog->hat);
+		error |= flib_ini_set_int(ini, "rounds", hog->rounds);
+		error |= flib_ini_set_int(ini, "kills", hog->kills);
+		error |= flib_ini_set_int(ini, "deaths", hog->deaths);
+		error |= flib_ini_set_int(ini, "suicides", hog->suicides);
+		if(error) {
+			return error;
+		}
+	}
+	return 0;
+}
+
+static int writeBindingSection(const flib_team *team, flib_ini *ini) {
+	if(team->bindingCount == 0) {
+		return 0;
+	}
+	if(flib_ini_create_section(ini, "binds")) {
+		return -1;
+	}
+	for(int i=0; i<team->bindingCount; i++) {
+		bool error = false;
+		char *action = flib_urlencode(team->bindings[i].action);
+		if(action) {
+			error |= flib_ini_set_str(ini, action, team->bindings[i].binding);
+			free(action);
+		} else {
+			error = true;
+		}
+		if(error) {
+			return error;
+		}
+	}
+	return 0;
+}
+
+int flib_team_to_ini(const char *filename, const flib_team *team) {
+	int result = -1;
+	if(!log_badargs_if2(filename==NULL, team==NULL)) {
+		flib_ini *ini = flib_ini_create(filename);
+		bool error = false;
+		error |= writeTeamSection(team, ini);
+		error |= writeHogSections(team, ini);
+		error |= writeBindingSection(team, ini);
+		if(!error) {
+			result = flib_ini_save(ini, filename);
+		}
+		flib_ini_destroy(ini);
+	}
+	return result;
+}
+
+int flib_team_set_weaponset(flib_team *team, const flib_weaponset *set) {
+	if(team) {
+		for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+			flib_weaponset_destroy(team->hogs[i].weaponset);
+			team->hogs[i].weaponset = flib_weaponset_copy(set);
+			if(set && !team->hogs[i].weaponset) {
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+void flib_team_set_health(flib_team *team, int health) {
+	if(team) {
+		for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+			team->hogs[i].initialHealth = health;
+		}
+	}
+}
+
+static char *strdupWithError(const char *in, bool *error) {
+	char *out = flib_strdupnull(in);
+	if(in && !out) {
+		*error = true;
+	}
+	return out;
+}
+
+flib_team *flib_team_copy(const flib_team *team) {
+	flib_team *result = NULL;
+	if(team) {
+		flib_team *tmpTeam = flib_calloc(1, sizeof(flib_team));
+		if(tmpTeam) {
+			bool error = false;
+
+			for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+				tmpTeam->hogs[i].name = strdupWithError(team->hogs[i].name, &error);
+				tmpTeam->hogs[i].hat = strdupWithError(team->hogs[i].hat, &error);
+				tmpTeam->hogs[i].rounds = team->hogs[i].rounds;
+				tmpTeam->hogs[i].kills = team->hogs[i].kills;
+				tmpTeam->hogs[i].deaths = team->hogs[i].deaths;
+				tmpTeam->hogs[i].suicides = team->hogs[i].suicides;
+				tmpTeam->hogs[i].difficulty = team->hogs[i].difficulty;
+				tmpTeam->hogs[i].initialHealth = team->hogs[i].initialHealth;
+				tmpTeam->hogs[i].weaponset = flib_weaponset_copy(team->hogs[i].weaponset);
+				if(team->hogs[i].weaponset && !tmpTeam->hogs[i].weaponset) {
+					error = true;
+				}
+			}
+
+			tmpTeam->name = strdupWithError(team->name, &error);
+			tmpTeam->grave = strdupWithError(team->grave, &error);
+			tmpTeam->fort = strdupWithError(team->fort, &error);
+			tmpTeam->voicepack = strdupWithError(team->voicepack, &error);
+			tmpTeam->flag = strdupWithError(team->flag, &error);
+			tmpTeam->ownerName = strdupWithError(team->ownerName, &error);
+
+			tmpTeam->bindingCount = team->bindingCount;
+			if(team->bindings) {
+				tmpTeam->bindings = flib_calloc(team->bindingCount, sizeof(flib_binding));
+				if(tmpTeam->bindings) {
+					for(int i=0; i<tmpTeam->bindingCount; i++) {
+						tmpTeam->bindings[i].action = strdupWithError(team->bindings[i].action, &error);
+						tmpTeam->bindings[i].binding = strdupWithError(team->bindings[i].binding, &error);
+					}
+				} else {
+					error = true;
+				}
+			}
+
+			tmpTeam->rounds = team->rounds;
+			tmpTeam->wins = team->wins;
+			tmpTeam->campaignProgress = team->campaignProgress;
+
+			tmpTeam->colorIndex = team->colorIndex;
+			tmpTeam->hogsInGame = team->hogsInGame;
+			tmpTeam->remoteDriven = team->remoteDriven;
+
+			if(!error) {
+				result = tmpTeam;
+				tmpTeam = 0;
+			}
+		}
+		flib_team_destroy(tmpTeam);
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/team.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,130 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * This file defines a data structure for a hedgewars team.
+ *
+ * Teams are used in several different contexts in Hedgewars, and some of these require
+ * extra information about teams. For example, the weaponset is important
+ * to the engine, but not for ini reading/writing, and with the team statistics it is the
+ * other way around. To keep things simple, the data structure can hold all information
+ * used in any context. On the downside, that means we can't use static typing to ensure
+ * that team information is "complete" for a particular purpose.
+ */
+#ifndef TEAM_H_
+#define TEAM_H_
+
+
+#include "weapon.h"
+#include "../hwconsts.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define TEAM_DEFAULT_HEALTH 100
+
+/**
+ * Struct representing a single keybinding.
+ */
+typedef struct {
+	char *action;
+	char *binding;
+} flib_binding;
+
+typedef struct {
+	char *name;
+	char *hat;			// e.g. hair_yellow; References a .png file in Data/Graphics/Hats
+
+	// Statistics. They are irrelevant for the engine or server,
+	// but provided for ini reading/writing by the frontend.
+	int rounds;
+	int kills;
+	int deaths;
+	int suicides;
+
+	int difficulty;		// 0 = human, 1 = most difficult bot ... 5 = least difficult bot (somewhat counterintuitive)
+
+	// Transient setting used in game setup
+	int initialHealth;
+	flib_weaponset *weaponset;
+} flib_hog;
+
+typedef struct {
+	flib_hog hogs[HEDGEHOGS_PER_TEAM];
+	char *name;
+	char *grave;		// e.g. "Bone"; References a .png file in Data/Graphics/Graves
+	char *fort;			// e.g. "Castle"; References a series of files in Data/Forts
+	char *voicepack;	// e.g. "Classic"; References a directory in Data/Sounds/voices
+	char *flag;			// e.g. "hedgewars"; References a .png file in Data/Graphics/Flags
+
+	flib_binding *bindings;
+	int bindingCount;
+
+	// Statistics. They are irrelevant for the engine or server,
+	// but provided for ini reading/writing by the frontend.
+	int rounds;
+	int wins;
+	int campaignProgress;
+
+	// Transient settings used in game setup
+	int colorIndex;		// Index into a color table
+	int hogsInGame;		// The number of hogs that will actually play
+	bool remoteDriven;	// true for non-local teams in a network game
+	char *ownerName;	// Username of the owner of a team in a network game
+} flib_team;
+
+/**
+ * Free all memory associated with the team
+ */
+void flib_team_destroy(flib_team *team);
+
+/**
+ * Loads a team, returns NULL on error. Destroy this team using flib_team_destroy.
+ * This will not fill in the fields marked as "transient" in the structs above.
+ */
+flib_team *flib_team_from_ini(const char *filename);
+
+/**
+ * Write the team to an ini file. Attempts to retain extra ini settings
+ * that were already present. Note that not all fields of a team struct
+ * are stored in the ini, some are only used intermittently to store
+ * information about a team in the context of a game.
+ *
+ * The flib_team can handle "difficulty" on a per-hog basis, but it
+ * is only written per-team in the team file. The difficulty of the
+ * first hog is used for the entire team when writing.
+ */
+int flib_team_to_ini(const char *filename, const flib_team *team);
+
+/**
+ * Set the same weaponset for every hog in the team
+ */
+int flib_team_set_weaponset(flib_team *team, const flib_weaponset *set);
+
+/**
+ * Set the same initial health for every hog.
+ */
+void flib_team_set_health(flib_team *team, int health);
+
+/**
+ * Create a deep copy of a team. Returns NULL on failure.
+ */
+flib_team *flib_team_copy(const flib_team *team);
+
+#endif /* TEAM_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/teamlist.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,120 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "teamlist.h"
+
+#include "../util/util.h"
+#include "../util/list.h"
+#include "../util/logging.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+flib_teamlist *flib_teamlist_create() {
+	return flib_calloc(1, sizeof(flib_teamlist));
+}
+
+void flib_teamlist_destroy(flib_teamlist *list) {
+	if(list) {
+		for(int i=0; i<list->teamCount; i++) {
+			flib_team_destroy(list->teams[i]);
+		}
+		free(list->teams);
+		free(list);
+	}
+}
+
+GENERATE_STATIC_LIST_INSERT(insertTeam, flib_team*)
+GENERATE_STATIC_LIST_DELETE(deleteTeam, flib_team*)
+
+static int findTeam(const flib_teamlist *list, const char *name) {
+	for(int i=0; i<list->teamCount; i++) {
+		if(!strcmp(name, list->teams[i]->name)) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+int flib_teamlist_insert(flib_teamlist *list, flib_team *team, int pos) {
+	if(!log_badargs_if2(list==NULL, team==NULL)
+			&& !insertTeam(&list->teams, &list->teamCount, team, pos)) {
+		return 0;
+	}
+	return -1;
+}
+
+int flib_teamlist_delete(flib_teamlist *list, const char *name) {
+	int result = -1;
+	if(!log_badargs_if2(list==NULL, name==NULL)) {
+		int itemid = findTeam(list, name);
+		if(itemid>=0) {
+			flib_team *team = list->teams[itemid];
+			if(!deleteTeam(&list->teams, &list->teamCount, itemid)) {
+				flib_team_destroy(team);
+				result = 0;
+			}
+		}
+	}
+	return result;
+}
+
+flib_team *flib_teamlist_find(const flib_teamlist *list, const char *name) {
+	flib_team *result = NULL;
+	if(!log_badargs_if2(list==NULL, name==NULL)) {
+		int itemid = findTeam(list, name);
+		if(itemid>=0) {
+			result = list->teams[itemid];
+		}
+	}
+	return result;
+}
+
+void flib_teamlist_clear(flib_teamlist *list) {
+	if(!log_badargs_if(list==NULL)) {
+		for(int i=0; i<list->teamCount; i++) {
+			flib_team_destroy(list->teams[i]);
+		}
+		free(list->teams);
+		list->teams = NULL;
+		list->teamCount = 0;
+	}
+}
+
+flib_teamlist *flib_teamlist_copy(flib_teamlist *list) {
+	if(!list) {
+		return NULL;
+	}
+	flib_teamlist *result = flib_teamlist_create();
+	if(result) {
+		bool error = false;
+		for(int i=0; !error && i<list->teamCount; i++) {
+			flib_team *teamcopy = flib_team_copy(list->teams[i]);
+			if(!teamcopy || flib_teamlist_insert(result, teamcopy, i)) {
+				flib_team_destroy(teamcopy);
+				error = true;
+			}
+		}
+		if(error) {
+			flib_teamlist_destroy(result);
+			result = NULL;
+		}
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/teamlist.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,61 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef TEAMLIST_H_
+#define TEAMLIST_H_
+
+#include "team.h"
+
+typedef struct {
+	int teamCount;
+	flib_team **teams;
+} flib_teamlist;
+
+flib_teamlist *flib_teamlist_create();
+
+void flib_teamlist_destroy(flib_teamlist *list);
+
+/**
+ * Insert a team into the list. The list takes ownership of the team. Returns 0 on success.
+ */
+int flib_teamlist_insert(flib_teamlist *list, flib_team *team, int pos);
+
+/**
+ * Delete the team with the name [name] from the list and destroys it.
+ * Returns 0 on success.
+ */
+int flib_teamlist_delete(flib_teamlist *list, const char *name);
+
+/**
+ * Returns the team with the name [name] from the list if it exists, NULL otherwise
+ */
+flib_team *flib_teamlist_find(const flib_teamlist *list, const char *name);
+
+/**
+ * Removes all items from the list and destroys them.
+ */
+void flib_teamlist_clear(flib_teamlist *list);
+
+/**
+ * Create a copy of the list and all the teams it contains. Weaponsets are not copied, but
+ * kept as references
+ */
+flib_teamlist *flib_teamlist_copy(flib_teamlist *list);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/weapon.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,234 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "weapon.h"
+
+#include "../util/inihelper.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../util/list.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+static void setField(char field[WEAPONS_COUNT+1], const char *line, int lineLen, bool no9) {
+	if(lineLen>WEAPONS_COUNT) {
+		lineLen = WEAPONS_COUNT;
+	}
+
+	char min = '0';
+	char max = no9 ? '8' : '9';
+	for(int i=0; i<lineLen; i++) {
+		if(line[i] >= min && line[i] <= max) {
+			field[i] = line[i];
+		} else {
+			flib_log_w("Invalid character in weapon config string \"%.*s\", position %i", lineLen, line, i);
+			field[i] = '0';
+		}
+	}
+	for(int i=lineLen; i<WEAPONS_COUNT; i++) {
+		field[i] = '0';
+	}
+	field[WEAPONS_COUNT] = 0;
+}
+
+flib_weaponset *flib_weaponset_create(const char *name) {
+	flib_weaponset *result = NULL;
+	if(!log_badargs_if(name==NULL)) {
+		flib_weaponset *newSet = flib_calloc(1, sizeof(flib_weaponset));
+		if(newSet) {
+			newSet->name = flib_strdupnull(name);
+			if(newSet->name) {
+				setField(newSet->loadout, "", 0, false);
+				setField(newSet->crateprob, "", 0, false);
+				setField(newSet->crateammo, "", 0, false);
+				setField(newSet->delay, "", 0, false);
+				result = newSet;
+				newSet = NULL;
+			}
+		}
+		flib_weaponset_destroy(newSet);
+	}
+	return result;
+}
+
+void flib_weaponset_destroy(flib_weaponset *cfg) {
+	if(cfg) {
+		free(cfg->name);
+		free(cfg);
+	}
+}
+
+flib_weaponset *flib_weaponset_copy(const flib_weaponset *weaponset) {
+	if(!weaponset) {
+		return NULL;
+	}
+
+	flib_weaponset *result = flib_weaponset_create(weaponset->name);
+	if(result) {
+		memcpy(result->loadout, weaponset->loadout, WEAPONS_COUNT+1);
+		memcpy(result->crateprob, weaponset->crateprob, WEAPONS_COUNT+1);
+		memcpy(result->delay, weaponset->delay, WEAPONS_COUNT+1);
+		memcpy(result->crateammo, weaponset->crateammo, WEAPONS_COUNT+1);
+	}
+
+	return result;
+}
+
+void flib_weaponsetlist_destroy(flib_weaponsetlist *list) {
+	if(list) {
+		for(int i=0; i<list->weaponsetCount; i++) {
+			flib_weaponset_destroy(list->weaponsets[i]);
+		}
+		free(list->weaponsets);
+		free(list);
+	}
+}
+
+flib_weaponset *flib_weaponset_from_ammostring(const char *name, const char *ammostring) {
+	flib_weaponset *result = NULL;
+	if(!log_badargs_if2(name==NULL, ammostring==NULL)) {
+		result = flib_weaponset_create(name);
+		if(result) {
+			int fieldlen = strlen(ammostring)/4;
+			setField(result->loadout, ammostring, fieldlen, false);
+			setField(result->crateprob, ammostring + fieldlen, fieldlen, true);
+			setField(result->delay, ammostring + 2*fieldlen, fieldlen, true);
+			setField(result->crateammo, ammostring + 3*fieldlen, fieldlen, true);
+		}
+	}
+	return result;
+}
+
+static int fillWeaponsetFromIni(flib_weaponsetlist *list, flib_ini *ini, int index) {
+	int result = -1;
+	char *keyname = flib_ini_get_keyname(ini, index);
+	char *decodedKeyname = flib_urldecode(keyname);
+	char *ammostring = NULL;
+	if(decodedKeyname && !flib_ini_get_str(ini, &ammostring, keyname)) {
+		flib_weaponset *set = flib_weaponset_from_ammostring(decodedKeyname, ammostring);
+		if(set) {
+			result = flib_weaponsetlist_insert(list, set, list->weaponsetCount);
+			if(result) {
+				flib_weaponset_destroy(set);
+			}
+		}
+	}
+	free(ammostring);
+	free(decodedKeyname);
+	free(keyname);
+	return result;
+}
+
+static int fillWeaponsetsFromIni(flib_weaponsetlist *list, flib_ini *ini) {
+	bool error = false;
+	int weaponsets = flib_ini_get_keycount(ini);
+
+	for(int i=0; i<weaponsets && !error; i++) {
+		error |= fillWeaponsetFromIni(list, ini, i);
+	}
+	return error;
+}
+
+flib_weaponsetlist *flib_weaponsetlist_from_ini(const char *filename) {
+	flib_weaponsetlist *result = NULL;
+	if(!log_badargs_if(filename==NULL)) {
+		flib_ini *ini = flib_ini_load(filename);
+		if(!ini) {
+			flib_log_e("Missing file %s.", filename);
+		} else if(flib_ini_enter_section(ini, "General")) {
+			flib_log_e("Missing section \"General\" in file %s.", filename);
+		} else {
+			flib_weaponsetlist *tmpList = flib_weaponsetlist_create();
+			if(tmpList && !fillWeaponsetsFromIni(tmpList, ini)) {
+				result = tmpList;
+				tmpList = NULL;
+			}
+			flib_weaponsetlist_destroy(tmpList);
+		}
+		flib_ini_destroy(ini);
+	}
+	return result;
+}
+
+static bool needsEscape(char c) {
+	return !((c>='0' && c<='9') || (c>='a' && c <='z'));
+}
+
+static int writeWeaponsetToIni(flib_ini *ini, flib_weaponset *set) {
+	int result = -1;
+	char weaponstring[WEAPONS_COUNT*4+1];
+	strcpy(weaponstring, set->loadout);
+	strcat(weaponstring, set->crateprob);
+	strcat(weaponstring, set->delay);
+	strcat(weaponstring, set->crateammo);
+
+	char *escapedname = flib_urlencode_pred(set->name, needsEscape);
+	if(escapedname) {
+		result = flib_ini_set_str(ini, escapedname, weaponstring);
+	}
+	free(escapedname);
+	return result;
+}
+
+int flib_weaponsetlist_to_ini(const char *filename, const flib_weaponsetlist *list) {
+	int result = -1;
+	if(!log_badargs_if2(filename==NULL, list==NULL)) {
+		flib_ini *ini = flib_ini_create(NULL);
+		if(ini && !flib_ini_create_section(ini, "General")) {
+			bool error = false;
+			for(int i=0; i<list->weaponsetCount && !error; i++) {
+				error |= writeWeaponsetToIni(ini, list->weaponsets[i]);
+			}
+
+			if(!error) {
+				result = flib_ini_save(ini, filename);
+			}
+		}
+		flib_ini_destroy(ini);
+	}
+	return result;
+}
+
+flib_weaponsetlist *flib_weaponsetlist_create() {
+	return flib_calloc(1, sizeof(flib_weaponsetlist));
+}
+
+GENERATE_STATIC_LIST_INSERT(insertWeaponset, flib_weaponset*)
+GENERATE_STATIC_LIST_DELETE(deleteWeaponset, flib_weaponset*)
+
+int flib_weaponsetlist_insert(flib_weaponsetlist *list, flib_weaponset *set, int pos) {
+	if(!log_badargs_if2(list==NULL, set==NULL)
+			&& !insertWeaponset(&list->weaponsets, &list->weaponsetCount, set, pos)) {
+		return 0;
+	}
+	return -1;
+}
+
+int flib_weaponsetlist_delete(flib_weaponsetlist *list, int pos) {
+	if(!log_badargs_if(list==NULL)) {
+		flib_weaponset *elem = list->weaponsets[pos];
+		if(!deleteWeaponset(&list->weaponsets, &list->weaponsetCount, pos)) {
+			flib_weaponset_destroy(elem);
+			return 0;
+		}
+	}
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/model/weapon.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,104 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef MODEL_WEAPON_H_
+#define MODEL_WEAPON_H_
+
+#include "../hwconsts.h"
+
+/**
+ * These values are all ASCII characters in the range '0'..'9'
+ * The fields are zero-terminated so they can easily be used as strings.
+ *
+ * For loadout, 9 means inifinite ammo.
+ * For the other setting, 9 is invalid.
+ */
+typedef struct {
+	char loadout[WEAPONS_COUNT+1];
+	char crateprob[WEAPONS_COUNT+1];
+	char crateammo[WEAPONS_COUNT+1];
+	char delay[WEAPONS_COUNT+1];
+	char *name;
+} flib_weaponset;
+
+typedef struct {
+	int weaponsetCount;
+	flib_weaponset **weaponsets;
+} flib_weaponsetlist;
+
+/**
+ * Returns a new weapon set, or NULL on error.
+ * name must not be NULL.
+ *
+ * The new weapon set is pre-filled with default
+ * settings (see hwconsts.h)
+ */
+flib_weaponset *flib_weaponset_create(const char *name);
+
+/**
+ * Free the memory used by this weaponset
+ */
+void flib_weaponset_destroy(flib_weaponset *weaponset);
+
+flib_weaponset *flib_weaponset_copy(const flib_weaponset *weaponset);
+
+/**
+ * Create a weaponset from an ammostring. This format is used both in the ini files
+ * and in the net protocol.
+ */
+flib_weaponset *flib_weaponset_from_ammostring(const char *name, const char *ammostring);
+
+/**
+ * Load a list of weaponsets from the ini file.
+ * Returns NULL on error.
+ */
+flib_weaponsetlist *flib_weaponsetlist_from_ini(const char *filename);
+
+/**
+ * Store the list of weaponsets to an ini file.
+ * Returns NULL on error.
+ */
+int flib_weaponsetlist_to_ini(const char *filename, const flib_weaponsetlist *weaponsets);
+
+/**
+ * Create an empty weaponset list. Returns NULL on error.
+ */
+flib_weaponsetlist *flib_weaponsetlist_create();
+
+/**
+ * Release all memory associated with the weaponsetlist and release all contained weaponsets
+ */
+void flib_weaponsetlist_destroy(flib_weaponsetlist *list);
+
+/**
+ * Insert a new weaponset into the list at position pos, moving all higher weaponsets to make place.
+ * pos must be at least 0 (insert at the start) and at most list->weaponsetCount (insert at the end).
+ * Ownership of the weaponset is transferred to the list.
+ * Returns 0 on success.
+ */
+int flib_weaponsetlist_insert(flib_weaponsetlist *list, flib_weaponset *weaponset, int pos);
+
+/**
+ * Delete a weaponset from the list at position pos, moving down all higher weaponsets.
+ * The weaponset is destroyed.
+ * Returns 0 on success.
+ */
+int flib_weaponsetlist_delete(flib_weaponsetlist *list, int pos);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netbase.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,259 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "netbase.h"
+#include "../util/buffer.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../socket.h"
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define NET_READBUFFER_LIMIT (1024*1024)
+
+struct _flib_netbase {
+	flib_vector *readBuffer;
+	flib_tcpsocket *sock;
+};
+
+flib_netbase *flib_netbase_create(const char *server, uint16_t port) {
+	if(log_badargs_if2(server==NULL, port==0)) {
+		return NULL;
+	}
+
+	flib_netbase *result = NULL;
+	flib_netbase *newNet =  flib_calloc(1, sizeof(flib_netbase));
+
+	if(newNet) {
+		newNet->readBuffer = flib_vector_create();
+		newNet->sock = flib_socket_connect(server, port);
+		if(newNet->readBuffer && newNet->sock) {
+			flib_log_i("Connected to server %s:%u", server, (unsigned)port);
+			result = newNet;
+			newNet = NULL;
+		}
+	}
+	flib_netbase_destroy(newNet);
+
+	return result;
+}
+
+void flib_netbase_destroy(flib_netbase *net) {
+	if(net) {
+		flib_socket_close(net->sock);
+		flib_vector_destroy(net->readBuffer);
+		free(net);
+	}
+}
+
+bool flib_netbase_connected(flib_netbase *net) {
+	if(!log_badargs_if(net==NULL) && net->sock) {
+		return true;
+	}
+	return false;
+}
+
+/**
+ * Parses and returns a message, and removes it from the vector.
+ */
+static flib_netmsg *parseMessage(flib_vector *vec) {
+	const uint8_t *partStart = flib_vector_data(vec);
+	const uint8_t *end = partStart+flib_vector_size(vec);
+	flib_netmsg *result = flib_netmsg_create();
+	if(!result) {
+		return NULL;
+	}
+
+	while(1) {
+		const uint8_t *partEnd = memchr(partStart, '\n', end-partStart);
+		if(!partEnd) {
+			// message incomplete
+			flib_netmsg_destroy(result);
+			return NULL;
+		} else if(partEnd-partStart == 0) {
+			// Zero-length part, message end marker. Remove the message from the vector.
+			uint8_t *vectorStart = flib_vector_data(vec);
+			size_t msgLen = partEnd+1-vectorStart;
+			memmove(vectorStart, partEnd+1, flib_vector_size(vec)-msgLen);
+			flib_vector_resize(vec, flib_vector_size(vec)-msgLen);
+			return result;
+		} else {
+			if(flib_netmsg_append_part(result, partStart, partEnd-partStart)) {
+				flib_netmsg_destroy(result);
+				return NULL;
+			}
+			partStart = partEnd+1; // Skip the '\n'
+		}
+	}
+	return NULL; // Never reached
+}
+
+/**
+ * Receive some bytes and add them to the buffer.
+ * Returns the number of bytes received.
+ * Automatically closes the socket if an error occurs
+ * and sets sock=NULL.
+ */
+static int receiveToBuffer(flib_netbase *net) {
+	uint8_t buffer[256];
+	if(!net->sock) {
+		return 0;
+	} else if(flib_vector_size(net->readBuffer) > NET_READBUFFER_LIMIT) {
+		flib_log_e("Net connection closed: Net message too big");
+		flib_socket_close(net->sock);
+		net->sock = NULL;
+		return 0;
+	} else {
+		int size = flib_socket_nbrecv(net->sock, buffer, sizeof(buffer));
+		if(size>=0 && !flib_vector_append(net->readBuffer, buffer, size)) {
+			return size;
+		} else {
+			flib_socket_close(net->sock);
+			net->sock = NULL;
+			return 0;
+		}
+	}
+}
+
+flib_netmsg *flib_netbase_recv_message(flib_netbase *net) {
+	if(log_badargs_if(net==NULL)) {
+		return NULL;
+	}
+
+	flib_netmsg *msg;
+	while(!(msg=parseMessage(net->readBuffer))
+			&& receiveToBuffer(net)) {}
+
+	if(msg) {
+		return msg;
+	} else if(!net->sock && flib_vector_size(net->readBuffer)>0) {
+		// Connection is down and we didn't get a complete message, just flush the rest.
+		flib_vector_resize(net->readBuffer, 0);
+	}
+	return NULL;
+}
+
+static void logSentMsg(const uint8_t *data, size_t len) {
+	if(flib_log_isActive(FLIB_LOGLEVEL_DEBUG)) {
+		flib_log_d("[NET OUT][%03u]%*.*s",(unsigned)len, (unsigned)len, (unsigned)len, data);
+	}
+}
+
+int flib_netbase_send_raw(flib_netbase *net, const void *data, size_t len) {
+	if(log_badargs_if2(net==NULL, data==NULL && len>0)) {
+		return -1;
+	}
+	if(!net->sock) {
+		flib_log_w("flib_netbase_send_raw: Not connected.");
+		return -1;
+	}
+
+	if(flib_socket_send(net->sock, data, len) == len) {
+		logSentMsg(data, len);
+		return 0;
+	} else {
+		flib_log_w("Failed or incomplete write: net connection lost.");
+		flib_socket_close(net->sock);
+		net->sock = NULL;
+		return -1;
+	}
+}
+
+int flib_netbase_send_message(flib_netbase *net, const flib_netmsg *msg) {
+	if(log_badargs_if2(net==NULL, msg==NULL)) {
+		return -1;
+	}
+
+	size_t totalSize = 0;
+	for(int i=0; i<msg->partCount; i++) {
+		totalSize += strlen(msg->parts[i]) + 1;
+	}
+	totalSize++; // Last part ends in two '\n' instead of one
+
+	uint8_t *buffer = flib_malloc(totalSize);
+	if(!buffer) {
+		return -1;
+	}
+	size_t pos = 0;
+	for(int i=0; i<msg->partCount; i++) {
+		size_t partsize = strlen(msg->parts[i]);
+		memcpy(buffer+pos, msg->parts[i], partsize);
+		pos += partsize;
+		buffer[pos++] = '\n';
+	}
+	buffer[pos++] = '\n';
+	return flib_netbase_send_raw(net, buffer, pos);
+}
+
+int flib_netbase_sendf(flib_netbase *net, const char *format, ...) {
+	int result = -1;
+	if(!log_badargs_if2(net==NULL, format==NULL)) {
+		va_list argp;
+		va_start(argp, format);
+		char *buffer = flib_vasprintf(format, argp);
+		if(buffer) {
+			result = flib_netbase_send_raw(net, buffer, strlen(buffer));
+		}
+		free(buffer);
+		va_end(argp);
+	}
+	return result;
+}
+
+flib_netmsg *flib_netmsg_create() {
+	flib_netmsg *result = flib_calloc(1, sizeof(flib_netmsg));
+	if(result) {
+		result->partCount = 0;
+		result->parts = NULL;
+		return result;
+	} else {
+		return NULL;
+	}
+}
+
+void flib_netmsg_destroy(flib_netmsg *msg) {
+	if(msg) {
+		for(int i=0; i<msg->partCount; i++) {
+			free(msg->parts[i]);
+		}
+		free(msg->parts);
+		free(msg);
+	}
+}
+
+int flib_netmsg_append_part(flib_netmsg *msg, const void *part, size_t partlen) {
+	int result = -1;
+	if(!log_badargs_if2(msg==NULL, part==NULL && partlen>0)) {
+		char **newParts = realloc(msg->parts, (msg->partCount+1)*sizeof(*msg->parts));
+		if(newParts) {
+			msg->parts = newParts;
+			msg->parts[msg->partCount] = flib_malloc(partlen+1);
+			if(msg->parts[msg->partCount]) {
+				memcpy(msg->parts[msg->partCount], part, partlen);
+				msg->parts[msg->partCount][partlen] = 0;
+				msg->partCount++;
+				result = 0;
+			}
+		}
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netbase.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,91 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * Low-level protocol support for the network connection
+ */
+
+#ifndef NETBASE_H_
+#define NETBASE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+struct _flib_netbase;
+typedef struct _flib_netbase flib_netbase;
+
+typedef struct {
+	int partCount;
+	char **parts;
+} flib_netmsg;
+
+/**
+ * Start a connection to the specified Hedgewars server.
+ *
+ * Returns NULL on error. Destroy the created object with flib_netconn_destroy.
+ */
+flib_netbase *flib_netbase_create(const char *server, uint16_t port);
+
+/**
+ * Free resources and close sockets.
+ */
+void flib_netbase_destroy(flib_netbase *net);
+
+/**
+ * Determine the current connection state. Starts out true, and turns to
+ * false when we are disconnected from the server.
+ */
+bool flib_netbase_connected(flib_netbase *net);
+
+/**
+ * Receive a new message and return it as a flib_netmsg. The netmsg has to be
+ * destroyed with flib_netmsg_destroy after use.
+ * Returns NULL if no message is available.
+ *
+ * Note: When a connection is closed, you probably want to call this function until
+ * no further message is returned, to ensure you see all messages that were sent
+ * before the connection closed.
+ */
+flib_netmsg *flib_netbase_recv_message(flib_netbase *net);
+
+int flib_netbase_send_raw(flib_netbase *net, const void *data, size_t len);
+
+/**
+ * Write a single message to the server. This call blocks until the
+ * message is completely written or the connection is closed or an error occurs.
+ *
+ * Returns a negative value on failure.
+ */
+int flib_netbase_send_message(flib_netbase *net, const flib_netmsg *msg);
+
+/**
+ * Send a message printf-style.
+ *
+ * flib_netbase_sendf(net, "%s\n\n", "TOGGLE_READY");
+ * flib_netbase_sendf(net, "%s\n%s\n%i\n\n", "CFG", "MAPGEN", MAPGEN_MAZE);
+ */
+int flib_netbase_sendf(flib_netbase *net, const char *format, ...);
+
+flib_netmsg *flib_netmsg_create();
+void flib_netmsg_destroy(flib_netmsg *msg);
+int flib_netmsg_append_part(flib_netmsg *msg, const void *param, size_t len);
+
+#endif /* NETBASE_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netconn.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,626 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2006-2008 Igor Ulyanov <iulyanov@gmail.com>
+ * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ * Copyright (c) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include "netconn_internal.h"
+#include "netprotocol.h"
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../md5/md5.h"
+#include "../base64/base64.h"
+#include "../model/mapcfg.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+flib_netconn *flib_netconn_create(const char *playerName, const char *dataDirPath, const char *host, int port) {
+	flib_netconn *result = NULL;
+	if(!log_badargs_if4(playerName==NULL, host==NULL, port<1, port>65535)) {
+		flib_netconn *newConn = flib_calloc(1, sizeof(flib_netconn));
+		if(newConn) {
+			newConn->netBase = flib_netbase_create(host, port);
+			newConn->playerName = flib_strdupnull(playerName);
+			newConn->dataDirPath = flib_strdupnull(dataDirPath);
+
+			newConn->netconnState = NETCONN_STATE_CONNECTING;
+			newConn->isAdmin = false;
+
+			newConn->isChief = false;
+			newConn->map = flib_map_create_named("", "NoSuchMap");
+			newConn->pendingTeamlist.teamCount = 0;
+			newConn->pendingTeamlist.teams = NULL;
+			newConn->teamlist.teamCount = 0;
+			newConn->teamlist.teams = NULL;
+			newConn->scheme = NULL;
+			newConn->style = NULL;
+			newConn->weaponset = NULL;
+
+			newConn->running = false;
+			newConn->destroyRequested = false;
+			netconn_clearCallbacks(newConn);
+			if(newConn->netBase && newConn->playerName && newConn->dataDirPath && newConn->map) {
+				result = newConn;
+				newConn = NULL;
+			}
+		}
+		flib_netconn_destroy(newConn);
+	}
+	return result;
+}
+
+void flib_netconn_destroy(flib_netconn *conn) {
+	if(conn) {
+		if(conn->running) {
+			/*
+			 * The function was called from a callback, so the tick function is still running
+			 * and we delay the actual destruction. We ensure no further callbacks will be
+			 * sent to prevent surprises.
+			 */
+			netconn_clearCallbacks(conn);
+			conn->destroyRequested = true;
+		} else {
+			flib_netbase_destroy(conn->netBase);
+			free(conn->playerName);
+			free(conn->dataDirPath);
+
+			flib_map_destroy(conn->map);
+			flib_teamlist_clear(&conn->pendingTeamlist);
+			flib_teamlist_clear(&conn->teamlist);
+			flib_scheme_destroy(conn->scheme);
+			free(conn->style);
+			flib_weaponset_destroy(conn->weaponset);
+
+			free(conn);
+		}
+	}
+}
+
+bool flib_netconn_is_chief(flib_netconn *conn) {
+	if(!log_badargs_if(conn==NULL) && conn->netconnState==NETCONN_STATE_ROOM) {
+		return conn->isChief;
+	}
+	return false;
+}
+
+const char *flib_netconn_get_playername(flib_netconn *conn) {
+	if(!log_badargs_if(conn==NULL)) {
+		return conn->playerName;
+	}
+	return NULL;
+}
+
+void netconn_leaveRoom(flib_netconn *conn) {
+	conn->netconnState = NETCONN_STATE_LOBBY;
+	conn->isChief = false;
+	flib_map_destroy(conn->map);
+	conn->map = flib_map_create_named("", "NoSuchMap");
+	flib_teamlist_clear(&conn->pendingTeamlist);
+	flib_teamlist_clear(&conn->teamlist);
+	flib_scheme_destroy(conn->scheme);
+	conn->scheme = NULL;
+	free(conn->style);
+	conn->style = NULL;
+	flib_weaponset_destroy(conn->weaponset);
+	conn->weaponset = NULL;
+}
+
+void netconn_setMap(flib_netconn *conn, const flib_map *map) {
+	flib_map *copy = flib_map_copy(map);
+	if(copy) {
+		flib_map_destroy(conn->map);
+		conn->map = copy;
+	}
+}
+
+void netconn_setWeaponset(flib_netconn *conn, const flib_weaponset *weaponset) {
+	flib_weaponset *copy = flib_weaponset_copy(weaponset);
+	if(copy) {
+		flib_weaponset_destroy(conn->weaponset);
+		conn->weaponset = copy;
+	}
+}
+
+void netconn_setScript(flib_netconn *conn, const char *script) {
+	char *copy = flib_strdupnull(script);
+	if(copy) {
+		free(conn->style);
+		conn->style = copy;
+	}
+}
+
+void netconn_setScheme(flib_netconn *conn, const flib_scheme *scheme) {
+	flib_scheme *copy = flib_scheme_copy(scheme);
+	if(copy) {
+		flib_scheme_destroy(conn->scheme);
+		conn->scheme = copy;
+	}
+}
+
+flib_gamesetup *flib_netconn_create_gamesetup(flib_netconn *conn) {
+	flib_gamesetup *result = NULL;
+	if(!log_badargs_if(conn==NULL)) {
+		if(conn->teamlist.teamCount==0 || !conn->scheme || !conn->weaponset) {
+			flib_log_e("Incomplete room state");
+		} else {
+			flib_gamesetup stackSetup = {0};
+			stackSetup.gamescheme = conn->scheme;
+			stackSetup.map = conn->map;
+			stackSetup.style = conn->style;
+			stackSetup.teamlist = &conn->teamlist;
+			result = flib_gamesetup_copy(&stackSetup);
+			if(result) {
+				bool error = false;
+				for(int i=0; i<result->teamlist->teamCount; i++) {
+					if(flib_team_set_weaponset(result->teamlist->teams[i], conn->weaponset)) {
+						error = true;
+					}
+					flib_team_set_health(result->teamlist->teams[i], flib_scheme_get_setting(conn->scheme, "health", 100));
+				}
+				if(result->map->mapgen == MAPGEN_NAMED && result->map->name) {
+					flib_mapcfg mapcfg;
+					if(!flib_mapcfg_read(conn->dataDirPath, result->map->name, &mapcfg)) {
+						free(result->map->theme);
+						result->map->theme = flib_strdupnull(mapcfg.theme);
+						if(!result->map->theme) {
+							error = true;
+						}
+					} else {
+						flib_log_e("Unable to read map config for map %s", result->map->name);
+					}
+				}
+				if(error) {
+					flib_gamesetup_destroy(result);
+					result = NULL;
+				}
+			}
+		}
+	}
+	return result;
+}
+
+static void flib_netconn_wrappedtick(flib_netconn *conn) {
+	flib_netmsg *netmsg;
+	flib_netbase *net = conn->netBase;
+	bool exit = false;
+
+	while(!exit && !conn->destroyRequested && (netmsg=flib_netbase_recv_message(conn->netBase))) {
+		if(netmsg->partCount==0) {
+			flib_log_w("Empty server message");
+			continue;
+		}
+
+		if(flib_log_isActive(FLIB_LOGLEVEL_DEBUG)) {
+			char *buf = flib_join(netmsg->parts, netmsg->partCount, "|");
+			if(buf) {
+				flib_log_d("[Net In]%s", buf);
+			}
+			free(buf);
+		}
+
+		const char *cmd = netmsg->parts[0];
+
+	    if (!strcmp(cmd, "NICK") && netmsg->partCount>=2) {
+	    	if(netmsg->partCount<2) {
+	    		flib_log_w("Net: Malformed NICK message");
+	    	} else {
+	    		char *nick = flib_strdupnull(netmsg->parts[1]);
+	    		if(nick) {
+					free(conn->playerName);
+					conn->playerName = nick;
+	    		} else {
+					conn->netconnState = NETCONN_STATE_DISCONNECTED;
+					conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_INTERNAL_ERROR, "Out of memory");
+					exit = true;
+				}
+	    	}
+	    } else if (!strcmp(cmd, "PROTO")) {
+	        // The server just echoes this back apparently
+		} else if (!strcmp(cmd, "ERROR")) {
+			if (netmsg->partCount >= 2) {
+				conn->onMessageCb(conn->onMessageCtx, NETCONN_MSG_TYPE_ERROR, netmsg->parts[1]);
+			} else {
+				conn->onMessageCb(conn->onMessageCtx, NETCONN_MSG_TYPE_ERROR, "Unknown Error");
+			}
+		} else if(!strcmp(cmd, "WARNING")) {
+			if (netmsg->partCount >= 2) {
+				conn->onMessageCb(conn->onMessageCtx, NETCONN_MSG_TYPE_WARNING, netmsg->parts[1]);
+			} else {
+				conn->onMessageCb(conn->onMessageCtx, NETCONN_MSG_TYPE_WARNING, "Unknown Warning");
+			}
+	    } else if(!strcmp(cmd, "CONNECTED")) {
+			if(netmsg->partCount<3 || atol(netmsg->parts[2])<MIN_SERVER_VERSION) {
+				flib_log_w("Net: Server too old");
+				flib_netbase_sendf(net, "%s\n%s\n\n", "QUIT", "Server too old");
+				conn->netconnState = NETCONN_STATE_DISCONNECTED;
+				conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_SERVER_TOO_OLD, "Server too old");
+				exit = true;
+			} else {
+				flib_netbase_sendf(net, "%s\n%s\n\n", "NICK", conn->playerName);
+				flib_netbase_sendf(net, "%s\n%i\n\n", "PROTO", (int)PROTOCOL_VERSION);
+			}
+		} else if(!strcmp(cmd, "PING")) {
+	        if (netmsg->partCount > 1) {
+	        	flib_netbase_sendf(net, "%s\n%s\n\n", "PONG", netmsg->parts[1]);
+	        } else {
+	        	flib_netbase_sendf(net, "%s\n\n", "PONG");
+	        }
+	    } else if(!strcmp(cmd, "ROOMS")) {
+	        if(netmsg->partCount % 8 != 1) {
+	        	flib_log_w("Net: Malformed ROOMS message");
+	        } else {
+	        	int roomCount = netmsg->partCount/8;
+	        	flib_room **rooms = flib_room_array_from_netmsg(netmsg->parts+1, roomCount);
+	        	if(rooms) {
+	        		conn->onRoomlistCb(conn->onRoomlistCtx, (const flib_room**)rooms, roomCount);
+	        		for(int i=0; i<roomCount; i++) {
+	        			flib_room_destroy(rooms[i]);
+	        		}
+	        		free(rooms);
+	        	}
+	        }
+	    } else if (!strcmp(cmd, "SERVER_MESSAGE")) {
+	        if(netmsg->partCount < 2) {
+	        	flib_log_w("Net: Empty SERVERMESSAGE message");
+	        } else {
+	        	conn->onMessageCb(conn->onMessageCtx, NETCONN_MSG_TYPE_SERVERMESSAGE, netmsg->parts[1]);
+	        }
+	    } else if (!strcmp(cmd, "CHAT")) {
+	        if(netmsg->partCount < 3) {
+	        	flib_log_w("Net: Empty CHAT message");
+	        } else {
+	        	conn->onChatCb(conn->onChatCtx, netmsg->parts[1], netmsg->parts[2]);
+	        }
+	    } else if (!strcmp(cmd, "INFO")) {
+	        if(netmsg->partCount < 5) {
+	        	flib_log_w("Net: Malformed INFO message");
+	        } else {
+	        	char *joined = flib_join(netmsg->parts+1, netmsg->partCount-1, "\n");
+	        	if(joined) {
+	        		conn->onMessageCb(conn->onMessageCtx, NETCONN_MSG_TYPE_PLAYERINFO, joined);
+	        	}
+	        	free(joined);
+	        }
+	    } else if(!strcmp(cmd, "SERVER_VARS")) {
+	    	for(int offset=1; offset+2<netmsg->partCount; offset+=2) {
+	    		conn->onServerVarCb(conn->onServerVarCtx, netmsg->parts[offset], netmsg->parts[offset+1]);
+	    	}
+	    } else if (!strcmp(cmd, "CLIENT_FLAGS")) {
+	        if(netmsg->partCount < 3 || strlen(netmsg->parts[1]) < 2) {
+	        	flib_log_w("Net: Malformed CLIENT_FLAGS message");
+	        } else {
+				const char *flags = netmsg->parts[1];
+				bool setFlag = flags[0] == '+';
+
+				for(int i=1; flags[i]; i++) {
+					switch(flags[i]) {
+					case 'r':
+						for(int j = 2; j < netmsg->partCount; ++j) {
+							conn->onReadyStateCb(conn->onReadyStateCtx, netmsg->parts[j], setFlag);
+						}
+						break;
+					default:
+						flib_log_w("Net: Unknown flag %c in CLIENT_FLAGS message", flags[i]);
+						break;
+					}
+				}
+	        }
+	    } else if (!strcmp(cmd, "ADD_TEAM")) {
+	        if(netmsg->partCount != 24 || conn->netconnState!=NETCONN_STATE_ROOM) {
+	            flib_log_w("Net: Bad ADD_TEAM message");
+	        } else {
+	        	flib_team *team = flib_team_from_netmsg(netmsg->parts+1);
+	        	if(!team || flib_teamlist_insert(&conn->teamlist, team, conn->teamlist.teamCount)) {
+					flib_team_destroy(team);
+					conn->netconnState = NETCONN_STATE_DISCONNECTED;
+					conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_INTERNAL_ERROR, "Internal error");
+					exit = true;
+	        	} else {
+	        		team->remoteDriven = true;
+	        		conn->onTeamAddCb(conn->onTeamAddCtx, team);
+	        	}
+	        }
+	    } else if (!strcmp(cmd, "REMOVE_TEAM")) {
+	        if(netmsg->partCount != 2 || conn->netconnState!=NETCONN_STATE_ROOM) {
+	            flib_log_w("Net: Bad REMOVETEAM message");
+	        } else {
+	        	flib_teamlist_delete(&conn->teamlist, netmsg->parts[1]);
+	        	conn->onTeamDeleteCb(conn->onTeamDeleteCtx, netmsg->parts[1]);
+	        }
+	    } else if(!strcmp(cmd, "ROOMABANDONED")) {
+	    	netconn_leaveRoom(conn);
+	        conn->onLeaveRoomCb(conn->onLeaveRoomCtx, NETCONN_ROOMLEAVE_ABANDONED, "Room destroyed");
+	    } else if(!strcmp(cmd, "KICKED")) {
+	    	netconn_leaveRoom(conn);
+	    	conn->onLeaveRoomCb(conn->onLeaveRoomCtx, NETCONN_ROOMLEAVE_KICKED, "You got kicked");
+	    } else if(!strcmp(cmd, "JOINED")) {
+	        if(netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad JOINED message");
+	        } else {
+				for(int i = 1; i < netmsg->partCount; ++i)
+				{
+					bool isMe = !strcmp(conn->playerName, netmsg->parts[i]);
+					if (isMe) {
+						conn->netconnState = NETCONN_STATE_ROOM;
+						conn->onEnterRoomCb(conn->onEnterRoomCtx, conn->isChief);
+					}
+
+					conn->onRoomJoinCb(conn->onRoomJoinCtx, netmsg->parts[i]);
+				}
+	        }
+	    } else if(!strcmp(cmd, "LOBBY:JOINED")) {
+	        if(netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad JOINED message");
+	        } else {
+				for(int i = 1; i < netmsg->partCount; ++i)
+				{
+					bool isMe = !strcmp(conn->playerName, netmsg->parts[i]);
+					if (isMe && conn->netconnState == NETCONN_STATE_CONNECTING) {
+						conn->onConnectedCb(conn->onConnectedCtx);
+						conn->netconnState = NETCONN_STATE_LOBBY;
+					}
+					conn->onLobbyJoinCb(conn->onLobbyJoinCtx, netmsg->parts[i]);
+				}
+	        }
+	    } else if(!strcmp(cmd, "LEFT")) {
+	        if(netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad LEFT message");
+	        } else {
+	        	conn->onRoomLeaveCb(conn->onRoomLeaveCtx, netmsg->parts[1], netmsg->partCount>2 ? netmsg->parts[2] : NULL);
+	        }
+	    } else if(!strcmp(cmd, "ROOM") && netmsg->partCount >= 2) {
+	    	const char *subcmd = netmsg->parts[1];
+	    	if(!strcmp(subcmd, "ADD") && netmsg->partCount == 10) {
+	    		flib_room *room = flib_room_from_netmsg(netmsg->parts+2);
+	    		if(room) {
+	    			conn->onRoomAddCb(conn->onRoomAddCtx, room);
+	    		}
+	    		flib_room_destroy(room);
+			} else if(!strcmp(subcmd, "UPD") && netmsg->partCount == 11) {
+				flib_room *room = flib_room_from_netmsg(netmsg->parts+3);
+				if(room) {
+	    			conn->onRoomUpdateCb(conn->onRoomUpdateCtx, netmsg->parts[2], room);
+	    		}
+				flib_room_destroy(room);
+			} else if(!strcmp(subcmd, "DEL") && netmsg->partCount == 3) {
+				conn->onRoomDeleteCb(conn->onRoomDeleteCtx, netmsg->parts[2]);
+			} else {
+				flib_log_w("Net: Unknown or malformed ROOM subcommand: %s", subcmd);
+			}
+	    } else if(!strcmp(cmd, "LOBBY:LEFT")) {
+	        if(netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad LOBBY:LEFT message");
+	        } else {
+	        	conn->onLobbyLeaveCb(conn->onLobbyLeaveCtx, netmsg->parts[1], netmsg->partCount>2 ? netmsg->parts[2] : NULL);
+	        }
+	    } else if (!strcmp(cmd, "RUN_GAME")) {
+	        conn->onRunGameCb(conn->onRunGameCtx);
+	    } else if (!strcmp(cmd, "ASKPASSWORD")) {
+	    	conn->onPasswordRequestCb(conn->onPasswordRequestCtx, conn->playerName);
+	    } else if (!strcmp(cmd, "NOTICE")) {
+	        if(netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad NOTICE message");
+	        } else {
+				errno = 0;
+				long n = strtol(netmsg->parts[1], NULL, 10);
+				if(errno) {
+					flib_log_w("Net: Bad NOTICE message");
+				} else if(n==0) {
+					conn->onNickTakenCb(conn->onNickTakenCtx, conn->playerName);
+				} else {
+					flib_log_w("Net: Unknown NOTICE message: %l", n);
+				}
+	        }
+	    } else if (!strcmp(cmd, "TEAM_ACCEPTED")) {
+	        if (netmsg->partCount != 2 || conn->netconnState!=NETCONN_STATE_ROOM) {
+	            flib_log_w("Net: Bad TEAM_ACCEPTED message");
+	        } else {
+	        	flib_team *team = flib_team_copy(flib_teamlist_find(&conn->pendingTeamlist, netmsg->parts[1]));
+	        	if(team) {
+	        		flib_teamlist_insert(&conn->teamlist, team, conn->teamlist.teamCount);
+	        		flib_teamlist_delete(&conn->pendingTeamlist, netmsg->parts[1]);
+	        	} else {
+	        		flib_log_e("Team accepted that was not requested: %s", netmsg->parts[1]);
+	        	}
+	        	conn->onTeamAcceptedCb(conn->onTeamAcceptedCtx, netmsg->parts[1]);
+	        }
+	    } else if (!strcmp(cmd, "CFG")) {
+	        if(netmsg->partCount < 3 || conn->netconnState!=NETCONN_STATE_ROOM) {
+	            flib_log_w("Net: Bad CFG message");
+	        } else {
+	        	const char *subcmd = netmsg->parts[1];
+				if(!strcmp(subcmd, "SCHEME") && netmsg->partCount == flib_meta.modCount + flib_meta.settingCount + 3) {
+					flib_scheme *cfg = flib_scheme_from_netmsg(netmsg->parts+2);
+					if(cfg) {
+						flib_scheme_destroy(conn->scheme);
+						conn->scheme = cfg;
+						conn->onCfgSchemeCb(conn->onCfgSchemeCtx, cfg);
+					} else {
+						flib_log_e("Error processing CFG SCHEME message");
+					}
+				} else if(!strcmp(subcmd, "FULLMAPCONFIG") && netmsg->partCount == 7) {
+					flib_map *map = flib_map_from_netmsg(netmsg->parts+2);
+					if(map) {
+						flib_map_destroy(conn->map);
+						conn->map = map;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_FULL);
+					} else {
+						flib_log_e("Error processing CFG FULLMAPCONFIG message");
+					}
+				} else if(!strcmp(subcmd, "MAP") && netmsg->partCount == 3) {
+					char *mapname = flib_strdupnull(netmsg->parts[2]);
+					if(mapname) {
+						free(conn->map->name);
+						conn->map->name = mapname;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_MAP);
+					} else {
+						flib_log_e("Error processing CFG MAP message");
+					}
+				} else if(!strcmp(subcmd, "THEME") && netmsg->partCount == 3) {
+					char *themename = flib_strdupnull(netmsg->parts[2]);
+					if(themename) {
+						free(conn->map->theme);
+						conn->map->theme = themename;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_THEME);
+					} else {
+						flib_log_e("Error processing CFG THEME message");
+					}
+				} else if(!strcmp(subcmd, "SEED") && netmsg->partCount == 3) {
+					char *seed = flib_strdupnull(netmsg->parts[2]);
+					if(seed) {
+						free(conn->map->seed);
+						conn->map->seed = seed;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_SEED);
+					} else {
+						flib_log_e("Error processing CFG SEED message");
+					}
+				} else if(!strcmp(subcmd, "TEMPLATE") && netmsg->partCount == 3) {
+					conn->map->templateFilter = atoi(netmsg->parts[2]);
+					conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_TEMPLATE);
+				} else if(!strcmp(subcmd, "MAPGEN") && netmsg->partCount == 3) {
+					conn->map->mapgen = atoi(netmsg->parts[2]);
+					conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_MAPGEN);
+				} else if(!strcmp(subcmd, "MAZE_SIZE") && netmsg->partCount == 3) {
+					conn->map->mazeSize = atoi(netmsg->parts[2]);
+					conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_MAZE_SIZE);
+				} else if(!strcmp(subcmd, "DRAWNMAP") && netmsg->partCount == 3) {
+					size_t drawnMapSize = 0;
+					uint8_t *drawnMapData = NULL;
+					if(!flib_drawnmapdata_from_netmsg(netmsg->parts[2], &drawnMapData, &drawnMapSize)) {
+						free(conn->map->drawData);
+						conn->map->drawData = drawnMapData;
+						conn->map->drawDataSize = drawnMapSize;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_DRAWNMAP);
+					} else {
+						flib_log_e("Error processing CFG DRAWNMAP message");
+					}
+				} else if(!strcmp(subcmd, "SCRIPT") && netmsg->partCount == 3) {
+					netconn_setScript(conn, netmsg->parts[2]);
+					conn->onScriptChangedCb(conn->onScriptChangedCtx, netmsg->parts[2]);
+				} else if(!strcmp(subcmd, "AMMO") && netmsg->partCount == 4) {
+					flib_weaponset *weapons = flib_weaponset_from_ammostring(netmsg->parts[2], netmsg->parts[3]);
+					if(weapons) {
+						flib_weaponset_destroy(conn->weaponset);
+						conn->weaponset = weapons;
+						conn->onWeaponsetChangedCb(conn->onWeaponsetChangedCtx, weapons);
+					} else {
+						flib_log_e("Error processing CFG AMMO message");
+					}
+				} else {
+					flib_log_w("Net: Unknown or malformed CFG subcommand: %s", subcmd);
+				}
+	        }
+	    } else if (!strcmp(cmd, "HH_NUM")) {
+	        if (netmsg->partCount != 3 || conn->netconnState!=NETCONN_STATE_ROOM) {
+	            flib_log_w("Net: Bad HH_NUM message");
+	        } else {
+	        	int hogs = atoi(netmsg->parts[2]);
+	        	if(hogs<=0 || hogs>HEDGEHOGS_PER_TEAM) {
+	        		flib_log_w("Net: Bad HH_NUM message: %s hogs", netmsg->parts[2]);
+	        	} else {
+	        		flib_team *team = flib_teamlist_find(&conn->teamlist, netmsg->parts[1]);
+	        		if(team) {
+	        			team->hogsInGame = hogs;
+	        		} else {
+	        			flib_log_e("HH_NUM message for unknown team %s", netmsg->parts[1]);
+	        		}
+	        		conn->onHogCountChangedCb(conn->onHogCountChangedCtx, netmsg->parts[1], hogs);
+	        	}
+	        }
+	    } else if (!strcmp(cmd, "TEAM_COLOR")) {
+	        if (netmsg->partCount != 3 || conn->netconnState!=NETCONN_STATE_ROOM) {
+	            flib_log_w("Net: Bad TEAM_COLOR message");
+	        } else {
+	        	long color;
+	        	if(sscanf(netmsg->parts[2], "%lu", &color) && color>=0 && color<flib_teamcolor_count) {
+	        		flib_team *team = flib_teamlist_find(&conn->teamlist, netmsg->parts[1]);
+	        		if(team) {
+	        			team->colorIndex = color;
+	        		} else {
+	        			flib_log_e("TEAM_COLOR message for unknown team %s", netmsg->parts[1]);
+	        		}
+	        		conn->onTeamColorChangedCb(conn->onTeamColorChangedCtx, netmsg->parts[1], color);
+	        	} else {
+	        		flib_log_w("Net: Bad TEAM_COLOR message: Color %s", netmsg->parts[2]);
+	        	}
+	        }
+	    } else if (!strcmp(cmd, "EM")) {
+	        if(netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad EM message");
+	        } else {
+	        	for(int i = 1; i < netmsg->partCount; ++i) {
+					char *out = NULL;
+					size_t outlen;
+					bool ok = base64_decode_alloc(netmsg->parts[i], strlen(netmsg->parts[i]), &out, &outlen);
+					if(ok && outlen) {
+						conn->onEngineMessageCb(conn->onEngineMessageCtx, (uint8_t*)out, outlen);
+					} else {
+						flib_log_e("Net: Malformed engine message: %s", netmsg->parts[i]);
+					}
+					free(out);
+	        	}
+	        }
+	    } else if (!strcmp(cmd, "BYE")) {
+	        if (netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad BYE message");
+	        } else {
+				conn->netconnState = NETCONN_STATE_DISCONNECTED;
+				if (!strcmp(netmsg->parts[1], "Authentication failed")) {
+					conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_AUTH_FAILED, netmsg->parts[1]);
+				} else {
+					conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_NORMAL, netmsg->parts[1]);
+				}
+				exit = true;
+	        }
+	    } else if (!strcmp(cmd, "ADMIN_ACCESS")) {
+	    	conn->onAdminAccessCb(conn->onAdminAccessCtx);
+	    	conn->isAdmin = true;
+	    } else if (!strcmp(cmd, "ROOM_CONTROL_ACCESS")) {
+	        if (netmsg->partCount < 2) {
+	            flib_log_w("Net: Bad ROOM_CONTROL_ACCESS message");
+	        } else {
+	        	conn->isChief = strcmp("0", netmsg->parts[1]);
+	        	conn->onRoomChiefStatusCb(conn->onRoomChiefStatusCtx, conn->isChief);
+	        }
+	    } else {
+	    	flib_log_w("Unknown server command: %s", cmd);
+	    }
+		flib_netmsg_destroy(netmsg);
+	}
+
+	if(!exit && !conn->destroyRequested && !flib_netbase_connected(net)) {
+		conn->netconnState = NETCONN_STATE_DISCONNECTED;
+		conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_CONNLOST, "Connection lost");
+	}
+}
+
+void flib_netconn_tick(flib_netconn *conn) {
+	if(!log_badargs_if(conn==NULL)
+			&& !log_w_if(conn->running, "Call to flib_netconn_tick from a callback")
+			&& !log_w_if(conn->netconnState == NETCONN_STATE_DISCONNECTED, "We are already done.")) {
+		conn->running = true;
+		flib_netconn_wrappedtick(conn);
+		conn->running = false;
+
+		if(conn->destroyRequested) {
+			flib_netconn_destroy(conn);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netconn.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,557 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * This file contains functions for communicating with a Hedgewars server to chat, prepare
+ * and play rounds of Hedgewars.
+ *
+ * To use this, first create a netconn object by calling flib_netconn_create. This will
+ * start the connection to the game server (which might fail right away, the function
+ * returns null then). You should also register your callback functions right at the start
+ * to ensure you don't miss any callbacks.
+ *
+ * In order to allow the netconn to run, you should regularly call flib_netconn_tick(), which
+ * performs network I/O and calls your callbacks on interesting events.
+ *
+ * When the connection is closed, you will receive the onDisconnect callback. This is the
+ * signal to destroy the netconn and stop calling tick().
+ *
+ * The connection process lasts from the time you create the netconn until you receive the
+ * onConnected callback (or onDisconnected in case something goes wrong). During that time,
+ * you might receive the onNickTaken and onPasswordRequest callbacks; see their description
+ * for more information on how to handle them. You could also receive other callbacks during
+ * connecting (e.g. about the room list), but it should be safe to ignore them.
+ *
+ * Once you are connected, you are in the lobby, and you can enter rooms and leave them again.
+ * The room and lobby states have different protocols, so many commands only work in either
+ * one or the other. If you are in a room you might also be in a game, but that does not
+ * change the protocol. The functions below are grouped by the states in which they make
+ * sense, or (for the callbacks) the states in which you would typically receive them.
+ *
+ * The state changes from lobby to room when the server tells you that you just entered one,
+ * which will also trigger the onEnterRoom callback. This usually happens in reply to either
+ * a joinRoom, createRoom or playerFollow command.
+ *
+ * The state changes back to lobby when the room is dissolved, when you are kicked from the
+ * room, or when you actively leave the room using flib_netconn_send_leaveRoom. The first
+ * two events will trigger the onLeaveRoom callback.
+ */
+
+#ifndef NETCONN_H_
+#define NETCONN_H_
+
+#include "../model/gamesetup.h"
+#include "../model/scheme.h"
+#include "../model/room.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#define NETCONN_STATE_CONNECTING 0
+#define NETCONN_STATE_LOBBY 1
+#define NETCONN_STATE_ROOM 2
+#define NETCONN_STATE_DISCONNECTED 10
+
+#define NETCONN_DISCONNECT_NORMAL 0				// The connection was closed normally
+#define NETCONN_DISCONNECT_SERVER_TOO_OLD 1		// The server has a lower protocol version than we do
+#define NETCONN_DISCONNECT_AUTH_FAILED 2		// You sent a password with flib_netconn_send_password that was not accepted
+#define NETCONN_DISCONNECT_CONNLOST 3			// The network connection was lost
+#define NETCONN_DISCONNECT_INTERNAL_ERROR 100	// Something went wrong in frontlib itself
+
+#define NETCONN_ROOMLEAVE_ABANDONED 0			// The room was closed because the chief left
+#define NETCONN_ROOMLEAVE_KICKED 1				// You have been kicked from the room
+
+#define NETCONN_MSG_TYPE_PLAYERINFO 0			// A response to flib_netconn_send_playerInfo
+#define NETCONN_MSG_TYPE_SERVERMESSAGE 1		// The welcome message when connecting to the lobby
+#define NETCONN_MSG_TYPE_WARNING 2				// A general warning message
+#define NETCONN_MSG_TYPE_ERROR 3				// A general error message
+
+#define NETCONN_MAPCHANGE_FULL 0
+#define NETCONN_MAPCHANGE_MAP 1
+#define NETCONN_MAPCHANGE_MAPGEN 2
+#define NETCONN_MAPCHANGE_DRAWNMAP 3
+#define NETCONN_MAPCHANGE_MAZE_SIZE 4
+#define NETCONN_MAPCHANGE_TEMPLATE 5
+#define NETCONN_MAPCHANGE_THEME 6
+#define NETCONN_MAPCHANGE_SEED 7
+
+// TODO: Order these functions, and match the order in netconn.c
+typedef struct _flib_netconn flib_netconn;
+
+/**
+ * Create a new netplay connection with these parameters.
+ * The path to the data directory must end with a path delimiter (e.g. C:\Games\Hedgewars\Data\)
+ */
+flib_netconn *flib_netconn_create(const char *playerName, const char *dataDirPath, const char *host, int port);
+void flib_netconn_destroy(flib_netconn *conn);
+
+/**
+ * Perform I/O operations and call callbacks if something interesting happens.
+ * Should be called regularly.
+ */
+void flib_netconn_tick(flib_netconn *conn);
+
+/**
+ * Are you currently the owner of this room? The return value only makes sense in
+ * NETCONN_STATE_ROOM and NETCONN_STATE_INGAME states.
+ */
+bool flib_netconn_is_chief(flib_netconn *conn);
+
+/**
+ * Returns the playername. This is *probably* the one provided on creation, but
+ * if that name was already taken, a different one could have been set by the
+ * onNickTaken callback or its default implementation.
+ */
+const char *flib_netconn_get_playername(flib_netconn *conn);
+
+/**
+ * Generate a game setup from the current room state.
+ * Returns NULL if the room state does not contain enough information
+ * for a complete game setup, or if an error occurs.
+ *
+ * The new gamesetup must be destroyed with flib_gamesetup_destroy().
+ */
+flib_gamesetup *flib_netconn_create_gamesetup(flib_netconn *conn);
+
+/**
+ * quitmsg may be null
+ */
+int flib_netconn_send_quit(flib_netconn *conn, const char *quitmsg);
+int flib_netconn_send_chat(flib_netconn *conn, const char *chat);
+
+/**
+ * Send a teamchat message, forwarded from the engine. Only makes sense ingame.
+ * The server does not send a reply. In contrast to a Chat message, the server
+ * automatically converts this into an engine message and passes it on to the other
+ * clients.
+ */
+int flib_netconn_send_teamchat(flib_netconn *conn, const char *msg);
+
+/**
+ * Send the password in reply to a password request.
+ * If the server does not accept the password, you will be disconnected (NETCONN_DISCONNECT_AUTH_FAILED)
+ */
+int flib_netconn_send_password(flib_netconn *conn, const char *passwd);
+
+/**
+ * Request a different nickname.
+ * This function only makes sense in reaction to an onNickTaken callback, because the netconn automatically
+ * requests the nickname you provide on creation, and once the server accepts the nickname it can no longer
+ * be changed.
+ */
+int flib_netconn_send_nick(flib_netconn *conn, const char *nick);
+
+/**
+ * Request an update of the room list. Only makes sense when in lobby state.
+ * If the action succeeds, you will receive an onRoomlist callback containing the current room data.
+ */
+int flib_netconn_send_request_roomlist(flib_netconn *conn);
+
+/**
+ * Join a room as guest (not chief). Only makes sense when in lobby state. If the action succeeds, you will
+ * receive an onEnterRoom callback with chief=false.
+ */
+int flib_netconn_send_joinRoom(flib_netconn *conn, const char *room);
+
+/**
+ * Create and join a new room. Only makes sense when in lobby state. If the action succeeds, you will
+ * receive an onEnterRoom callback with chief=true.
+ */
+int flib_netconn_send_createRoom(flib_netconn *conn, const char *room);
+
+/**
+ * Rename the current room. Only makes sense in room state and if you are chief. If the action succeeds, you will
+ * receive an onRoomUpdate callback containing the change.
+ */
+int flib_netconn_send_renameRoom(flib_netconn *conn, const char *roomName);
+
+/**
+ * Leave the room for the lobby. Only makes sense in room state. msg can be NULL if you don't want to
+ * send a message. The server always accepts a part message, so once you send it off, you can just
+ * assume that you are back in the lobby.
+ */
+int flib_netconn_send_leaveRoom(flib_netconn *conn, const char *msg);
+
+/**
+ * Change your "ready" status in the room. Only makes sense when in room state. If the action succeeds, you will
+ * receive an onReadyState callback containing the change.
+ */
+int flib_netconn_send_toggleReady(flib_netconn *conn);
+
+/**
+ * Add a team to the current room. Apart from the "fixed" team information, this also includes
+ * the color, but not the number of hogs. Only makes sense when in room state. If the action
+ * succeeds, you will receive an onTeamAccepted callback with the name of the team.
+ */
+int flib_netconn_send_addTeam(flib_netconn *conn, const flib_team *team);
+
+/**
+ * Remove the team with the name teamname. Only makes sense when in room state.
+ * The server does not send a reply on success.
+ */
+int flib_netconn_send_removeTeam(flib_netconn *conn, const char *teamname);
+
+/**
+ * Send an engine message. Only makes sense when ingame. In a networked game, you have to
+ * pass all the engine messages from the engine here, and they will be spread to all other
+ * clients in the game to keep the game in sync.
+ */
+int flib_netconn_send_engineMessage(flib_netconn *conn, const uint8_t *message, size_t size);
+
+/**
+ * Set the number of hogs for a team. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_teamHogCount(flib_netconn *conn, const char *teamname, int hogcount);
+
+/**
+ * Set the teamcolor of a team. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_teamColor(flib_netconn *conn, const char *teamname, int colorIndex);
+
+/**
+ * Set the weaponset for the room. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_weaponset(flib_netconn *conn, const flib_weaponset *weaponset);
+
+/**
+ * Set the map for the room. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_map(flib_netconn *conn, const flib_map *map);
+
+/**
+ * Set the mapname. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapName(flib_netconn *conn, const char *mapName);
+
+/**
+ * Set the map generator. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapGen(flib_netconn *conn, int mapGen);
+
+/**
+ * Set the map template for regular maps. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapTemplate(flib_netconn *conn, int templateFilter);
+
+/**
+ * Set the maze template (maze size) for mazes. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapMazeSize(flib_netconn *conn, int mazeSize);
+
+/**
+ * Set the seed for the map. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapSeed(flib_netconn *conn, const char *seed);
+
+/**
+ * Set the theme for the map. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapTheme(flib_netconn *conn, const char *theme);
+
+/**
+ * Set the draw data for the drawn map. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_mapDrawdata(flib_netconn *conn, const uint8_t *drawData, size_t size);
+
+/**
+ * Set the script (game style). Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_script(flib_netconn *conn, const char *scriptName);
+
+/**
+ * Set the scheme. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_scheme(flib_netconn *conn, const flib_scheme *scheme);
+
+/**
+ * Inform the server that the round has ended. Call this when the engine
+ * has disconnected, passing 1 if the round ended normally, 0 otherwise.
+ */
+int flib_netconn_send_roundfinished(flib_netconn *conn, bool withoutError);
+
+/**
+ * Ban a player. You need to be in the lobby and a server admin for this to work.
+ */
+int flib_netconn_send_ban(flib_netconn *conn, const char *playerName);
+
+/**
+ * Kick a player. This has different meanings in the lobby and in a room;
+ * In the lobby, it will kick the player from the server, and you need to be a server admin to do it.
+ * In a room, it will kick the player from the room, and you need to be room chief.
+ */
+int flib_netconn_send_kick(flib_netconn *conn, const char *playerName);
+
+/**
+ * Request information about a player. If the action succeeds, you will
+ * receive an onMessage callback with NETCONN_MSG_TYPE_PLAYERINFO containing
+ * the requested information.
+ */
+int flib_netconn_send_playerInfo(flib_netconn *conn, const char *playerName);
+
+/**
+ * Follow a player. Only valid in the lobby. If the player is in a room (or in a game),
+ * this command is analogous to calling flib_netconn_send_joinRoom with that room.
+ */
+int flib_netconn_send_playerFollow(flib_netconn *conn, const char *playerName);
+
+/**
+ * Signal that you want to start the game. Only makes sense in room state and if you are chief.
+ * The server will check whether all players are ready and whether it believes the setup makes
+ * sense (e.g. more than one clan). If the server is satisfied, you will receive an onRunGame
+ * callback (all other clients in the room are notified the same way). Otherwise the server
+ * might answer with a warning, or might not answer at all.
+ */
+int flib_netconn_send_startGame(flib_netconn *conn);
+
+/**
+ * Allow/forbid players to join the room. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_toggleRestrictJoins(flib_netconn *conn);
+
+/**
+ * Allow/forbid adding teams to the room. Only makes sense in room state and if you are chief.
+ * The server does not send a reply.
+ */
+int flib_netconn_send_toggleRestrictTeams(flib_netconn *conn);
+
+/**
+ * Does something administrator-y. At any rate you need to be an administrator and in the lobby
+ * to use this command.
+ */
+int flib_netconn_send_clearAccountsCache(flib_netconn *conn);
+
+/**
+ * Sets a server variable to the indicated value. Only makes sense if you are server admin.
+ * Known variables are MOTD_NEW, MOTD_OLD and LATEST_PROTO.
+ */
+int flib_netconn_send_setServerVar(flib_netconn *conn, const char *name, const char *value);
+
+/**
+ * Queries all server variables. Only makes sense if you are server admin.
+ * If the action succeeds, you will receive several onServerVar callbacks with the
+ * current values of all server variables.
+ */
+int flib_netconn_send_getServerVars(flib_netconn *conn);
+
+
+
+
+
+
+
+
+
+
+/**
+ * Callback for several informational messages that should be displayed to the user
+ * (e.g. in the chat window), but do not require a reaction. If a game is running, you might
+ * want to redirect some of these messages to the engine as well so the user will see them.
+ */
+void flib_netconn_onMessage(flib_netconn *conn, void (*callback)(void *context, int msgtype, const char *msg), void* context);
+
+/**
+ * We received a chat message. Where this message belongs depends on the current state (lobby/room/game). In particular,
+ * if a game is running the message should be passed to the engine.
+ */
+void flib_netconn_onChat(flib_netconn *conn, void (*callback)(void *context, const char *nick, const char *msg), void* context);
+
+/**
+ * This is called when we receive a CONNECTED message from the server, which should be the first
+ * message arriving from the server.
+ */
+void flib_netconn_onConnected(flib_netconn *conn, void (*callback)(void *context), void* context);
+
+/**
+ * This is *always* the last callback (unless the netconn is destroyed early), and the netconn should be destroyed when it is received.
+ * The reason is one of the NETCONN_DISCONNECT_ constants. Sometime a message is included as well, but that parameter might
+ * also be NULL.
+ */
+void flib_netconn_onDisconnected(flib_netconn *conn, void (*callback)(void *context, int reason, const char *message), void* context);
+
+/**
+ * Callbacks for room list updates. The roomlist can be queried with flib_netconn_send_request_roomlist(), which will
+ * trigger flib_netconn_onRoomlist once the server replies. Additionally, the roomAdd/delete/update callbacks will fire
+ * whenever the server informs about these events, which can happen *before* the roomlist is first received - so be sure
+ * not to blindly reference your room list in these callbacks. The server currently only sends updates when a room changes
+ * its name, so in order to update other room information you need to query the roomlist again.
+ */
+void flib_netconn_onRoomlist(flib_netconn *conn, void (*callback)(void *context, const flib_room **rooms, int roomCount), void* context);
+void flib_netconn_onRoomAdd(flib_netconn *conn, void (*callback)(void *context, const flib_room *room), void* context);
+void flib_netconn_onRoomDelete(flib_netconn *conn, void (*callback)(void *context, const char *name), void* context);
+void flib_netconn_onRoomUpdate(flib_netconn *conn, void (*callback)(void *context, const char *oldName, const flib_room *room), void* context);
+
+/**
+ * Callbacks for players joining or leaving the lobby. If join is true it's a join, otherwise a leave.
+ * NOTE: partMessage is null if no parting message was given.
+ */
+void flib_netconn_onLobbyJoin(flib_netconn *conn, void (*callback)(void *context, const char *nick), void* context);
+void flib_netconn_onLobbyLeave(flib_netconn *conn, void (*callback)(void *context, const char *nick, const char *partMessage), void* context);
+
+/**
+ * onNickTaken is called on connecting to the server, if it turns out that there is already a player with the same nick.
+ * In order to proceed, a new nickname needs to be sent to the server using flib_netconn_send_nick() (or of course you can
+ * bail out and send a QUIT). If you don't set a callback, the netconn will automatically react by generating a new name.
+ * Once the server accepts a name, you will be informed with an onNickAccept callback.
+ */
+void flib_netconn_onNickTaken(flib_netconn *conn, void (*callback)(void *context, const char *nick), void* context);
+
+/**
+ * When connecting with a registered nickname, the server will ask for a password before admitting you in.
+ * This callback is called when that happens. As a reaction, you can send the password using
+ * flib_netconn_send_password or choose a different nick. If you don't register a callback,
+ * the default behavior is to just quit in a way that will cause a disconnect with NETCONN_DISCONNECT_AUTH_FAILED.
+ */
+void flib_netconn_onPasswordRequest(flib_netconn *conn, void (*callback)(void *context, const char *nick), void* context);
+
+/**
+ * You just left the lobby and entered a room.
+ * If chief is true, you can and should send a full configuration for the room now.
+ * This consists of ammo, scheme, script and map, where map apparently has to come last.
+ */
+void flib_netconn_onEnterRoom(flib_netconn *conn, void (*callback)(void *context, bool chief), void *context);
+
+
+/**
+ * The following callbacks are only relevant in room state.
+ */
+
+/**
+ * This callback informs about changes to your room chief status, i.e. whether you are allowed to
+ * modify the current room. Generally when you create a room you start out being room chief, and
+ * when you join an existing room you are not. However, in some situations room ownership can change,
+ * and if that happens this callback is called with the new status.
+ *
+ * Note: This callback does not automatically fire when joining a room. You can always query the
+ * current chief status using flib_netconn_is_chief().
+ */
+void flib_netconn_onRoomChiefStatus(flib_netconn *conn, void (*callback)(void *context, bool chief), void* context);
+
+/**
+ * One of the players in the room (possibly you!) changed their ready state.
+ */
+void flib_netconn_onReadyState(flib_netconn *conn, void (*callback)(void *context, const char *nick, bool ready), void* context);
+
+/**
+ * You just left a room and entered the lobby again.
+ * reason is one of the NETCONN_ROOMLEAVE_ constants.
+ * This will not be called when you actively leave a room using PART.
+ */
+void flib_netconn_onLeaveRoom(flib_netconn *conn, void (*callback)(void *context, int reason, const char *message), void *context);
+
+/**
+ * A new team was added to the room. The person who adds a team does NOT receive this callback (he gets onTeamAccepted instead).
+ * The team does not contain bindings, stats, weaponset, color or the number of hogs.
+ *
+ * If you receive this message and you are the room chief, you are expected to provide a color and hog count for this team using
+ * flib_netconn_send_teamHogCount / teamColor.
+ */
+void flib_netconn_onTeamAdd(flib_netconn *conn, void (*callback)(void *context, const flib_team *team), void *context);
+
+/**
+ * A team was removed from the room.
+ */
+void flib_netconn_onTeamDelete(flib_netconn *conn, void (*callback)(void *context, const char *teamname), void *context);
+
+void flib_netconn_onRoomJoin(flib_netconn *conn, void (*callback)(void *context, const char *nick), void* context);
+void flib_netconn_onRoomLeave(flib_netconn *conn, void (*callback)(void *context, const char *nick, const char *partMessage), void* context);
+
+/**
+ * The game is starting. Fire up the engine and join in!
+ * You can let the netconn generate the right game setup using flib_netconn_create_gamesetup
+ */
+void flib_netconn_onRunGame(flib_netconn *conn, void (*callback)(void *context), void *context);
+
+/**
+ * When you ask for a team to be added, the server might reject it for several reasons, e.g. because it has the same name
+ * as an existing team, or because the room chief restricted adding new teams. If the team is accepted by the server,
+ * this callback is fired.
+ *
+ * If you are the room chief, you are expected to provide the hog count for your own team now using flib_netconn_send_teamHogCount.
+ * The color of the team is already set to the one you provided in addTeam, but the QtFrontend apparently always uses 0 there and
+ * instead sets the color after the team is accepted.
+ */
+void flib_netconn_onTeamAccepted(flib_netconn *conn, void (*callback)(void *context, const char *team), void *context);
+
+/**
+ * The number of hogs in a team has been changed by the room chief. If you are the chief and change the number of hogs yourself,
+ * you will not receive this callback!
+ */
+void flib_netconn_onHogCountChanged(flib_netconn *conn, void (*callback)(void *context, const char *teamName, int hogs), void *context);
+
+/**
+ * The color of a team has been changed by the room chief. If you are the chief and change the color yourself,
+ * you will not receive this callback!
+ */
+void flib_netconn_onTeamColorChanged(flib_netconn *conn, void (*callback)(void *context, const char *teamName, int colorIndex), void *context);
+
+void flib_netconn_onEngineMessage(flib_netconn *conn, void (*callback)(void *context, const uint8_t *message, size_t size), void *context);
+
+void flib_netconn_onCfgScheme(flib_netconn *conn, void (*callback)(void *context, const flib_scheme *scheme), void *context);
+
+/**
+ * This is called when the map configuration in a room is changed (or first received). Only non-chiefs receive these messages.
+ * To reduce the number of callback functions, the netconn keeps track of the current map settings and always passes the entire
+ * current map config, but informs the callee about what has changed (see the NETCONN_MAPCHANGE_ constants).
+ * The map parameter passed to the callback is an internally held map config. If you want to keep it around, best make a copy
+ * or it may or may not change while you are not looking.
+ *
+ * Caution: Due to the way the protocol works, the map might not be complete at this point if it is a hand-drawn map, because
+ * the "full" map config does not include the drawn map data.
+ */
+void flib_netconn_onMapChanged(flib_netconn *conn, void (*callback)(void *context, const flib_map *map, int changetype), void *context);
+
+/**
+ * The "game style" script has been changed by the room chief. If you are the chief and change the script yourself,
+ * you will not receive this callback!
+ */
+void flib_netconn_onScriptChanged(flib_netconn *conn, void (*callback)(void *context, const char *script), void *context);
+
+/**
+ * The weaponset has been changed by the room chief. If you are the chief and change the weaponset yourself,
+ * you will not receive this callback!
+ */
+void flib_netconn_onWeaponsetChanged(flib_netconn *conn, void (*callback)(void *context, const flib_weaponset *weaponset), void *context);
+
+/**
+ * This callback is called if the server informs us that we have admin rights.
+ */
+void flib_netconn_onAdminAccess(flib_netconn *conn, void (*callback)(void *context), void *context);
+
+/**
+ * When you query the server vars with flib_netconn_send_getServerVars (only works as admin), the server
+ * replies with a list of them. This callback is called for each entry in that list.
+ */
+void flib_netconn_onServerVar(flib_netconn *conn, void (*callback)(void *context, const char *name, const char *value), void *context);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netconn_callbacks.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,153 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "netconn_internal.h"
+
+#include "../util/logging.h"
+#include "../util/util.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+static void defaultCallback_onMessage(void *context, int msgtype, const char *msg) {
+	flib_log_i("Net: [%i] %s", msgtype, msg);
+}
+
+static void defaultCallback_onChat(void *context, const char *nick, const char *msg) {
+	flib_log_i("%s: %s", nick, msg);
+}
+
+// Change the name by suffixing it with a number. If it already ends in a number, increase that number by 1.
+static void defaultCallback_onNickTaken(void *context, const char *requestedNick) {
+	flib_netconn *conn = context;
+	size_t namelen = strlen(requestedNick);
+	int digits = 0;
+	while(digits<namelen && isdigit(requestedNick[namelen-1-digits])) {
+		digits++;
+	}
+	long suffix = 0;
+	if(digits>0) {
+		suffix = atol(requestedNick+namelen-digits)+1;
+	}
+	char *newPlayerName = flib_asprintf("%.*s%li", namelen-digits, requestedNick, suffix);
+	if(newPlayerName) {
+		flib_netconn_send_nick(conn, newPlayerName);
+	} else {
+		flib_netconn_send_quit(conn, "Nick already taken.");
+	}
+	free(newPlayerName);
+}
+
+// Default behavior: Quit
+static void defaultCallback_onPasswordRequest(void *context, const char *requestedNick) {
+	flib_netconn_send_quit((flib_netconn*)context, "Authentication failed");
+}
+
+void netconn_clearCallbacks(flib_netconn *conn) {
+	flib_netconn_onMessage(conn, NULL, NULL);
+	flib_netconn_onConnected(conn, NULL, NULL);
+	flib_netconn_onDisconnected(conn, NULL, NULL);
+	flib_netconn_onRoomlist(conn, NULL, NULL);
+	flib_netconn_onRoomAdd(conn, NULL, NULL);
+	flib_netconn_onRoomDelete(conn, NULL, NULL);
+	flib_netconn_onRoomUpdate(conn, NULL, NULL);
+	flib_netconn_onChat(conn, NULL, NULL);
+	flib_netconn_onLobbyJoin(conn, NULL, NULL);
+	flib_netconn_onLobbyLeave(conn, NULL, NULL);
+	flib_netconn_onRoomJoin(conn, NULL, NULL);
+	flib_netconn_onRoomLeave(conn, NULL, NULL);
+	flib_netconn_onNickTaken(conn, NULL, NULL);
+	flib_netconn_onPasswordRequest(conn, NULL, NULL);
+	flib_netconn_onRoomChiefStatus(conn, NULL, NULL);
+	flib_netconn_onReadyState(conn, NULL, NULL);
+	flib_netconn_onEnterRoom(conn, NULL, NULL);
+	flib_netconn_onLeaveRoom(conn, NULL, NULL);
+	flib_netconn_onTeamAdd(conn, NULL, NULL);
+	flib_netconn_onTeamDelete(conn, NULL, NULL);
+	flib_netconn_onRunGame(conn, NULL, NULL);
+	flib_netconn_onTeamAccepted(conn, NULL, NULL);
+	flib_netconn_onHogCountChanged(conn, NULL, NULL);
+	flib_netconn_onTeamColorChanged(conn, NULL, NULL);
+	flib_netconn_onEngineMessage(conn, NULL, NULL);
+	flib_netconn_onCfgScheme(conn, NULL, NULL);
+	flib_netconn_onMapChanged(conn, NULL, NULL);
+	flib_netconn_onScriptChanged(conn, NULL, NULL);
+	flib_netconn_onWeaponsetChanged(conn, NULL, NULL);
+	flib_netconn_onAdminAccess(conn, NULL, NULL);
+	flib_netconn_onServerVar(conn, NULL, NULL);
+}
+
+/**
+ * This macro generates a callback setter function. It uses the name of the callback to
+ * automatically generate the function name and the fields to set, so a consistent naming
+ * convention needs to be enforced (not that that is a bad thing). If null is passed as
+ * callback to the generated function, the defaultCb will be set instead (with conn
+ * as the context).
+ */
+#define GENERATE_CB_SETTER(cbName, cbParameterTypes, defaultCb) \
+	void flib_netconn_##cbName(flib_netconn *conn, void (*callback)cbParameterTypes, void *context) { \
+		if(!log_badargs_if(conn==NULL)) { \
+			conn->cbName##Cb = callback ? callback : &defaultCb; \
+			conn->cbName##Ctx = callback ? context : conn; \
+		} \
+	}
+
+/**
+ * Generate a callback setter function like GENERATE_CB_SETTER, and automatically generate a
+ * no-op callback function as well that is used as default.
+ */
+#define GENERATE_CB_SETTER_AND_DEFAULT(cbName, cbParameterTypes) \
+	static void _noop_callback_##cbName cbParameterTypes {} \
+	GENERATE_CB_SETTER(cbName, cbParameterTypes, _noop_callback_##cbName)
+
+GENERATE_CB_SETTER(onMessage, (void *context, int msgtype, const char *msg), defaultCallback_onMessage);
+GENERATE_CB_SETTER_AND_DEFAULT(onConnected, (void *context));
+GENERATE_CB_SETTER_AND_DEFAULT(onDisconnected, (void *context, int reason, const char *message));
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomlist, (void *context, const flib_room **rooms, int roomCount));
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomAdd, (void *context, const flib_room *room));
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomDelete, (void *context, const char *name));
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomUpdate, (void *context, const char *oldName, const flib_room *room));
+GENERATE_CB_SETTER(onChat, (void *context, const char *nick, const char *msg), defaultCallback_onChat);
+GENERATE_CB_SETTER_AND_DEFAULT(onLobbyJoin, (void *context, const char *nick));
+GENERATE_CB_SETTER_AND_DEFAULT(onLobbyLeave, (void *context, const char *nick, const char *partMsg));
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomJoin, (void *context, const char *nick));
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomLeave, (void *context, const char *nick, const char *partMessage));
+GENERATE_CB_SETTER(onNickTaken, (void *context, const char *nick), defaultCallback_onNickTaken);
+GENERATE_CB_SETTER(onPasswordRequest, (void *context, const char *nick), defaultCallback_onPasswordRequest);
+GENERATE_CB_SETTER_AND_DEFAULT(onRoomChiefStatus, (void *context, bool chief));
+GENERATE_CB_SETTER_AND_DEFAULT(onReadyState, (void *context, const char *nick, bool ready));
+GENERATE_CB_SETTER_AND_DEFAULT(onEnterRoom, (void *context, bool chief));
+GENERATE_CB_SETTER_AND_DEFAULT(onLeaveRoom, (void *context, int reason, const char *message));
+GENERATE_CB_SETTER_AND_DEFAULT(onTeamAdd, (void *context, const flib_team *team));
+GENERATE_CB_SETTER_AND_DEFAULT(onTeamDelete, (void *context, const char *teamname));
+GENERATE_CB_SETTER_AND_DEFAULT(onRunGame, (void *context));
+GENERATE_CB_SETTER_AND_DEFAULT(onTeamAccepted, (void *context, const char *teamName));
+GENERATE_CB_SETTER_AND_DEFAULT(onHogCountChanged, (void *context, const char *teamName, int hogs));
+GENERATE_CB_SETTER_AND_DEFAULT(onTeamColorChanged, (void *context, const char *teamName, int colorIndex));
+GENERATE_CB_SETTER_AND_DEFAULT(onEngineMessage, (void *context, const uint8_t *message, size_t size));
+GENERATE_CB_SETTER_AND_DEFAULT(onCfgScheme, (void *context, const flib_scheme *scheme));
+GENERATE_CB_SETTER_AND_DEFAULT(onMapChanged, (void *context, const flib_map *map, int changetype));
+GENERATE_CB_SETTER_AND_DEFAULT(onScriptChanged, (void *context, const char *script));
+GENERATE_CB_SETTER_AND_DEFAULT(onWeaponsetChanged, (void *context, const flib_weaponset *weaponset));
+GENERATE_CB_SETTER_AND_DEFAULT(onAdminAccess, (void *context));
+GENERATE_CB_SETTER_AND_DEFAULT(onServerVar, (void *context, const char *name, const char *value));
+
+#undef GENERATE_CB_SETTER_AND_DEFAULT
+#undef GENERATE_CB_SETTER
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netconn_internal.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,158 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Common definitions needed by netconn functions, to allow splitting them into several files.
+ */
+
+#ifndef NETCONN_INTERNAL_H_
+#define NETCONN_INTERNAL_H_
+
+#include "netconn.h"
+#include "netbase.h"
+#include "../model/map.h"
+#include "../model/team.h"
+#include "../model/weapon.h"
+#include "../model/room.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+struct _flib_netconn {
+	flib_netbase *netBase;
+	char *playerName;
+	char *dataDirPath;
+
+	int netconnState;			// One of the NETCONN_STATE constants
+	bool isAdmin;				// Player is server administrator
+
+	bool isChief;				// Player can modify the current room
+	flib_map *map;
+	flib_teamlist pendingTeamlist;
+	flib_teamlist teamlist;
+	flib_scheme *scheme;
+	char *style;
+	flib_weaponset *weaponset;
+
+	void (*onMessageCb)(void *context, int msgtype, const char *msg);
+	void *onMessageCtx;
+
+	void (*onConnectedCb)(void *context);
+	void *onConnectedCtx;
+
+	void (*onDisconnectedCb)(void *context, int reason, const char *message);
+	void *onDisconnectedCtx;
+
+	void (*onRoomlistCb)(void *context, const flib_room **rooms, int roomCount);
+	void *onRoomlistCtx;
+
+	void (*onRoomAddCb)(void *context, const flib_room *room);
+	void *onRoomAddCtx;
+
+	void (*onRoomDeleteCb)(void *context, const char *name);
+	void *onRoomDeleteCtx;
+
+	void (*onRoomUpdateCb)(void *context, const char *oldName, const flib_room *room);
+	void *onRoomUpdateCtx;
+
+	void (*onChatCb)(void *context, const char *nick, const char *msg);
+	void *onChatCtx;
+
+	void (*onLobbyJoinCb)(void *context, const char *nick);
+	void *onLobbyJoinCtx;
+
+	void (*onLobbyLeaveCb)(void *context, const char *nick, const char *partMessage);
+	void *onLobbyLeaveCtx;
+
+	void (*onRoomJoinCb)(void *context, const char *nick);
+	void *onRoomJoinCtx;
+
+	void (*onRoomLeaveCb)(void *context, const char *nick, const char *partMessage);
+	void *onRoomLeaveCtx;
+
+	void (*onNickTakenCb)(void *context, const char *nick);
+	void *onNickTakenCtx;
+
+	void (*onPasswordRequestCb)(void *context, const char *nick);
+	void *onPasswordRequestCtx;
+
+	void (*onRoomChiefStatusCb)(void *context, bool isChief);
+	void *onRoomChiefStatusCtx;
+
+	void (*onReadyStateCb)(void *context, const char *nick, bool ready);
+	void *onReadyStateCtx;
+
+	void (*onEnterRoomCb)(void *context, bool chief);
+	void *onEnterRoomCtx;
+
+	void (*onLeaveRoomCb)(void *context, int reason, const char *message);
+	void *onLeaveRoomCtx;
+
+	void (*onTeamAddCb)(void *context, const flib_team *team);
+	void *onTeamAddCtx;
+
+	void (*onTeamDeleteCb)(void *context, const char *teamname);
+	void *onTeamDeleteCtx;
+
+	void (*onRunGameCb)(void *context);
+	void *onRunGameCtx;
+
+	void (*onTeamAcceptedCb)(void *context, const char *teamName);
+	void *onTeamAcceptedCtx;
+
+	void (*onHogCountChangedCb)(void *context, const char *teamName, int hogs);
+	void *onHogCountChangedCtx;
+
+	void (*onTeamColorChangedCb)(void *context, const char *teamName, int colorIndex);
+	void *onTeamColorChangedCtx;
+
+	void (*onEngineMessageCb)(void *context, const uint8_t *message, size_t size);
+	void *onEngineMessageCtx;
+
+	void (*onCfgSchemeCb)(void *context, const flib_scheme *scheme);
+	void *onCfgSchemeCtx;
+
+	void (*onMapChangedCb)(void *context, const flib_map *map, int changetype);
+	void *onMapChangedCtx;
+
+	void (*onScriptChangedCb)(void *context, const char *script);
+	void *onScriptChangedCtx;
+
+	void (*onWeaponsetChangedCb)(void *context, const flib_weaponset *weaponset);
+	void *onWeaponsetChangedCtx;
+
+	void (*onAdminAccessCb)(void *context);
+	void *onAdminAccessCtx;
+
+	void (*onServerVarCb)(void *context, const char *name, const char *value);
+	void *onServerVarCtx;
+
+	bool running;
+	bool destroyRequested;
+};
+
+void netconn_clearCallbacks(flib_netconn *conn);
+void netconn_leaveRoom(flib_netconn *conn);
+void netconn_setMap(flib_netconn *conn, const flib_map *map);
+void netconn_setWeaponset(flib_netconn *conn, const flib_weaponset *weaponset);
+void netconn_setScript(flib_netconn *conn, const char *script);
+void netconn_setScheme(flib_netconn *conn, const flib_scheme *scheme);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netconn_send.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,495 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "netconn_internal.h"
+
+#include "../util/logging.h"
+#include "../util/util.h"
+#include "../util/buffer.h"
+#include "../md5/md5.h"
+#include "../base64/base64.h"
+
+#include <zlib.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+// cmdname is always given as literal from functions in this file, so it is never null.
+static int sendVoid(flib_netconn *conn, const char *cmdname) {
+	if(log_e_if(!conn, "Invalid parameter sending %s command", cmdname)) {
+		return -1;
+	}
+	return flib_netbase_sendf(conn->netBase, "%s\n\n", cmdname);
+}
+
+// Testing for !*str prevents sending 0-length parameters (they trip up the protocol)
+static int sendStr(flib_netconn *conn, const char *cmdname, const char *str) {
+	if(log_e_if(!conn || flib_strempty(str), "Invalid parameter sending %s command", cmdname)) {
+		return -1;
+	}
+	return flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", cmdname, str);
+}
+
+static int sendInt(flib_netconn *conn, const char *cmdname, int param) {
+	if(log_e_if(!conn, "Invalid parameter sending %s command", cmdname)) {
+		return -1;
+	}
+	return flib_netbase_sendf(conn->netBase, "%s\n%i\n\n", cmdname, param);
+}
+
+int flib_netconn_send_quit(flib_netconn *conn, const char *quitmsg) {
+	return sendStr(conn, "QUIT", (quitmsg && *quitmsg) ? quitmsg : "User quit");
+}
+
+int flib_netconn_send_chat(flib_netconn *conn, const char *chat) {
+	if(!flib_strempty(chat)) {
+		return sendStr(conn, "CHAT", chat);
+	}
+	return 0;
+}
+
+int flib_netconn_send_teamchat(flib_netconn *conn, const char *chat) {
+	if(!flib_strempty(chat)) {
+		return sendStr(conn, "TEAMCHAT", chat);
+	}
+	return 0;
+}
+
+int flib_netconn_send_nick(flib_netconn *conn, const char *nick) {
+	int result = -1;
+	if(!log_badargs_if2(conn==NULL, flib_strempty(nick))) {
+		char *tmpName = flib_strdupnull(nick);
+		if(tmpName) {
+			if(!flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", "NICK", nick)) {
+				free(conn->playerName);
+				conn->playerName = tmpName;
+				tmpName = NULL;
+				result = 0;
+			}
+		}
+		free(tmpName);
+	}
+	return result;
+}
+
+int flib_netconn_send_password(flib_netconn *conn, const char *passwd) {
+	int result = -1;
+	if(!log_badargs_if2(conn==NULL, passwd==NULL)) {
+		md5_state_t md5state;
+		uint8_t md5bytes[16];
+		char md5hex[33];
+		md5_init(&md5state);
+		md5_append(&md5state, (unsigned char*)passwd, strlen(passwd));
+		md5_finish(&md5state, md5bytes);
+		for(int i=0;i<sizeof(md5bytes); i++) {
+			// Needs to be lowercase - server checks case sensitive
+			snprintf(md5hex+i*2, 3, "%02x", (unsigned)md5bytes[i]);
+		}
+		result = flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", "PASSWORD", md5hex);
+	}
+	return result;
+}
+
+int flib_netconn_send_request_roomlist(flib_netconn *conn) {
+	return sendVoid(conn, "LIST");
+}
+
+int flib_netconn_send_joinRoom(flib_netconn *conn, const char *room) {
+	if(!sendStr(conn, "JOIN_ROOM", room)) {
+		conn->isChief = false;
+		return 0;
+	}
+	return -1;
+}
+
+int flib_netconn_send_createRoom(flib_netconn *conn, const char *room) {
+	if(!sendStr(conn, "CREATE_ROOM", room)) {
+		conn->isChief = true;
+		return 0;
+	}
+	return -1;
+}
+
+int flib_netconn_send_renameRoom(flib_netconn *conn, const char *roomName) {
+	return sendStr(conn, "ROOM_NAME", roomName);
+}
+
+int flib_netconn_send_leaveRoom(flib_netconn *conn, const char *str) {
+	int result = -1;
+	if(conn->netconnState==NETCONN_STATE_ROOM) {
+		result = (str && *str) ? sendStr(conn, "PART", str) : sendVoid(conn, "PART");
+		if(!result) {
+			netconn_leaveRoom(conn);
+		}
+	}
+	return result;
+}
+
+int flib_netconn_send_toggleReady(flib_netconn *conn) {
+	return sendVoid(conn, "TOGGLE_READY");
+}
+
+static void addTeamToPendingList(flib_netconn *conn, const flib_team *team) {
+	flib_team *teamcopy = flib_team_copy(team);
+	if(teamcopy) {
+		teamcopy->remoteDriven = false;
+		free(teamcopy->ownerName);
+		teamcopy->ownerName = flib_strdupnull(conn->playerName);
+		if(teamcopy->ownerName) {
+			flib_teamlist_delete(&conn->pendingTeamlist, team->name);
+			if(!flib_teamlist_insert(&conn->pendingTeamlist, teamcopy, 0)) {
+				teamcopy = NULL;
+			}
+		}
+	}
+	flib_team_destroy(teamcopy);
+}
+
+int flib_netconn_send_addTeam(flib_netconn *conn, const flib_team *team) {
+	int result = -1;
+	if(!log_badargs_if2(conn==NULL, team==NULL)) {
+		bool missingInfo = flib_strempty(team->name) || flib_strempty(team->grave) || flib_strempty(team->fort) || flib_strempty(team->voicepack) || flib_strempty(team->flag);
+		for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+			missingInfo |= flib_strempty(team->hogs[i].name) || flib_strempty(team->hogs[i].hat);
+		}
+		if(!log_e_if(missingInfo, "Incomplete team definition")) {
+			flib_vector *vec = flib_vector_create();
+			if(vec) {
+				bool error = false;
+				error |= flib_vector_appendf(vec, "ADD_TEAM\n%s\n%i\n%s\n%s\n%s\n%s\n%i\n", team->name, team->colorIndex, team->grave, team->fort, team->voicepack, team->flag, team->hogs[0].difficulty);
+				for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+					error |= flib_vector_appendf(vec, "%s\n%s\n", team->hogs[i].name, team->hogs[i].hat);
+				}
+				error |= flib_vector_appendf(vec, "\n");
+				if(!error && !flib_netbase_send_raw(conn->netBase, flib_vector_data(vec), flib_vector_size(vec))) {
+					addTeamToPendingList(conn, team);
+					result = 0;
+				}
+			}
+			flib_vector_destroy(vec);
+		}
+	}
+	return result;
+}
+
+int flib_netconn_send_removeTeam(flib_netconn *conn, const char *teamname) {
+	flib_team *team = flib_teamlist_find(&conn->teamlist, teamname);
+	if(team && !team->remoteDriven && !sendStr(conn, "REMOVE_TEAM", teamname)) {
+		flib_teamlist_delete(&conn->teamlist, teamname);
+		return 0;
+	}
+	return -1;
+}
+
+int flib_netconn_send_engineMessage(flib_netconn *conn, const uint8_t *message, size_t size) {
+	int result = -1;
+	if(!log_badargs_if2(conn==NULL, message==NULL && size>0)) {
+		char *base64encout = NULL;
+		base64_encode_alloc((const char*)message, size, &base64encout);
+		if(base64encout) {
+			result = flib_netbase_sendf(conn->netBase, "EM\n%s\n\n", base64encout);
+		}
+		free(base64encout);
+	}
+	return result;
+}
+
+int flib_netconn_send_teamHogCount(flib_netconn *conn, const char *teamname, int hogcount) {
+	if(!log_badargs_if5(conn==NULL, flib_strempty(teamname), hogcount<1, hogcount>HEDGEHOGS_PER_TEAM, !conn->isChief)
+			&& !flib_netbase_sendf(conn->netBase, "HH_NUM\n%s\n%i\n\n", teamname, hogcount)) {
+		flib_team *team = flib_teamlist_find(&conn->teamlist, teamname);
+		if(team) {
+			team->hogsInGame = hogcount;
+		}
+		return 0;
+	}
+	return -1;
+}
+
+int flib_netconn_send_teamColor(flib_netconn *conn, const char *teamname, int colorIndex) {
+	if(!log_badargs_if3(conn==NULL, flib_strempty(teamname), !conn->isChief)
+			&& !flib_netbase_sendf(conn->netBase, "TEAM_COLOR\n%s\n%i\n\n", teamname, colorIndex)) {
+		flib_team *team = flib_teamlist_find(&conn->teamlist, teamname);
+		if(team) {
+			team->colorIndex = colorIndex;
+		}
+		return 0;
+	}
+	return -1;
+}
+
+int flib_netconn_send_weaponset(flib_netconn *conn, const flib_weaponset *weaponset) {
+	if(!log_badargs_if3(conn==NULL, weaponset==NULL, flib_strempty(weaponset->name))) {
+		char ammostring[WEAPONS_COUNT*4+1];
+		strcpy(ammostring, weaponset->loadout);
+		strcat(ammostring, weaponset->crateprob);
+		strcat(ammostring, weaponset->delay);
+		strcat(ammostring, weaponset->crateammo);
+		if(conn->isChief) {
+			if(!flib_netbase_sendf(conn->netBase, "CFG\nAMMO\n%s\n%s\n\n", weaponset->name, ammostring)) {
+				netconn_setWeaponset(conn, weaponset);
+				return 0;
+			}
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_map(flib_netconn *conn, const flib_map *map) {
+	if(log_badargs_if2(conn==NULL, map==NULL)) {
+		return -1;
+	}
+	bool error = false;
+
+	if(map->seed) {
+		error |= flib_netconn_send_mapSeed(conn, map->seed);
+	}
+	error |= flib_netconn_send_mapTemplate(conn, map->templateFilter);
+	if(map->theme) {
+		error |= flib_netconn_send_mapTheme(conn, map->theme);
+	}
+	error |= flib_netconn_send_mapGen(conn, map->mapgen);
+	error |= flib_netconn_send_mapMazeSize(conn, map->mazeSize);
+	if(map->drawData && map->drawDataSize>0) {
+		error |= flib_netconn_send_mapDrawdata(conn, map->drawData, map->drawDataSize);
+	}
+	// Name is sent last, because the QtFrontend uses this to update its preview, and to show/hide
+	// certain fields
+	if(map->name) {
+		error |= flib_netconn_send_mapName(conn, map->name);
+	}
+	return error;
+}
+
+int flib_netconn_send_mapName(flib_netconn *conn, const char *mapName) {
+	if(log_badargs_if2(conn==NULL, mapName==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendStr(conn, "CFG\nMAP", mapName)) {
+			char *copy = flib_strdupnull(mapName);
+			if(copy) {
+				free(conn->map->name);
+				conn->map->name = copy;
+				return 0;
+			}
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_mapGen(flib_netconn *conn, int mapGen) {
+	if(log_badargs_if(conn==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendInt(conn, "CFG\nMAPGEN", mapGen)) {
+			conn->map->mapgen = mapGen;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_mapTemplate(flib_netconn *conn, int templateFilter) {
+	if(log_badargs_if(conn==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendInt(conn, "CFG\nTEMPLATE", templateFilter)) {
+			conn->map->templateFilter = templateFilter;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_mapMazeSize(flib_netconn *conn, int mazeSize) {
+	if(log_badargs_if(conn==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendInt(conn, "CFG\nMAZE_SIZE", mazeSize)) {
+			conn->map->mazeSize = mazeSize;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_mapSeed(flib_netconn *conn, const char *seed) {
+	if(log_badargs_if2(conn==NULL, seed==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendStr(conn, "CFG\nSEED", seed)) {
+			char *copy = flib_strdupnull(seed);
+			if(copy) {
+				free(conn->map->seed);
+				conn->map->seed = copy;
+				return 0;
+			}
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_mapTheme(flib_netconn *conn, const char *theme) {
+	if(log_badargs_if2(conn==NULL, theme==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendStr(conn, "CFG\nTHEME", theme)) {
+			char *copy = flib_strdupnull(theme);
+			if(copy) {
+				free(conn->map->theme);
+				conn->map->theme = copy;
+				return 0;
+			}
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_mapDrawdata(flib_netconn *conn, const uint8_t *drawData, size_t size) {
+	int result = -1;
+	if(!log_badargs_if3(conn==NULL, drawData==NULL && size>0, size>SIZE_MAX/2) && conn->isChief) {
+		uLongf zippedSize = compressBound(size);
+		uint8_t *zipped = flib_malloc(zippedSize+4); // 4 extra bytes for header
+		if(zipped) {
+			// Create the QCompress size header (uint32 big endian)
+			zipped[0] = (size>>24) & 0xff;
+			zipped[1] = (size>>16) & 0xff;
+			zipped[2] = (size>>8) & 0xff;
+			zipped[3] = (size) & 0xff;
+
+			if(compress(zipped+4, &zippedSize, drawData, size) != Z_OK) {
+				flib_log_e("Error compressing drawn map data.");
+			} else {
+				char *base64encout = NULL;
+				base64_encode_alloc((const char*)zipped, zippedSize+4, &base64encout);
+				if(!base64encout) {
+					flib_log_e("Error base64-encoding drawn map data.");
+				} else {
+					result = flib_netbase_sendf(conn->netBase, "CFG\nDRAWNMAP\n%s\n\n", base64encout);
+				}
+				free(base64encout);
+			}
+		}
+		free(zipped);
+	}
+
+	if(!result) {
+		uint8_t *copy = flib_bufdupnull(drawData, size);
+		if(copy) {
+			free(conn->map->drawData);
+			conn->map->drawData = copy;
+			conn->map->drawDataSize = size;
+		}
+	}
+	return result;
+}
+
+int flib_netconn_send_script(flib_netconn *conn, const char *scriptName) {
+	if(log_badargs_if2(conn==NULL, scriptName==NULL)) {
+		return -1;
+	}
+	if(conn->isChief) {
+		if(!sendStr(conn, "CFG\nSCRIPT", scriptName)) {
+			netconn_setScript(conn, scriptName);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int flib_netconn_send_scheme(flib_netconn *conn, const flib_scheme *scheme) {
+	int result = -1;
+	if(!log_badargs_if3(conn==NULL, scheme==NULL, flib_strempty(scheme->name)) && conn->isChief) {
+		flib_vector *vec = flib_vector_create();
+		if(vec) {
+			bool error = false;
+			error |= flib_vector_appendf(vec, "CFG\nSCHEME\n%s\n", scheme->name);
+			for(int i=0; i<flib_meta.modCount; i++) {
+				error |= flib_vector_appendf(vec, "%s\n", scheme->mods[i] ? "true" : "false");
+			}
+			for(int i=0; i<flib_meta.settingCount; i++) {
+				error |= flib_vector_appendf(vec, "%i\n", scheme->settings[i]);
+			}
+			error |= flib_vector_appendf(vec, "\n");
+			if(!error) {
+				result = flib_netbase_send_raw(conn->netBase, flib_vector_data(vec), flib_vector_size(vec));
+			}
+		}
+		flib_vector_destroy(vec);
+	}
+
+	if(!result) {
+		netconn_setScheme(conn, scheme);
+	}
+	return result;
+}
+
+int flib_netconn_send_roundfinished(flib_netconn *conn, bool withoutError) {
+	return sendInt(conn, "ROUNDFINISHED", withoutError ? 1 : 0);
+}
+
+int flib_netconn_send_ban(flib_netconn *conn, const char *playerName) {
+	return sendStr(conn, "BAN", playerName);
+}
+
+int flib_netconn_send_kick(flib_netconn *conn, const char *playerName) {
+	return sendStr(conn, "KICK", playerName);
+}
+
+int flib_netconn_send_playerInfo(flib_netconn *conn, const char *playerName) {
+	return sendStr(conn, "INFO", playerName);
+}
+
+int flib_netconn_send_playerFollow(flib_netconn *conn, const char *playerName) {
+	return sendStr(conn, "FOLLOW", playerName);
+}
+
+int flib_netconn_send_startGame(flib_netconn *conn) {
+	return sendVoid(conn, "START_GAME");
+}
+
+int flib_netconn_send_toggleRestrictJoins(flib_netconn *conn) {
+	return sendVoid(conn, "TOGGLE_RESTRICT_JOINS");
+}
+
+int flib_netconn_send_toggleRestrictTeams(flib_netconn *conn) {
+	return sendVoid(conn, "TOGGLE_RESTRICT_TEAMS");
+}
+
+int flib_netconn_send_clearAccountsCache(flib_netconn *conn) {
+	return sendVoid(conn, "CLEAR_ACCOUNTS_CACHE");
+}
+
+int flib_netconn_send_setServerVar(flib_netconn *conn, const char *name, const char *value) {
+	if(log_badargs_if3(conn==NULL, flib_strempty(name), flib_strempty(value))) {
+		return -1;
+	}
+	return flib_netbase_sendf(conn->netBase, "%s\n%s\n%s\n\n", "SET_SERVER_VAR", name, value);
+}
+
+int flib_netconn_send_getServerVars(flib_netconn *conn) {
+	return sendVoid(conn, "GET_SERVER_VAR");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netprotocol.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,187 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "netprotocol.h"
+
+#include "../util/util.h"
+#include "../util/logging.h"
+
+#include "../base64/base64.h"
+
+#include <zlib.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+
+static int fillTeamFromMsg(flib_team *team, char **parts) {
+	team->name = flib_strdupnull(parts[0]);
+	team->grave = flib_strdupnull(parts[1]);
+	team->fort = flib_strdupnull(parts[2]);
+	team->voicepack = flib_strdupnull(parts[3]);
+	team->flag = flib_strdupnull(parts[4]);
+	team->ownerName = flib_strdupnull(parts[5]);
+	if(!team->name || !team->grave || !team->fort || !team->voicepack || !team->flag || !team->ownerName) {
+		return -1;
+	}
+
+	errno = 0;
+	long difficulty = strtol(parts[6], NULL, 10);
+	if(errno) {
+		return -1;
+	}
+
+	for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+		flib_hog *hog = &team->hogs[i];
+		hog->difficulty = difficulty;
+		hog->name = flib_strdupnull(parts[7+2*i]);
+		hog->hat = flib_strdupnull(parts[8+2*i]);
+		if(!hog->name || !hog->hat) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+flib_team *flib_team_from_netmsg(char **parts) {
+	flib_team *result = NULL;
+	flib_team *tmpTeam = flib_calloc(1, sizeof(flib_team));
+	if(tmpTeam) {
+		if(!fillTeamFromMsg(tmpTeam, parts)) {
+			result = tmpTeam;
+			tmpTeam = NULL;
+		} else {
+			flib_log_e("Error parsing team from net.");
+		}
+	}
+	flib_team_destroy(tmpTeam);
+	return result;
+}
+
+flib_scheme *flib_scheme_from_netmsg(char **parts) {
+	flib_scheme *result = flib_scheme_create(parts[0]);
+	if(result) {
+		for(int i=0; i<flib_meta.modCount; i++) {
+			result->mods[i] = !strcmp(parts[i+1], "true");
+		}
+		for(int i=0; i<flib_meta.settingCount; i++) {
+			result->settings[i] = atoi(parts[i+flib_meta.modCount+1]);
+		}
+	}
+	return result;
+}
+
+flib_map *flib_map_from_netmsg(char **parts) {
+	flib_map *result = flib_map_create_named(parts[3], parts[0]);
+	if(result) {
+		result->mapgen = atoi(parts[1]);
+		result->mazeSize = atoi(parts[2]);
+		result->templateFilter = atoi(parts[4]);
+	}
+	return result;
+}
+
+int flib_drawnmapdata_from_netmsg(char *netmsg, uint8_t** outbuf, size_t *outlen) {
+	int result = -1;
+
+	// First step: base64 decoding
+	char *base64decout = NULL;
+	size_t base64declen;
+	bool ok = base64_decode_alloc(netmsg, strlen(netmsg), &base64decout, &base64declen);
+	if(ok && base64declen>3) {
+		// Second step: unzip with the QCompress header. That header is just a big-endian
+		// uint32 indicating the length of the uncompressed data.
+		uint8_t *ubyteBuf = (uint8_t*)base64decout;
+		uint32_t unzipLen =
+				(((uint32_t)ubyteBuf[0])<<24)
+				+ (((uint32_t)ubyteBuf[1])<<16)
+				+ (((uint32_t)ubyteBuf[2])<<8)
+				+ ubyteBuf[3];
+		if(unzipLen==0) {
+			*outbuf = NULL;
+			*outlen = 0;
+			result = 0;
+		} else {
+			uint8_t *out = flib_malloc(unzipLen);
+			if(out) {
+				uLongf actualUnzipLen = unzipLen;
+				int resultcode = uncompress(out, &actualUnzipLen, (Bytef*)(base64decout+4), base64declen-4);
+				if(resultcode == Z_OK) {
+					*outbuf = out;
+					*outlen = actualUnzipLen;
+					out = NULL;
+					result = 0;
+				} else {
+					flib_log_e("Uncompressing drawn map failed. Code: %i", resultcode);
+				}
+			}
+			free(out);
+		}
+	} else {
+		flib_log_e("base64 decoding of drawn map failed.");
+	}
+	free(base64decout);
+	return result;
+}
+
+flib_room *flib_room_from_netmsg(char **params) {
+	flib_room *result = NULL;
+	flib_room *tmpRoom = flib_calloc(1, sizeof(flib_room));
+	if(tmpRoom) {
+		tmpRoom->inProgress = !strcmp(params[0], "True");
+		tmpRoom->name = flib_strdupnull(params[1]);
+		tmpRoom->playerCount = atoi(params[2]);
+		tmpRoom->teamCount = atoi(params[3]);
+		tmpRoom->owner = flib_strdupnull(params[4]);
+		tmpRoom->map = flib_strdupnull(params[5]);
+		tmpRoom->scheme = flib_strdupnull(params[6]);
+		tmpRoom->weapons = flib_strdupnull(params[7]);
+		if(tmpRoom->name && tmpRoom->owner && tmpRoom->map && tmpRoom->scheme && tmpRoom->weapons) {
+			result = tmpRoom;
+			tmpRoom = NULL;
+		}
+	}
+	flib_room_destroy(tmpRoom);
+	return result;
+}
+
+int fillRoomArray(flib_room **array, char **params, int count) {
+	for(int i=0; i<count; i++) {
+		array[i] = flib_room_from_netmsg(params + 8*i);
+		if(!array[i]) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+flib_room **flib_room_array_from_netmsg(char **params, int count) {
+	flib_room **result = flib_calloc(count, sizeof(flib_room*));
+	if(result) {
+		if(fillRoomArray(result, params, count)) {
+			for(int i=0; i<count; i++) {
+				flib_room_destroy(result[i]);
+			}
+			free(result);
+			result = NULL;
+		}
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/net/netprotocol.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,66 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef NETPROTOCOL_H_
+#define NETPROTOCOL_H_
+
+#include "../model/team.h"
+#include "../model/scheme.h"
+#include "../model/map.h"
+#include "../model/room.h"
+
+#include <stddef.h>
+
+/**
+ * Create a new team from this 23-part net message
+ */
+flib_team *flib_team_from_netmsg(char **parts);
+
+/**
+ * Create a new scheme from this net message, which must have
+ * meta->modCount+meta->settingCount+1 parts.
+ */
+flib_scheme *flib_scheme_from_netmsg(char **parts);
+
+/**
+ * Create a new map from this five-part netmsg
+ */
+flib_map *flib_map_from_netmsg(char **parts);
+
+/**
+ * Decode the drawn map data from this netmessage line.
+ *
+ * The data is first base64 decoded and then quncompress()ed.
+ * The return value is a newly allocated byte buffer, the length
+ * is written to the variable pointed to by outlen.
+ * Returns NULL on error.
+ */
+int flib_drawnmapdata_from_netmsg(char *netmsg, uint8_t **outbuf, size_t *outlen);
+
+/**
+ * Create a new room from this 8-part net message
+ */
+flib_room *flib_room_from_netmsg(char **params);
+
+/**
+ * Create an array of count rooms from count*8 netmessage parts
+ */
+flib_room **flib_room_array_from_netmsg(char **params, int count);
+
+#endif /* NETPROTOCOL_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/socket.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,191 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "socket.h"
+#include "util/logging.h"
+#include "util/util.h"
+#include <stdlib.h>
+#include <SDL_net.h>
+#include <time.h>
+
+struct _flib_tcpsocket {
+	TCPsocket sock;
+	SDLNet_SocketSet sockset;
+};
+
+struct _flib_acceptor {
+	TCPsocket sock;
+	uint16_t port;
+};
+
+static uint32_t getPeerIp(TCPsocket sock) {
+	IPaddress *addr = SDLNet_TCP_GetPeerAddress(sock);
+	return SDLNet_Read32(&addr->host);
+}
+
+static bool connectionIsLocal(TCPsocket sock) {
+	return getPeerIp(sock) == (uint32_t)((127UL<<24)+1); // 127.0.0.1
+}
+
+static flib_tcpsocket *createSocket(TCPsocket sdlsock) {
+	flib_tcpsocket *result = flib_calloc(1, sizeof(flib_tcpsocket));
+	if(result) {
+		result->sock = sdlsock;
+		result->sockset = SDLNet_AllocSocketSet(1);
+
+		if(!result->sockset) {
+			flib_log_e("Can't allocate socket: Out of memory!");
+			SDLNet_FreeSocketSet(result->sockset);
+			free(result);
+			result = NULL;
+		} else {
+			SDLNet_AddSocket(result->sockset, (SDLNet_GenericSocket)result->sock);
+		}
+	}
+	return result;
+}
+
+TCPsocket listen(uint16_t port) {
+	IPaddress addr;
+	addr.host = INADDR_ANY;
+	SDLNet_Write16(port, &addr.port);
+	TCPsocket sock = SDLNet_TCP_Open(&addr);
+	if(!sock) {
+		flib_log_w("Unable to listen on port %u: %s", (unsigned)port, SDLNet_GetError());
+	}
+	return sock;
+}
+
+flib_acceptor *flib_acceptor_create(uint16_t port) {
+	flib_acceptor *result = flib_calloc(1, sizeof(flib_acceptor));
+	if(result) {
+		if(port > 0) {
+			result->port = port;
+			result->sock = listen(result->port);
+		} else {
+			/* SDL_net does not seem to have a way to listen on a random unused port
+			   and find out which port that is, so let's try to find one ourselves. */
+			srand(time(NULL));
+			for(int i=0; !result->sock && i<1000; i++) {
+				// IANA suggests using ports in the range 49152-65535 for things like this
+				result->port = 49152+(rand()%(65535-49152));
+				result->sock = listen(result->port);
+			}
+		}
+		if(!result->sock) {
+			flib_log_e("Failed to create acceptor.");
+			free(result);
+			result = NULL;
+		}
+	}
+	return result;
+}
+
+uint16_t flib_acceptor_listenport(flib_acceptor *acceptor) {
+	if(!acceptor) {
+		flib_log_e("Call to flib_acceptor_listenport with acceptor==null");
+		return 0;
+	}
+	return acceptor->port;
+}
+
+void flib_acceptor_close(flib_acceptor *acceptor) {
+	if(acceptor) {
+		SDLNet_TCP_Close(acceptor->sock);
+		free(acceptor);
+	}
+}
+
+flib_tcpsocket *flib_socket_accept(flib_acceptor *acceptor, bool localOnly) {
+	flib_tcpsocket *result = NULL;
+	if(!acceptor) {
+		flib_log_e("Call to flib_socket_accept with acceptor==null");
+	} else {
+		TCPsocket sock = NULL;
+		while(!result && (sock = SDLNet_TCP_Accept(acceptor->sock))) {
+			if(localOnly && !connectionIsLocal(sock)) {
+				flib_log_i("Rejected nonlocal connection attempt from %s", flib_format_ip(getPeerIp(sock)));
+			} else {
+				result = createSocket(sock);
+			}
+			if(!result) {
+				SDLNet_TCP_Close(sock);
+			}
+		}
+	}
+	return result;
+}
+
+flib_tcpsocket *flib_socket_connect(const char *host, uint16_t port) {
+	flib_tcpsocket *result = NULL;
+	if(!host || port==0) {
+		flib_log_e("Invalid parameter in flib_socket_connect");
+	} else {
+		IPaddress ip;
+		if(SDLNet_ResolveHost(&ip,host,port)==-1) {
+		   flib_log_e("SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+		} else {
+			TCPsocket sock=SDLNet_TCP_Open(&ip);
+			if(!sock) {
+				flib_log_e("SDLNet_TCP_Open: %s\n", SDLNet_GetError());
+			} else {
+				result = createSocket(sock);
+				if(result) {
+					sock = NULL;
+				}
+			}
+			SDLNet_TCP_Close(sock);
+		}
+	}
+	return result;
+}
+
+void flib_socket_close(flib_tcpsocket *sock) {
+	if(sock) {
+		SDLNet_DelSocket(sock->sockset, (SDLNet_GenericSocket)sock->sock);
+		SDLNet_TCP_Close(sock->sock);
+		SDLNet_FreeSocketSet(sock->sockset);
+		free(sock);
+	}
+}
+
+int flib_socket_nbrecv(flib_tcpsocket *sock, void *data, int maxlen) {
+	if(!sock || (maxlen>0 && !data)) {
+		flib_log_e("Call to flib_socket_nbrecv with sock==null or data==null");
+		return -1;
+	}
+	int readySockets = SDLNet_CheckSockets(sock->sockset, 0);
+	if(readySockets>0) {
+		int size = SDLNet_TCP_Recv(sock->sock, data, maxlen);
+		return size>0 ? size : -1;
+	} else if(readySockets==0) {
+		return 0;
+	} else {
+		flib_log_e("Error in select system call: %s", SDLNet_GetError());
+		return -1;
+	}
+}
+
+int flib_socket_send(flib_tcpsocket *sock, const void *data, int len) {
+	if(!sock || (len>0 && !data)) {
+		flib_log_e("Call to flib_socket_send with sock==null or data==null");
+		return -1;
+	}
+	return SDLNet_TCP_Send(sock->sock, data, len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/socket.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,91 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * Sockets for TCP networking.
+ *
+ * This layer offers some functionality over what SDL_net offers directly: listening
+ * sockets (called acceptors here) can be bound to port 0, which will make them listen
+ * on a random unused port, if one can be found. To support this feature, you can also
+ * query the local port that an acceptor is listening on.
+ *
+ * Further, we support nonblocking reads here.
+ */
+
+#ifndef SOCKET_H_
+#define SOCKET_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct _flib_tcpsocket flib_tcpsocket;
+typedef struct _flib_acceptor flib_acceptor;
+
+/**
+ * Create a new acceptor which will listen for incoming TCP connections
+ * on the given port. If port is 0, this will listen on a random
+ * unused port which can then be queried with flib_acceptor_listenport.
+ *
+ * Returns NULL on error.
+ */
+flib_acceptor *flib_acceptor_create(uint16_t port);
+
+/**
+ * Return the port on which the acceptor is listening.
+ */
+uint16_t flib_acceptor_listenport(flib_acceptor *acceptor);
+
+/**
+ * Close the acceptor and free its memory. NULL-safe.
+ */
+void flib_acceptor_close(flib_acceptor *acceptor);
+
+/**
+ * Try to accept a connection from an acceptor (listening socket).
+ * if localOnly is true, this will only accept connections which came from 127.0.0.1
+ * Returns NULL if nothing can be accepted.
+ */
+flib_tcpsocket *flib_socket_accept(flib_acceptor *acceptor, bool localOnly);
+
+/**
+ * Try to connect to the server at the given address.
+ */
+flib_tcpsocket *flib_socket_connect(const char *host, uint16_t port);
+
+/**
+ * Close the socket and free its memory. NULL-safe.
+ */
+void flib_socket_close(flib_tcpsocket *socket);
+
+/**
+ * Attempt to receive up to maxlen bytes from the socket, but does not
+ * block if nothing is available.
+ * Returns the ammount of data received, 0 if there was nothing to receive,
+ * or a negative number if the connection was closed or an error occurred.
+ */
+int flib_socket_nbrecv(flib_tcpsocket *sock, void *data, int maxlen);
+
+/**
+ * Blocking send all the data in the data buffer. Returns the actual ammount
+ * of data sent, or a negative value on error. If the value returned here
+ * is less than len, either the connection closed or an error occurred.
+ */
+int flib_socket_send(flib_tcpsocket *sock, const void *data, int len);
+
+#endif /* SOCKET_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/buffer.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,177 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "buffer.h"
+#include "logging.h"
+#include "util.h"
+
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+
+#define MIN_VECTOR_CAPACITY 16
+
+struct _flib_vector {
+	void *data;
+	size_t size;
+	size_t capacity;
+};
+
+flib_vector *flib_vector_create() {
+	flib_vector *result = NULL;
+	flib_vector *tmpVector = flib_calloc(1, sizeof(flib_vector));
+	if(tmpVector) {
+		tmpVector->data = flib_malloc(MIN_VECTOR_CAPACITY);
+		if(tmpVector->data) {
+			tmpVector->size = 0;
+			tmpVector->capacity = MIN_VECTOR_CAPACITY;
+			result = tmpVector;
+			tmpVector = NULL;
+		}
+	}
+	flib_vector_destroy(tmpVector);
+	return result;
+}
+
+void flib_vector_destroy(flib_vector *vec) {
+	if(vec) {
+		free(vec->data);
+		free(vec);
+	}
+}
+
+static int setCapacity(flib_vector *vec, size_t newCapacity) {
+	if(newCapacity == vec->capacity) {
+		return 0;
+	}
+	void *newData = realloc(vec->data, newCapacity);
+	if(newData) {
+		vec->data = newData;
+		vec->capacity = newCapacity;
+		return 0;
+	} else {
+		return -1;
+	}
+}
+
+static int allocateExtraCapacity(flib_vector *vec, size_t extraCapacity) {
+	if(extraCapacity <= SIZE_MAX - vec->capacity) {
+		return setCapacity(vec, vec->capacity + extraCapacity);
+	} else {
+		return -1;
+	}
+}
+
+int flib_vector_resize(flib_vector *vec, size_t newSize) {
+	if(log_badargs_if(vec==NULL)) {
+		return -1;
+	}
+
+	if(vec->capacity < newSize) {
+		// Resize exponentially for constant amortized time,
+		// But at least by as much as we need of course
+		size_t extraCapacity = (vec->capacity)/2;
+		size_t minExtraCapacity = newSize - vec->capacity;
+		if(extraCapacity < minExtraCapacity) {
+			extraCapacity = minExtraCapacity;
+		}
+
+		if(allocateExtraCapacity(vec, extraCapacity)) {
+			allocateExtraCapacity(vec, minExtraCapacity);
+		}
+	} else if(vec->capacity/2 > newSize) {
+		size_t newCapacity = newSize+newSize/4;
+		if(newCapacity < MIN_VECTOR_CAPACITY) {
+			newCapacity = MIN_VECTOR_CAPACITY;
+		}
+		setCapacity(vec, newCapacity);
+	}
+
+	if(vec->capacity >= newSize) {
+		vec->size = newSize;
+		return 0;
+	} else {
+		return -1;
+	}
+}
+
+int flib_vector_append(flib_vector *vec, const void *data, size_t len) {
+	if(!log_badargs_if2(vec==NULL, data==NULL && len>0)
+			&& !log_oom_if(len > SIZE_MAX-vec->size)) {
+		size_t oldSize = vec->size;
+		if(!log_oom_if(flib_vector_resize(vec, vec->size+len))) {
+			memmove(((uint8_t*)vec->data) + oldSize, data, len);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int flib_vector_appendf(flib_vector *vec, const char *fmt, ...) {
+	int result = -1;
+	if(!log_badargs_if2(vec==NULL, fmt==NULL)) {
+		va_list argp;
+		va_start(argp, fmt);
+		char *formatted = flib_vasprintf(fmt, argp);
+		va_end(argp);
+
+
+		if(formatted) {
+			size_t len = strlen(formatted);
+			result = flib_vector_append(vec, formatted, len);
+		}
+	}
+	return result;
+}
+
+flib_buffer flib_vector_as_buffer(flib_vector *vec) {
+	if(log_badargs_if(vec==NULL)) {
+		flib_buffer result = {NULL, 0};
+		return result;
+	} else {
+		flib_buffer result = {vec->data, vec->size};
+		return result;
+	}
+}
+
+flib_constbuffer flib_vector_as_constbuffer(flib_vector *vec) {
+	if(log_badargs_if(vec==NULL)) {
+		flib_constbuffer result = {NULL, 0};
+		return result;
+	} else {
+		flib_constbuffer result = {vec->data, vec->size};
+		return result;
+	}
+}
+
+void *flib_vector_data(flib_vector *vec) {
+	if(log_badargs_if(vec==NULL)) {
+		return NULL;
+	} else {
+		return vec->data;
+	}
+}
+
+size_t flib_vector_size(flib_vector *vec) {
+	if(log_badargs_if(vec==NULL)) {
+		return 0;
+	} else {
+		return vec->size;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/buffer.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,104 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef BUFFER_H_
+#define BUFFER_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+/**
+ * A simple struct to hold both the pointer to an array and its size,
+ * for e.g. conveniently returning it from a function.
+ *
+ * Convention: Size is zero iff data is a NULL pointer.
+ */
+typedef struct {
+	void *data;
+	size_t size;
+} flib_buffer;
+
+/**
+ * Just like flib_buffer, but the contents are not supposed to be modified.
+ */
+typedef struct {
+	const void *data;
+	size_t size;
+} flib_constbuffer;
+
+/**
+ * Simple variable-capacity data structure that can be efficiently appended to.
+ */
+typedef struct _flib_vector flib_vector;
+
+/**
+ * Create a new vector. Needs to be destroyed again later with flib_vector_destroy.
+ * May return NULL if memory runs out.
+ */
+flib_vector *flib_vector_create();
+
+/**
+ * Free the memory of this vector
+ */
+void flib_vector_destroy(flib_vector *vec);
+
+/**
+ * Resize the vector. This changes the size, and ensures the capacity is large enough to
+ * for the new size. Can also free memory if the new size is smaller. There is no guarantee
+ * about the contents of extra memory.
+ */
+int flib_vector_resize(flib_vector *vec, size_t newSize);
+
+/**
+ * Append the provided data to the end of the vector, enlarging it as required.
+ * The vector remains unchanged if appending fails.
+ * Returns 0 on success.
+ */
+int flib_vector_append(flib_vector *vec, const void *data, size_t len);
+
+/**
+ * Append data from a format string to the buffer (without trailing 0)
+ * Returns 0 on success.
+ */
+int flib_vector_appendf(flib_vector *vec, const char *template, ...);
+
+/**
+ * Return a pointer to the current data buffer of the vector. This pointer can
+ * become invalid if the vector size or capacity is changed.
+ */
+void *flib_vector_data(flib_vector *vec);
+
+/**
+ * Return the current size of the vector.
+ */
+size_t flib_vector_size(flib_vector *vec);
+
+/**
+ * Return a buffer pointing to the current contents of the vector.
+ * These will become invalid if the vector size or capacity is changed.
+ */
+flib_buffer flib_vector_as_buffer(flib_vector *vec);
+
+/**
+ * Return a constbuffer pointing to the current contents of the vector.
+ * These will become invalid if the vector size or capacity is changed.
+ */
+flib_constbuffer flib_vector_as_constbuffer(flib_vector *vec);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/inihelper.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,321 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "inihelper.h"
+#include "../iniparser/dictionary.h"
+#include "../iniparser/iniparser.h"
+
+#include "logging.h"
+#include "util.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdarg.h>
+
+struct _flib_ini {
+	dictionary *inidict;
+	char *currentSection;
+};
+
+static char *createDictKey(const char *sectionName, const char *keyName) {
+	return flib_asprintf("%s:%s", sectionName, keyName);
+}
+
+/**
+ * Turns a string into a lowercase string, in-place.
+ */
+static void strToLower(char *str) {
+	if(str) {
+		while(*str) {
+			*str = tolower(*str);
+			str++;
+		}
+	}
+}
+
+flib_ini *flib_ini_create(const char *filename) {
+	flib_ini *result = NULL;
+	flib_ini *tmpIni = flib_calloc(1, sizeof(flib_ini));
+	if(tmpIni) {
+		if(filename) {
+			tmpIni->inidict = iniparser_load(filename);
+		}
+		if(!tmpIni->inidict) {
+			tmpIni->inidict = dictionary_new(0);
+		}
+		if(tmpIni->inidict) {
+			result = tmpIni;
+			tmpIni = NULL;
+		}
+	}
+	flib_ini_destroy(tmpIni);
+	return result;
+}
+
+flib_ini *flib_ini_load(const char *filename) {
+	flib_ini *result = NULL;
+	if(!log_badargs_if(filename==NULL)) {
+		flib_ini *tmpIni = flib_calloc(1, sizeof(flib_ini));
+		if(tmpIni) {
+			tmpIni->inidict = iniparser_load(filename);
+			if(tmpIni->inidict) {
+				result = tmpIni;
+				tmpIni = NULL;
+			}
+		}
+		flib_ini_destroy(tmpIni);
+	}
+	return result;
+}
+
+int flib_ini_save(flib_ini *ini, const char *filename) {
+	int result = INI_ERROR_OTHER;
+	if(!log_badargs_if2(ini==NULL, filename==NULL)) {
+		FILE *file = fopen(filename, "wb");
+		if(!file) {
+			flib_log_e("Error opening file \"%s\" for writing.", filename);
+		} else {
+			iniparser_dump_ini(ini->inidict, file);
+			if(fclose(file)) {
+				flib_log_e("Write error on ini file \"%s\"", filename);
+			} else {
+				result = 0;
+			}
+		}
+	}
+	return result;
+}
+
+void flib_ini_destroy(flib_ini *ini) {
+	if(ini) {
+		if(ini->inidict) {
+			iniparser_freedict(ini->inidict);
+		}
+		free(ini->currentSection);
+		free(ini);
+	}
+}
+
+int flib_ini_enter_section(flib_ini *ini, const char *section) {
+	int result = INI_ERROR_OTHER;
+	if(ini) {
+		free(ini->currentSection);
+		ini->currentSection = NULL;
+	}
+	if(!log_badargs_if2(ini==NULL, section==NULL)) {
+		if(!iniparser_find_entry(ini->inidict, section)) {
+			flib_log_d("Ini section %s not found", section);
+			result = INI_ERROR_NOTFOUND;
+		} else {
+			ini->currentSection = flib_strdupnull(section);
+			if(ini->currentSection) {
+				// Usually iniparser ignores case, but some section-handling functions don't,
+				// so we set it to lowercase manually
+				strToLower(ini->currentSection);
+				result = 0;
+			}
+		}
+	}
+	return result;
+}
+
+int flib_ini_create_section(flib_ini *ini, const char *section) {
+	int result = INI_ERROR_OTHER;
+	if(!log_badargs_if2(ini==NULL, section==NULL)) {
+		result = flib_ini_enter_section(ini, section);
+		if(result == INI_ERROR_NOTFOUND) {
+			if(iniparser_set(ini->inidict, section, NULL)) {
+				flib_log_e("Error creating ini section %s", section);
+				result = INI_ERROR_OTHER;
+			} else {
+				result = flib_ini_enter_section(ini, section);
+			}
+		}
+	}
+	return result;
+}
+
+/**
+ * The result is an internal string of the iniparser, don't free it.
+ */
+static char *findValue(dictionary *dict, const char *section, const char *key) {
+	char *result = NULL;
+	char *dictKey = createDictKey(section, key);
+	if(dictKey) {
+		result = iniparser_getstring(dict, dictKey, NULL);
+	}
+	free(dictKey);
+	return result;
+}
+
+int flib_ini_get_str(flib_ini *ini, char **outVar, const char *key) {
+	char *tmpValue = NULL;
+	int result = flib_ini_get_str_opt(ini, &tmpValue, key, NULL);
+	if(result==0) {
+		if(tmpValue == NULL) {
+			result = INI_ERROR_NOTFOUND;
+		} else {
+			*outVar = tmpValue;
+			tmpValue = NULL;
+		}
+	}
+	free(tmpValue);
+	return result;
+}
+
+int flib_ini_get_str_opt(flib_ini *ini, char **outVar, const char *key, const char *def) {
+	int result = INI_ERROR_OTHER;
+	if(!log_badargs_if4(ini==NULL, ini->currentSection==NULL, outVar==NULL, key==NULL)) {
+		const char *value = findValue(ini->inidict, ini->currentSection, key);
+		if(!value) {
+			value = def;
+		}
+		char *valueDup = flib_strdupnull(value);
+		if(valueDup || !def) {
+			*outVar = valueDup;
+			result = 0;
+		}
+	}
+	return result;
+}
+
+int flib_ini_get_int(flib_ini *ini, int *outVar, const char *key) {
+	char *tmpValue = NULL;
+	int result = flib_ini_get_str(ini, &tmpValue, key);
+	if(result==0) {
+		errno = 0;
+		long val = strtol(tmpValue, NULL, 10);
+		if(errno!=0 || val<INT_MIN || val>INT_MAX) {
+			flib_log_w("Cannot parse ini setting %s/%s = \"%s\" as integer.", ini->currentSection, key, tmpValue);
+			result = INI_ERROR_FORMAT;
+		} else {
+			*outVar = val;
+		}
+	}
+	free(tmpValue);
+	return result;
+}
+
+int flib_ini_get_int_opt(flib_ini *ini, int *outVar, const char *key, int def) {
+	int tmpValue;
+	int result = flib_ini_get_int(ini, &tmpValue, key);
+	if(result == 0) {
+		*outVar = tmpValue;
+	} else if(result == INI_ERROR_NOTFOUND || result == INI_ERROR_FORMAT) {
+		*outVar = def;
+		result = 0;
+	}
+	return result;
+}
+
+int flib_ini_get_bool(flib_ini *ini, bool *outVar, const char *key) {
+	char *tmpValue = NULL;
+	int result = flib_ini_get_str(ini, &tmpValue, key);
+	if(result==0) {
+		bool trueval = strchr("1tTyY", tmpValue[0]);
+		bool falseval = strchr("0fFnN", tmpValue[0]);
+		if(!trueval && !falseval) {
+			flib_log_w("ini setting %s/%s = \"%s\" is not a recognized truth value.", ini->currentSection, key, tmpValue);
+			result = INI_ERROR_FORMAT;
+		} else {
+			*outVar = trueval;
+		}
+	}
+	free(tmpValue);
+	return result;
+}
+
+int flib_ini_get_bool_opt(flib_ini *ini, bool *outVar, const char *key, bool def) {
+	bool tmpValue;
+	int result = flib_ini_get_bool(ini, &tmpValue, key);
+	if(result == 0) {
+		*outVar = tmpValue;
+	} else if(result == INI_ERROR_NOTFOUND || result == INI_ERROR_FORMAT) {
+		*outVar = def;
+		result = 0;
+	}
+	return result;
+}
+
+int flib_ini_set_str(flib_ini *ini, const char *key, const char *value) {
+	int result = INI_ERROR_OTHER;
+	if(!log_badargs_if4(ini==NULL, ini->currentSection==NULL, key==NULL, value==NULL)) {
+		char *dictKey = createDictKey(ini->currentSection, key);
+		if(dictKey) {
+			result = iniparser_set(ini->inidict, dictKey, value);
+			if(result) {
+				flib_log_e("Error setting ini entry %s to %s", dictKey, value);
+			}
+		}
+		free(dictKey);
+	}
+	return result;
+}
+
+int flib_ini_set_int(flib_ini *ini, const char *key, int value) {
+	int result = INI_ERROR_OTHER;
+	char *strvalue = flib_asprintf("%i", value);
+	if(strvalue) {
+		result = flib_ini_set_str(ini, key, strvalue);
+	}
+	free(strvalue);
+	return result;
+}
+
+int flib_ini_set_bool(flib_ini *ini, const char *key, bool value) {
+	return flib_ini_set_str(ini, key, value ? "true" : "false");
+}
+
+int flib_ini_get_sectioncount(flib_ini *ini) {
+	if(!log_badargs_if(ini==NULL)) {
+		return iniparser_getnsec(ini->inidict);
+	}
+	return INI_ERROR_OTHER;
+}
+
+char *flib_ini_get_sectionname(flib_ini *ini, int number) {
+	if(!log_badargs_if2(ini==NULL, number<0)) {
+		return flib_strdupnull(iniparser_getsecname(ini->inidict, number));
+	}
+	return NULL;
+}
+
+int flib_ini_get_keycount(flib_ini *ini) {
+	if(!log_badargs_if2(ini==NULL, ini->currentSection==NULL)) {
+		return iniparser_getsecnkeys(ini->inidict, ini->currentSection);
+	}
+	return INI_ERROR_OTHER;
+}
+
+char *flib_ini_get_keyname(flib_ini *ini, int number) {
+	char *result = NULL;
+	if(!log_badargs_if3(ini==NULL, ini->currentSection==NULL, number<0)) {
+		int keyCount = iniparser_getsecnkeys(ini->inidict, ini->currentSection);
+		char **keys = iniparser_getseckeys(ini->inidict, ini->currentSection);
+		if(keys && keyCount>number) {
+			// The keys are in the format section:key, so we have to skip the section and colon.
+			result = flib_strdupnull(keys[number]+strlen(ini->currentSection)+1);
+		}
+		free(keys);
+	}
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/inihelper.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,178 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Convenience interface for ini reading/writing.
+ *
+ * We currently use iniparser in the background, but using its interface directly is a bit verbose.
+ * This module is supposed to 1. make ini reading and writing a bit more convenient, and 2. hide
+ * the iniparser dependency so it can at need be easily replaced.
+ */
+
+#ifndef INIHELPER_H_
+#define INIHELPER_H_
+
+#include <stdbool.h>
+
+#define INI_ERROR_NOTFOUND -1
+#define INI_ERROR_FORMAT -2
+#define INI_ERROR_OTHER -100
+
+typedef struct _flib_ini flib_ini;
+
+/**
+ * Create a new ini data structure, pre-filled with the contents of
+ * the file "filename" if it exists. If filename is null, or the file
+ * is not found, an empty ini will be created. However, if an error
+ * occurs while reading the ini file (or any other error), null
+ * is returned.
+ *
+ * This behavior is useful for modifying an existing ini file without
+ * discarding unknown keys.
+ */
+flib_ini *flib_ini_create(const char *filename);
+
+/**
+ * Similar to flib_ini_create, but fails if the file is not found
+ * or if filename is null.
+ */
+flib_ini *flib_ini_load(const char *filename);
+
+/**
+ * Store the ini to the file "filename", overwriting
+ * the previous contents. Returns 0 on success.
+ */
+int flib_ini_save(flib_ini *ini, const char *filename);
+
+void flib_ini_destroy(flib_ini *ini);
+
+/**
+ * Enter the section with the specified name. Returns 0 on
+ * success, INI_ERROR_NOTFOUND if the section does not exist
+ * and a different value if another error occurs.
+ * If an error occurs, there is no current section.
+ *
+ * The section name should only consist of letters and
+ * numbers.
+ */
+int flib_ini_enter_section(flib_ini *ini, const char *section);
+
+/**
+ * Creates and enters the section with the specified name. Simply
+ * enters the section if it exists already. Returns 0 on success
+ * and a different value if another error occurs.
+ * If an error occurs, there is no current section.
+ */
+int flib_ini_create_section(flib_ini *ini, const char *section);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as a newly allocated string. Returns 0 on success, INI_ERROR_NOTFOUND
+ * if the key was not found and a different value for other errors,
+ * e.g. if there is no current section.
+ */
+int flib_ini_get_str(flib_ini *ini, char **outVar, const char *key);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as a newly allocated string. If the key is not found, the default
+ * value will be used instead. Returns 0 on success.
+ */
+int flib_ini_get_str_opt(flib_ini *ini, char **outVar, const char *key, const char *def);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as an int. Returns 0 on success, INI_ERROR_NOTFOUND
+ * if the key was not found, INI_ERROR_FORMAT if it was found but
+ * could not be converted to an int, and a different value for other
+ * errors, e.g. if there is no current section.
+ */
+int flib_ini_get_int(flib_ini *ini, int *outVar, const char *key);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as an int. If the key is not found, the default value will be used instead.
+ * Returns 0 on success, INI_ERROR_FORMAT if the value was found but
+ * could not be converted to int, and another value otherwise.
+ */
+int flib_ini_get_int_opt(flib_ini *ini, int *outVar, const char *key, int def);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as a bool. Treats everything beginning with "Y", "T" or "1" as true,
+ * everything starting with "N", "F" or "1" as false.
+ *
+ * Returns 0 on success, INI_ERROR_NOTFOUND if the key was not found,
+ * INI_ERROR_FORMAT if the value could not be interpreted as boolean,
+ * and another value otherwise.
+ */
+int flib_ini_get_bool(flib_ini *ini, bool *outVar, const char *key);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as a bool. If the key is not found, the default value will be
+ * used instead. Returns 0 on success, INI_ERROR_FORMAT if the
+ * value could not be interpreted as boolean, and another value otherwise.
+ */
+int flib_ini_get_bool_opt(flib_ini *ini, bool *outVar, const char *key, bool def);
+
+/**
+ * In the current section, associate key with value. Returns 0 on success.
+ */
+int flib_ini_set_str(flib_ini *ini, const char *key, const char *value);
+
+/**
+ * In the current section, associate key with value. Returns 0 on success.
+ */
+int flib_ini_set_int(flib_ini *ini, const char *key, int value);
+
+/**
+ * In the current section, associate key with value. Returns 0 on success.
+ */
+int flib_ini_set_bool(flib_ini *ini, const char *key, bool value);
+
+/**
+ * Returns the number of sections in the ini file, or a negative value on error.
+ */
+int flib_ini_get_sectioncount(flib_ini *ini);
+
+/**
+ * Returns the name of the section, or NULL on error. The returned string must
+ * be free()d.
+ *
+ * Note: There is no guarantee that the order of the sections
+ * will remain stable if the ini is modified.
+ */
+char *flib_ini_get_sectionname(flib_ini *ini, int number);
+
+/**
+ * Returns the number of keys in the current section, or -1 on error.
+ */
+int flib_ini_get_keycount(flib_ini *ini);
+
+/**
+ * Returns the name of the key in the current section, or NULL on error.
+ * The returned string must be free()d.
+ *
+ * Note: There is no guarantee that the order of the keys in a section
+ * will remain stable if the ini is modified.
+ */
+char *flib_ini_get_keyname(flib_ini *ini, int number);
+
+#endif /* INIHELPER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/list.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,80 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Simple dynamic array manipulation functions.
+ */
+
+#ifndef LIST_H_
+#define LIST_H_
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include "util.h"
+#include "logging.h"
+
+/**
+ * Generate a static function that inserts a new value into a heap array of the given type,
+ * using realloc and memmove to increase the capacity and shift existing values.
+ * The function takes a pointer to the array variable and a pointer to the size variable
+ * because both can be changed by this operation (realloc / increment).
+ * The function returns 0 on success and leaves the array unchanged on error.
+ */
+#define GENERATE_STATIC_LIST_INSERT(fname, type) \
+	static int fname(type **listptr, int *listSizePtr, type element, int pos) { \
+		int result = -1; \
+		if(!log_badargs_if4(listptr==NULL, listSizePtr==NULL, pos < 0, pos > *listSizePtr)) { \
+			type *newList = flib_realloc(*listptr, ((*listSizePtr)+1)*sizeof(type)); \
+			if(newList) { \
+				memmove(newList + (pos+1), newList + pos, ((*listSizePtr)-pos)*sizeof(type)); \
+				newList[pos] = element; \
+				(*listSizePtr)++; \
+				*listptr = newList; \
+				result = 0; \
+			} \
+		} \
+		return result; \
+	}
+
+/**
+ * Generate a static function that deletes a value from a heap array of the given type,
+ * using realloc and memmove to decrease the capacity and shift existing values.
+ * The function takes a pointer to the array variable and a pointer to the size variable
+ * because both can be changed by this operation (realloc / decrement).
+ * The function returns 0 on success and leaves the array unchanged on error.
+ */
+#define GENERATE_STATIC_LIST_DELETE(fname, type) \
+	static int fname(type **listPtr, int *listSizePtr, int pos) { \
+		int result = -1; \
+		if(!log_badargs_if4(listPtr==NULL, listSizePtr==NULL, pos < 0, pos >= *listSizePtr)) { \
+			memmove((*listPtr) + pos, (*listPtr) + (pos+1), ((*listSizePtr)-(pos+1))*sizeof(type)); \
+			(*listSizePtr)--; \
+			\
+			size_t newCharSize = (*listSizePtr)*sizeof(type); \
+			type *newList = flib_realloc((*listPtr), newCharSize); \
+			if(newList || newCharSize==0) { \
+				(*listPtr) = newList; \
+			} /* If the realloc fails, just keep using the old buffer...*/ \
+			result = 0; \
+		} \
+		return result; \
+	}
+
+#endif /* LIST_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/logging.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,145 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "logging.h"
+
+#include <time.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+static int flib_loglevel = FLIB_LOGLEVEL_INFO;
+static FILE *flib_logfile = NULL;
+void (*flib_logCallback)(int level, const char *msg) = NULL;
+
+char* flib_format_ip(uint32_t numip) {
+	static char ip[16];
+	snprintf(ip, 16, "%u.%u.%u.%u", (unsigned)(numip>>24), (unsigned)((numip>>16)&0xff), (unsigned)((numip>>8)&0xff), (unsigned)(numip&0xff));
+	return ip;
+}
+
+static inline FILE *flib_log_getfile() {
+	if(flib_logfile==NULL) {
+		return stdout;
+	} else {
+		return flib_logfile;
+	}
+}
+
+static int log_time(char *buffer) {
+    time_t timer;
+    struct tm* tm_info;
+
+    time(&timer);
+    tm_info = localtime(&timer);
+
+    return strftime(buffer, 25, "%Y-%m-%d %H:%M:%S", tm_info);
+}
+
+static char getPrefix(int level) {
+	switch(level) {
+	case FLIB_LOGLEVEL_ERROR: return 'E';
+	case FLIB_LOGLEVEL_WARNING: return 'W';
+	case FLIB_LOGLEVEL_INFO: return 'I';
+	case FLIB_LOGLEVEL_DEBUG: return 'D';
+	default: return '?';
+	}
+}
+
+static void _flib_vflog(const char *func, int level, const char *fmt, va_list args) {
+	if(level >= flib_loglevel) {
+		char logbuffer[1024];
+		logbuffer[0] = getPrefix(level);
+		logbuffer[1] = ' ';
+
+		int pos = 2;
+
+		int len = log_time(logbuffer+pos);
+		if(len>=0) {
+			pos += len;
+			if(pos>sizeof(logbuffer)-1) pos = sizeof(logbuffer)-1;
+		} else {
+			return;
+		}
+
+		len = snprintf(logbuffer+pos, sizeof(logbuffer)-pos, " [%-30s] ", func);
+		if(len>=0) {
+			pos += len;
+			if(pos>sizeof(logbuffer)-1) pos = sizeof(logbuffer)-1;
+		} else {
+			return;
+		}
+
+		len = vsnprintf(logbuffer+pos, sizeof(logbuffer)-pos, fmt, args);
+		if(len>=0) {
+			pos += len;
+			if(pos>sizeof(logbuffer)-1) pos = sizeof(logbuffer)-1;
+		} else {
+			return;
+		}
+
+		if(flib_logCallback != NULL) {
+			flib_logCallback(level, logbuffer);
+		} else {
+			FILE *logfile = flib_log_getfile();
+			fputs(logbuffer, logfile);
+			fputc('\n', logfile);
+			fflush(logfile);
+		}
+	}
+}
+
+void _flib_flog(const char *func, int level, const char *fmt, ...) {
+	va_list argp;
+	va_start(argp, fmt);
+	_flib_vflog(func, level, fmt, argp);
+	va_end(argp);
+}
+
+bool _flib_fassert(const char *func, int level, bool cond, const char *fmt, ...) {
+	if(!cond) {
+		va_list argp;
+		va_start(argp, fmt);
+		_flib_vflog(func, level, fmt, argp);
+		va_end(argp);
+	}
+	return !cond;
+}
+
+int flib_log_getLevel() {
+	return flib_loglevel;
+}
+
+void flib_log_setLevel(int level) {
+	flib_loglevel = level;
+}
+
+void flib_log_setFile(FILE *file) {
+	flib_logfile = file;
+	flib_logCallback = NULL;
+}
+
+bool flib_log_isActive(int level) {
+	return level >= flib_log_getLevel();
+}
+
+void flib_log_setCallback(void (*logCallback)(int level, const char *msg)) {
+	flib_logCallback = logCallback;
+	flib_logfile = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/logging.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,106 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef LOGGING_H_
+#define LOGGING_H_
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#define FLIB_LOGLEVEL_ALL -100
+#define FLIB_LOGLEVEL_DEBUG -1
+#define FLIB_LOGLEVEL_INFO 0
+#define FLIB_LOGLEVEL_WARNING 1
+#define FLIB_LOGLEVEL_ERROR 2
+#define FLIB_LOGLEVEL_NONE 100
+
+/**
+ * Returns a pointer to a static buffer, don't free or store.
+ */
+char* flib_format_ip(uint32_t numip);
+
+/**
+ * Evaluates the expression cond. If it is true, a formatted error will be logged.
+ * Returns true if an error is logged, false otherwise (i.e. the boolean value of the argument)
+ * Usage: log_e_if(errorHasHappened, "Format string", formatArg, ...);
+ */
+#define log_e_if(cond, ...) _flib_fassert(__func__, FLIB_LOGLEVEL_ERROR, !(bool)(cond), __VA_ARGS__)
+#define log_w_if(cond, ...) _flib_fassert(__func__, FLIB_LOGLEVEL_WARNING, !(bool)(cond), __VA_ARGS__)
+
+/**
+ * Helper macros for log_badargs_if
+ * The t parameters are the textual representation of the c parameters. They need to be passed
+ * explicitly, to prevent them from being expanded in prescan.
+ */
+#define _flib_lbi(c1,t1) log_e_if(c1, "Invalid Argument (%s)", t1)
+#define _flib_lbi2(c1,t1,c2,t2) (_flib_lbi(c1,t1) || _flib_lbi(c2,t2))
+#define _flib_lbi3(c1,t1,c2,t2,c3,t3) (_flib_lbi(c1,t1) || _flib_lbi2(c2,t2,c3,t3))
+#define _flib_lbi4(c1,t1,c2,t2,c3,t3,c4,t4) (_flib_lbi(c1,t1) || _flib_lbi3(c2,t2,c3,t3,c4,t4))
+#define _flib_lbi5(c1,t1,c2,t2,c3,t3,c4,t4,c5,t5) (_flib_lbi(c1,t1) || _flib_lbi4(c2,t2,c3,t3,c4,t4,c5,t5))
+#define _flib_lbi6(c1,t1,c2,t2,c3,t3,c4,t4,c5,t5,c6,t6) (_flib_lbi(c1,t1) || _flib_lbi5(c2,t2,c3,t3,c4,t4,c5,t5,c6,t6))
+
+/**
+ * These macros log an "Invalid Argument" error for the first of their arguments that evaluates to true.
+ * The text of the argument is included in the log message.
+ * The expression returns true if any of its arguments is true (i.e. if an argument error was logged).
+ *
+ * For example, log_badargs_if(x==NULL) will log "Invalid Argument (x==NULL)" and return true if x is NULL.
+ */
+#define log_badargs_if(c1) _flib_lbi(c1,#c1)
+#define log_badargs_if2(c1, c2) _flib_lbi2(c1,#c1,c2,#c2)
+#define log_badargs_if3(c1, c2, c3) _flib_lbi3(c1,#c1,c2,#c2,c3,#c3)
+#define log_badargs_if4(c1, c2, c3, c4) _flib_lbi4(c1,#c1,c2,#c2,c3,#c3,c4,#c4)
+#define log_badargs_if5(c1, c2, c3, c4, c5) _flib_lbi5(c1,#c1,c2,#c2,c3,#c3,c4,#c4,c5,#c5)
+#define log_badargs_if6(c1, c2, c3, c4, c5, c6) _flib_lbi6(c1,#c1,c2,#c2,c3,#c3,c4,#c4,c5,#c5,c6,#c6)
+
+#define log_oom_if(cond) log_e_if(cond, "Out of Memory")
+
+#define flib_log_e(...) _flib_flog(__func__, FLIB_LOGLEVEL_ERROR, __VA_ARGS__)
+#define flib_log_w(...) _flib_flog(__func__, FLIB_LOGLEVEL_WARNING, __VA_ARGS__)
+#define flib_log_i(...) _flib_flog(__func__, FLIB_LOGLEVEL_INFO, __VA_ARGS__)
+#define flib_log_d(...) _flib_flog(__func__, FLIB_LOGLEVEL_DEBUG, __VA_ARGS__)
+
+bool _flib_fassert(const char *func, int level, bool cond, const char *fmt, ...);
+void _flib_flog(const char *func, int level, const char *fmt, ...);
+
+/**
+ * Only log messages that are at least the indicated level
+ */
+void flib_log_setLevel(int level);
+int flib_log_getLevel();
+
+/**
+ * Log to the indicated file. You can pass NULL to log to stdout.
+ * This overrides setCallback and vice versa.
+ */
+void flib_log_setFile(FILE *logfile);
+
+/**
+ * Returns whether messages of this level are logged at the moment.
+ */
+bool flib_log_isActive(int level);
+
+/**
+ * Allows logging through an arbitrary callback function. Useful for integrating into an
+ * existing logging system. This overrides setFile and vice versa.
+ */
+void flib_log_setCallback(void (*logCallback)(int level, const char *msg));
+
+#endif /* LOGGING_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/util.c	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,212 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "util.h"
+#include "logging.h"
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <limits.h>
+
+char *flib_asprintf(const char *fmt, ...) {
+	va_list argp;
+	va_start(argp, fmt);
+	char *result = flib_vasprintf(fmt, argp);
+	va_end(argp);
+	return result;
+}
+
+char *flib_vasprintf(const char *fmt, va_list args) {
+	char *result = NULL;
+	if(!log_badargs_if(fmt==NULL)) {
+		int requiredSize = vsnprintf(NULL, 0, fmt, args)+1;					// Figure out how much memory we need,
+		if(!log_e_if(requiredSize<0, "Error formatting string with template \"%s\"", fmt)) {
+			char *tmpbuf = flib_malloc(requiredSize);						// allocate it
+			if(tmpbuf && vsnprintf(tmpbuf, requiredSize, fmt, args)>=0) {	// and then do the actual formatting.
+				result = tmpbuf;
+				tmpbuf = NULL;
+			}
+			free(tmpbuf);
+		}
+	}
+	return result;
+}
+
+char *flib_join(char **parts, int partCount, const char *delimiter) {
+	char *result = NULL;
+	if(!log_badargs_if2(parts==NULL, delimiter==NULL)) {
+		size_t totalSize = 1;
+		size_t delimLen = strlen(delimiter);
+		for(int i=0; i<partCount; i++) {
+			totalSize += strlen(parts[i]) + delimLen;
+		}
+		result = flib_malloc(totalSize);
+
+		if(result) {
+			size_t outpos = 0;
+			for(int i=0; i<partCount; i++) {
+				if(i>0) {
+					strcpy(result+outpos, delimiter);
+					outpos += delimLen;
+				}
+				strcpy(result+outpos, parts[i]);
+				outpos += strlen(parts[i]);
+			}
+		}
+	}
+	return result;
+}
+
+char *flib_strdupnull(const char *str) {
+	return str==NULL ? NULL : flib_asprintf("%s", str);
+}
+
+void *flib_bufdupnull(const void *buf, size_t size) {
+	void *result = NULL;
+	if(!log_badargs_if(buf==NULL && size>0)) {
+		result = flib_malloc(size);
+		if(result) {
+			memcpy(result, buf, size);
+		}
+	}
+	return result;
+}
+
+void *flib_malloc(size_t size) {
+	void *result = malloc(size);
+	if(!result && size>0) {
+		flib_log_e("Out of memory trying to malloc %zu bytes.", size);
+	}
+	return result;
+}
+
+void *flib_calloc(size_t count, size_t elementsize) {
+	void *result = calloc(count, elementsize);
+	if(!result && count>0 && elementsize>0) {
+		flib_log_e("Out of memory trying to calloc %zu objects of %zu bytes each.", count, elementsize);
+	}
+	return result;
+}
+
+void *flib_realloc(void *ptr, size_t size) {
+	void *result = realloc(ptr, size);
+	if(!result && size>0) {
+		flib_log_e("Out of memory trying to realloc %zu bytes.", size);
+	}
+	return result;
+}
+
+static bool isAsciiAlnum(char c) {
+	return (c>='0' && c<='9') || (c>='a' && c <='z') || (c>='A' && c <='Z');
+}
+
+char *flib_urlencode(const char *inbuf) {
+	return flib_urlencode_pred(inbuf, isAsciiAlnum);
+}
+
+static size_t countCharsToEscape(const char *inbuf, bool (*needsEscaping)(char c)) {
+	size_t result = 0;
+	for(const char *c=inbuf; *c; c++) {
+		if(needsEscaping(*c)) {
+			result++;
+		}
+	}
+	return result;
+}
+
+char *flib_urlencode_pred(const char *inbuf, bool (*needsEscaping)(char c)) {
+	char *result = NULL;
+	if(inbuf && !log_badargs_if(needsEscaping == NULL)) {
+		size_t insize = strlen(inbuf);
+		if(!log_e_if(insize > SIZE_MAX/4, "String too long: %zu bytes.", insize)) {
+			size_t escapeCount = countCharsToEscape(inbuf, needsEscaping);
+			result = flib_malloc(insize + escapeCount*2 + 1);
+		}
+		if(result) {
+			char *out = result;
+			for(const char *in = inbuf; *in; in++) {
+				if(!needsEscaping(*in)) {
+					*out = *in;
+					out++;
+				} else {
+					snprintf(out, 4, "%%%02x", (unsigned)(*(uint8_t*)in));
+					out += 3;
+				}
+			}
+			*out = 0;
+		}
+	}
+	return result;
+}
+
+char *flib_urldecode(const char *inbuf) {
+	if(!inbuf) {
+		return NULL;
+	}
+	char *outbuf = flib_malloc(strlen(inbuf)+1);
+	if(!outbuf) {
+		return NULL;
+	}
+
+    size_t inpos = 0, outpos = 0;
+    while(inbuf[inpos]) {
+        if(inbuf[inpos] == '%' && isxdigit(inbuf[inpos+1]) && isxdigit(inbuf[inpos+2])) {
+            char temp[3] = {inbuf[inpos+1],inbuf[inpos+2],0};
+            outbuf[outpos++] = strtol(temp, NULL, 16);
+            inpos += 3;
+        } else {
+        	outbuf[outpos++] = inbuf[inpos++];
+        }
+    }
+    outbuf[outpos] = 0;
+    char *shrunk = realloc(outbuf, outpos+1);
+    return shrunk ? shrunk : outbuf;
+}
+
+bool flib_contains_dir_separator(const char *str) {
+	if(!log_badargs_if(!str)) {
+		for(;*str;str++) {
+			if(*str=='\\' || *str=='/') {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+bool flib_strempty(const char *str) {
+	return !str || !*str;
+}
+
+int flib_gets(char *str, size_t strlen) {
+	if(fgets(str, strlen, stdin)) {
+		for(char *s=str; *s; s++) {
+			if(*s=='\r' || *s=='\n') {
+				*s = 0;
+				break;
+			}
+		}
+		return 0;
+	}
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/frontlib/util/util.h	Sat Aug 18 00:48:09 2012 +0200
@@ -0,0 +1,122 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef FLIB_UTIL_H_
+#define FLIB_UTIL_H_
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdbool.h>
+
+/**
+ * Prints a format string to a newly allocated buffer of the required size.
+ * Parameters are like those for printf. Returns NULL on error.
+ *
+ * Returned buffer must be free()d
+ */
+char *flib_asprintf(const char *fmt, ...);
+
+/**
+ * Exactly as flib_asprintf, but accepts a va_list.
+ */
+char *flib_vasprintf(const char *fmt, va_list args);
+
+/**
+ * Creates a new string (that must be freed) containing all parts
+ * joined together, with the specified delimiter between each.
+ */
+char *flib_join(char **parts, int partCount, const char *delimiter);
+
+/**
+ * Return a duplicate of the provided string, or NULL if an error
+ * occurs or if str is already NULL.
+ *
+ * Returned buffer must be free()d
+ */
+char *flib_strdupnull(const char *str);
+
+/**
+ * Return a duplicate of the provided buffer, or NULL if an error
+ * occurs or if buf is already NULL or if size is 0.
+ *
+ * Returned buffer must be free()d
+ */
+void *flib_bufdupnull(const void *buf, size_t size);
+
+/**
+ * Simple malloc wrapper that automatically logs an error if no memory
+ * is available. Otherwise behaves exactly like malloc.
+ */
+void *flib_malloc(size_t size);
+
+/**
+ * Simple calloc wrapper that automatically logs an error if no memory
+ * is available. Otherwise behaves exactly like calloc.
+ */
+void *flib_calloc(size_t count, size_t elementsize);
+
+/**
+ * Simple realloc wrapper that automatically logs an error if no memory
+ * is available. Otherwise behaves exactly like realloc.
+ */
+void *flib_realloc(void *ptr, size_t size);
+
+/**
+ * Replace all non-alphanumeric and non-ascii bytes with escape
+ * sequences in the form %XX. Does not modify the original string,
+ * but returns a newly allocated one that must be free()d. Returns
+ * null on failure or if null was passed as argument.
+ *
+ * This should work fine with all ASCII-based charsets including UTF-8.
+ */
+char *flib_urlencode(const char *str);
+
+/**
+ * Replace some bytes with escape sequences in the form %XX.
+ * Does not modify the original string, but returns a newly allocated
+ * one that must be free()d.
+ *
+ * All bytes for which the predicate function returns true are escaped.
+ *
+ * Returns null on failure or if null was passed as argument.
+ */
+char *flib_urlencode_pred(const char *str, bool (*needsEscaping)(char c));
+
+/**
+ * Replace escape sequences of the form %XX with their byte values.
+ * Does not modify the original string, but returns a newly allocated
+ * one that must be free()d. Returns null on failure or if null was
+ * passed as argument.
+ */
+char *flib_urldecode(const char *str);
+
+/**
+ * Figure out if the string contains / or \. Useful in routines that
+ * construct filenames.
+ */
+bool flib_contains_dir_separator(const char *str);
+
+/**
+ * Returns true if str is either NULL or points to a 0-length string
+ */
+bool flib_strempty(const char *str);
+
+int flib_gets(char *str, size_t strlen);
+
+#endif