changeset 15082:b9c35ad9b9f8 draft

(svn r19708) -Add: NewGRF "debugging" window and data tables; should be a useful tool for NewGRF developers to get some insights into the value of some variables
author rubidium <rubidium@openttd.org>
date Sat, 24 Apr 2010 13:36:29 +0000
parents 961ab6166e79
children 68d1f9ea65c3
files projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/lang/english.txt src/newgrf.h src/newgrf_debug.h src/newgrf_debug_gui.cpp src/table/newgrf_debug_data.h src/window_type.h
diffstat 9 files changed, 1050 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -1184,6 +1184,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\newgrf_debug.h"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\newgrf_engine.h"
 				>
 			</File>
@@ -1852,6 +1856,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\newgrf_debug_gui.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\newgrf_gui.cpp"
 				>
 			</File>
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -1181,6 +1181,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\newgrf_debug.h"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\newgrf_engine.h"
 				>
 			</File>
@@ -1849,6 +1853,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\newgrf_debug_gui.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\newgrf_gui.cpp"
 				>
 			</File>
--- a/source.list
+++ b/source.list
@@ -205,6 +205,7 @@
 newgrf_cargo.h
 newgrf_commons.h
 newgrf_config.h
+newgrf_debug.h
 newgrf_engine.h
 newgrf_generic.h
 newgrf_house.h
@@ -389,6 +390,7 @@
 network/network_chat_gui.cpp
 network/network_content_gui.cpp
 network/network_gui.cpp
+newgrf_debug_gui.cpp
 newgrf_gui.cpp
 news_gui.cpp
 order_gui.cpp
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -2365,6 +2365,16 @@
 STR_NEWGRF_ADD_RESCAN_FILES                                     :{BLACK}Rescan files
 STR_NEWGRF_ADD_RESCAN_FILES_TOOLTIP                             :{BLACK}Update the list of available NewGRF files
 
+# NewGRF inspect window
+STR_NEWGRF_INSPECT_CAPTION                                      :{WHITE}Inspect - {STRING5}
+STR_NEWGRF_INSPECT_PARENT_BUTTON                                :{BLACK}Parent
+STR_NEWGRF_INSPECT_PARENT_TOOLTIP                               :{BLACK}Inspect the object of the parent scope
+
+STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT                            :{STRING1} at {HEX}
+STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_RAIL_TYPE                  :Rail type
+
+STR_NEWGRF_INSPECT_QUERY_CAPTION                                :{WHITE}NewGRF variable 60+x parameter (hexadecimal)
+
 # NewGRF (self) generated warnings/errors
 STR_NEWGRF_ERROR_MSG_INFO                                       :{SILVER}{RAW_STRING}
 STR_NEWGRF_ERROR_MSG_WARNING                                    :{RED}Warning: {SILVER}{RAW_STRING}
--- a/src/newgrf.h
+++ b/src/newgrf.h
@@ -60,6 +60,11 @@
 	GSF_RAILTYPES,
 	GSF_AIRPORTTILES,
 	GSF_END,
+
+	GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope)
+	GSF_FAKE_END,             ///< End of the fake features
+
+	GSF_INVALID = 0xFF        ///< An invalid spec feature
 };
 
 static const uint32 INVALID_GRFID = 0xFFFFFFFF;
new file mode 100644
--- /dev/null
+++ b/src/newgrf_debug.h
@@ -0,0 +1,60 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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.
+ * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file newgrf_debug.h Functions/types related to NewGRF debugging. */
+
+#ifndef NEWGRF_DEBUG_H
+#define NEWGRF_DEBUG_H
+
+#include "newgrf.h"
+
+/**
+ * Can we inspect the data given a certain feature and index.
+ * The index is normally an in-game location/identifier, such
+ * as a TileIndex or an IndustryID depending on the feature
+ * we want to inspect.
+ * @param feature The feature we want to inspect.
+ * @param index   The index/identifier of the feature to inspect.
+ * @return true if there is something to show.
+ */
+bool IsNewGRFInspectable(GrfSpecFeature feature, uint index);
+
+/**
+ * Show the inspect window for a given feature and index.
+ * The index is normally an in-game location/identifier, such
+ * as a TileIndex or an IndustryID depending on the feature
+ * we want to inspect.
+ * @param feature The feature we want to inspect.
+ * @param index   The index/identifier of the feature to inspect.
+ */
+void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index);
+
+/**
+ * Delete inspect window for a given feature and index.
+ * The index is normally an in-game location/identifier, such
+ * as a TileIndex or an IndustryID depending on the feature
+ * we want to inspect.
+ * @param feature The feature we want to delete the window for.
+ * @param index   The index/identifier of the feature to delete.
+ */
+void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index);
+
+/**
+ * Get the GrfSpecFeature associated with the tile.
+ * @return the GrfSpecFeature.
+ */
+GrfSpecFeature GetGrfSpecFeature(TileIndex tile);
+
+/**
+ * Get the GrfSpecFeature associated with the vehicle.
+ * @return the GrfSpecFeature.
+ */
+GrfSpecFeature GetGrfSpecFeature(VehicleType type);
+
+#endif /* NEWGRF_DEBUG_H */
new file mode 100644
--- /dev/null
+++ b/src/newgrf_debug_gui.cpp
@@ -0,0 +1,530 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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.
+ * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
+
+#include "stdafx.h"
+#include <stdarg.h>
+#include "window_gui.h"
+#include "window_func.h"
+#include "gfx_func.h"
+#include "string_func.h"
+#include "strings_func.h"
+#include "textbuf_gui.h"
+#include "tunnelbridge_map.h"
+
+#include "engine_base.h"
+#include "house.h"
+#include "industry.h"
+#include "rail.h"
+#include "station_base.h"
+#include "town.h"
+#include "vehicle_base.h"
+
+#include "newgrf_airporttiles.h"
+#include "newgrf_debug.h"
+#include "newgrf_spritegroup.h"
+#include "newgrf_station.h"
+#include "newgrf_town.h"
+
+#include "table/strings.h"
+
+/**
+ * Get the feature index related to the window number.
+ * @param window_number The window to get the feature index from.
+ * @return the feature index
+ */
+static inline uint GetFeatureIndex(uint window_number)
+{
+	return GB(window_number, 0, 24);
+}
+
+/**
+ * Get the window number for the inspect window given a
+ * feature and index.
+ * @param feature The feature we want to inspect.
+ * @param index   The index/identifier of the feature to inspect.
+ * @return the InspectWindow (Window)Number
+ */
+static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
+{
+	assert((index >> 24) == 0);
+	return (feature << 24) | index;
+}
+
+/**
+ * The type of a property to show. This is used to
+ * provide an appropriate represenation in the GUI.
+ */
+enum NIType {
+	NIT_INT,   ///< The property is a simple integer
+	NIT_CARGO, ///< The property is a cargo
+};
+
+/** Representation of the data from a NewGRF property. */
+struct NIProperty {
+	const char *name;       ///< A (human readable) name for the property
+	ptrdiff_t offset;       ///< Offset of the variable in the class
+	byte read_size;         ///< Number of bytes (i.e. byte, word, dword etc)
+	byte prop;              ///< The number of the property
+	byte type;
+};
+
+
+/**
+ * Representation of the available callbacks with
+ * information on when they actually apply.
+ */
+struct NICallback {
+	const char *name; ///< The human readable name of the callback
+	ptrdiff_t offset; ///< Offset of the variable in the class
+	byte read_size;   ///< The number of bytes (i.e. byte, word, dword etc) to read
+	byte cb_bit;      ///< The bit that needs to be set for this callback to be enabled
+	uint16 cb_id;     ///< The number of the callback
+};
+/** Mask to show no bit needs to be enabled for the callback. */
+static const int CBM_NO_BIT = UINT8_MAX;
+
+/** Representation on the NewGRF variables. */
+struct NIVariable {
+	const char *name;
+	byte var;
+};
+
+/** Helper class to wrap some functionality/queries in. */
+class NIHelper {
+public:
+	/**
+	 * Is the item with the given index inspectable?
+	 * @param index the index to check.
+	 * @return true iff the index is inspectable.
+	 */
+	virtual bool IsInspectable(uint index) const = 0;
+
+	/**
+	 * Get the parent "window_number" of a given instance.
+	 * @param index the instance to get the parent for.
+	 * @return the parent's window_number or UINT32_MAX if there is none.
+	 */
+	virtual uint GetParent(uint index) const = 0;
+
+	/**
+	 * Get the instance given an index.
+	 * @param index the index to get the instance for.
+	 * @return the instance.
+	 */
+	virtual const void *GetInstance(uint index) const = 0;
+
+	/**
+	 * Get (NewGRF) specs given an index.
+	 * @param index the index to get the specs for for.
+	 * @return the specs.
+	 */
+	virtual const void *GetSpec(uint index) const = 0;
+
+	/**
+	 * Set the string parameters to write the right data for a STRINGn.
+	 * @param index the index to get the string parameters for.
+	 */
+	virtual void SetStringParameters(uint index) const = 0;
+
+	/**
+	 * Resolve (action2) variable for a given index.
+	 * @param index The (instance) index to resolve the variable for.
+	 * @param var   The variable to actually resolve.
+	 * @param param The varaction2 0x60+x parameter to pass.
+	 * @param avail Return whether the variable is available.
+	 * @return The resolved variable's value.
+	 */
+	virtual uint Resolve(uint index, uint var, uint param, bool *avail) const
+	{
+		ResolverObject ro;
+		memset(&ro, 0, sizeof(ro));
+		this->Resolve(&ro, index);
+		return ro.GetVariable(&ro, var, param, avail);
+	}
+
+protected:
+	/**
+	 * Actually execute the real resolving for a given (instance) index.
+	 * @param ro    The resolver object to fill with everything
+	 *              needed to be able to resolve a variable.
+	 * @param index The (instance) index of the to-be-resolved variable.
+	 */
+	virtual void Resolve(ResolverObject *ro, uint index) const {}
+
+	/**
+	 * Helper to make setting the strings easier.
+	 * @param string the string to actually draw.
+	 * @param index  the (instance) index for the string.
+	 */
+	void SetSimpleStringParameters(StringID string, uint32 index) const
+	{
+		SetDParam(0, string);
+		SetDParam(1, index);
+	}
+
+
+	/**
+	 * Helper to make setting the strings easier for objects at a specific tile.
+	 * @param string the string to draw the object's name
+	 * @param index  the (instance) index for the string.
+	 * @param tile   the tile the object is at
+	 */
+	void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
+	{
+		SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
+		SetDParam(1, string);
+		SetDParam(2, index);
+		SetDParam(3, tile);
+	}
+};
+
+
+/** Container for all information for a given feature. */
+struct NIFeature {
+	const NIProperty *properties; ///< The properties associated with this feature.
+	const NICallback *callbacks;  ///< The callbacks associated with this feature.
+	const NIVariable *variables;  ///< The variables associated with this feature.
+	const NIHelper   *helper;     ///< The class container all helper functions.
+	uint psa_size;                ///< The size of the persistent storage in indices.
+	size_t psa_offset;            ///< Offset to the array in the PSA.
+};
+
+/* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
+#include "table/newgrf_debug_data.h"
+
+/**
+ * Get the NIFeature related to the window number.
+ * @param window_number The window to get the NIFeature for.
+ * @return the NIFeature, or NULL is there isn't one.
+ */
+static inline const NIFeature *GetFeature(uint window_number)
+{
+	byte idx = GB(window_number, 24, 8);
+	return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
+}
+
+/**
+ * Get the NIHelper related to the window number.
+ * @param window_number The window to get the NIHelper for.
+ * @pre GetFeature(window_number) != NULL
+ * @return the NIHelper
+ */
+static inline const NIHelper *GetFeatureHelper(uint window_number)
+{
+	return GetFeature(window_number)->helper;
+}
+
+
+/** Widget numbers of settings window */
+enum NewGRFInspectWidgets {
+	NIW_CAPTION,   ///< The caption bar ofcourse
+	NIW_PARENT,    ///< Inspect the parent
+	NIW_MAINPANEL, ///< Panel widget containing the actual data
+	NIW_SCROLLBAR, ///< Scrollbar
+};
+
+/** Window used for inspecting NewGRFs. */
+struct NewGRFInspectWindow : Window {
+	static const int LEFT_OFFSET   = 5; ///< Position of left edge
+	static const int RIGHT_OFFSET  = 5; ///< Position of right edge
+	static const int TOP_OFFSET    = 5; ///< Position of top edge
+	static const int BOTTOM_OFFSET = 5; ///< Position of bottom edge
+
+	/** The value for the variable 60 parameters. */
+	static byte var60params[0x20];
+
+	/** The currently editted parameter, to update the right one. */
+	byte current_edit_param;
+
+	/**
+	 * Check whether the given variable has a parameter.
+	 * @param variable the variable to check.
+	 * @return true iff the variable has a parameter.
+	 */
+	static bool HasVariableParameter(uint variable)
+	{
+		return IsInsideBS(variable, 0x60, 0x20);
+	}
+
+	NewGRFInspectWindow(const WindowDesc *desc, WindowNumber wno) : Window()
+	{
+		this->InitNested(desc, wno);
+
+		this->vscroll.SetCount(0);
+		this->SetWidgetDisabledState(NIW_PARENT, GetFeatureHelper(this->window_number)->GetParent(GetFeatureIndex(this->window_number)) == UINT32_MAX);
+	}
+
+	virtual void SetStringParameters(int widget) const
+	{
+		if (widget != NIW_CAPTION) return;
+
+		GetFeatureHelper(this->window_number)->SetStringParameters(GetFeatureIndex(this->window_number));
+	}
+
+	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+	{
+		if (widget != NIW_MAINPANEL) return;
+
+		resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
+		resize->width  = 1;
+
+		size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
+	}
+
+	/**
+	 * Helper function to draw a string (line) in the window.
+	 * @param r      The (screen) rectangle we must draw within
+	 * @param offset The offset (in lines) we want to draw for
+	 * @param format The format string
+	 */
+	void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
+	{
+		char buf[1024];
+
+		va_list va;
+		va_start(va, format);
+		vsnprintf(buf, lengthof(buf), format, va);
+		va_end(va);
+
+		offset -= this->vscroll.GetPosition();
+		if (offset < 0 || offset >= this->vscroll.GetCapacity()) return;
+
+		::DrawString(r.left + LEFT_OFFSET, r.right + RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
+	}
+
+	virtual void DrawWidget(const Rect &r, int widget) const
+	{
+		if (widget != NIW_MAINPANEL) return;
+
+		uint index = GetFeatureIndex(this->window_number);
+		const NIFeature *nif  = GetFeature(this->window_number);
+		const NIHelper *nih   = nif->helper;
+		const void *base      = nih->GetInstance(index);
+		const void *base_spec = nih->GetSpec(index);
+
+		uint i = 0;
+		if (nif->variables != NULL) {
+			this->DrawString(r, i++, "Variables:");
+			for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
+				bool avail = true;
+				uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[niv->var - 0x60] : 0;
+				uint value = nih->Resolve(index, niv->var, param, &avail);
+
+				if (!avail) continue;
+
+				if (HasVariableParameter(niv->var)) {
+					this->DrawString(r, i++, "  %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
+				} else {
+					this->DrawString(r, i++, "  %02x: %08x (%s)", niv->var, value, niv->name);
+				}
+			}
+		}
+
+		if (nif->psa_size != 0) {
+			this->DrawString(r, i++, "Persistent storage:");
+			assert(nif->psa_size % 4 == 0);
+			int32 *psa = (int32*)((byte*)base + nif->psa_offset);
+			for (uint j = 0; j < nif->psa_size; j += 4, psa += 4) {
+				this->DrawString(r, i++, "  %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
+			}
+		}
+
+		if (nif->properties != NULL) {
+			this->DrawString(r, i++, "Properties:");
+			for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
+				void *ptr = (byte*)base + nip->offset;
+				uint value;
+				switch (nip->read_size) {
+					case 1: value = *(uint8  *)ptr; break;
+					case 2: value = *(uint16 *)ptr; break;
+					case 4: value = *(uint32 *)ptr; break;
+					default: NOT_REACHED();
+				}
+
+				StringID string;
+				SetDParam(0, value);
+				switch (nip->type) {
+					case NIT_INT:
+						string = STR_JUST_INT;
+						break;
+
+					case NIT_CARGO:
+						string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
+						break;
+
+					default:
+						NOT_REACHED();
+				}
+
+				char buffer[64];
+				GetString(buffer, string, lastof(buffer));
+				this->DrawString(r, i++, "  %02x: %s (%s)", nip->prop, buffer, nip->name);
+			}
+		}
+
+		if (nif->callbacks != NULL) {
+			this->DrawString(r, i++, "Callbacks:");
+			for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
+				if (nic->cb_bit != CBM_NO_BIT) {
+					void *ptr = (byte*)base_spec + nic->offset;
+					uint value;
+					switch (nic->read_size) {
+						case 1: value = *(uint8  *)ptr; break;
+						case 2: value = *(uint16 *)ptr; break;
+						case 4: value = *(uint32 *)ptr; break;
+						default: NOT_REACHED();
+					}
+
+					if (!HasBit(value, nic->cb_bit)) continue;
+					this->DrawString(r, i++, "  %03x: %s", nic->cb_id, nic->name);
+				} else {
+					this->DrawString(r, i++, "  %03x: %s (unmasked)", nic->cb_id, nic->name);
+				}
+			}
+		}
+
+		/* Not nice and certainly a hack, but it beats duplicating
+		 * this whole function just to count the actual number of
+		 * elements. Especially because they need to be redrawn. */
+		const_cast<NewGRFInspectWindow*>(this)->vscroll.SetCount(i);
+	}
+
+	virtual void OnPaint()
+	{
+		this->DrawWidgets();
+	}
+
+	virtual void OnClick(Point pt, int widget, int click_count)
+	{
+		switch (widget) {
+			case NIW_PARENT: {
+				uint index = GetFeatureHelper(this->window_number)->GetParent(GetFeatureIndex(this->window_number));
+				::ShowNewGRFInspectWindow((GrfSpecFeature)GB(index, 24, 8), GetFeatureIndex(index));
+			} break;
+
+			case NIW_MAINPANEL: {
+				/* Does this feature have variables? */
+				const NIFeature *nif  = GetFeature(this->window_number);
+				if (nif->variables == NULL) return;
+
+				/* Get the line, make sure it's within the boundaries. */
+				int line = (pt.y - this->GetWidget<NWidgetBase>(NIW_MAINPANEL)->pos_y - TOP_OFFSET) / this->resize.step_height;
+				if (line >= this->vscroll.GetCapacity()) return;
+				line += this->vscroll.GetPosition();
+
+				/* Find the variable related to the line */
+				for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
+					if (line != 1) continue; // 1 because of the "Variables:" line
+
+					if (!HasVariableParameter(niv->var)) break;
+
+					this->current_edit_param = niv->var;
+					ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 3, 100, this, CS_HEXADECIMAL, QSF_NONE);
+				}
+			}
+		}
+	}
+
+	virtual void OnQueryTextFinished(char *str)
+	{
+		if (StrEmpty(str)) return;
+
+		NewGRFInspectWindow::var60params[this->current_edit_param - 0x60] = strtol(str, NULL, 16);
+		this->SetDirty();
+	}
+
+	virtual void OnResize()
+	{
+		this->vscroll.SetCapacityFromWidget(this, NIW_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
+	}
+};
+
+/* static */ byte NewGRFInspectWindow::var60params[0x20] = { 0 }; // Use spec to have 0s in whole array
+
+static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
+	NWidget(NWID_HORIZONTAL),
+		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+		NWidget(WWT_CAPTION, COLOUR_GREY, NIW_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, NIW_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
+		NWidget(WWT_SHADEBOX, COLOUR_GREY),
+		NWidget(WWT_STICKYBOX, COLOUR_GREY),
+	EndContainer(),
+	NWidget(NWID_HORIZONTAL),
+		NWidget(WWT_PANEL, COLOUR_GREY, NIW_MAINPANEL), SetMinimalSize(300, 0), EndContainer(),
+		NWidget(NWID_VERTICAL),
+			NWidget(WWT_SCROLLBAR, COLOUR_GREY, NIW_SCROLLBAR),
+			NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+		EndContainer(),
+	EndContainer(),
+};
+
+static const WindowDesc _newgrf_inspect_desc(
+	WDP_AUTO, 400, 300,
+	WC_NEWGRF_INSPECT, WC_NONE,
+	WDF_UNCLICK_BUTTONS,
+	_nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
+);
+
+void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index)
+{
+	if (!IsNewGRFInspectable(feature, index)) return;
+
+	WindowNumber wno = GetInspectWindowNumber(feature, index);
+	AllocateWindowDescFront<NewGRFInspectWindow>(&_newgrf_inspect_desc, wno);
+}
+
+void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
+{
+	if (feature == GSF_INVALID) return;
+
+	WindowNumber wno = GetInspectWindowNumber(feature, index);
+	DeleteWindowById(WC_NEWGRF_INSPECT, wno);
+
+	/* Reinitialise the land information window to remove the "debug" sprite if needed. */
+	Window *w = FindWindowById(WC_LAND_INFO, 0);
+	if (w != NULL) w->ReInit();
+}
+
+bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
+{
+	const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
+	if (nif == NULL) return false;
+	return nif->helper->IsInspectable(index);
+}
+
+GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
+{
+	switch (GetTileType(tile)) {
+		default:              return GSF_INVALID;
+		case MP_RAILWAY:      return GSF_RAILTYPES;
+		case MP_ROAD:         return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
+		case MP_HOUSE:        return GSF_HOUSES;
+		case MP_INDUSTRY:     return GSF_INDUSTRYTILES;
+
+		case MP_STATION:
+			switch (GetStationType(tile)) {
+				case STATION_RAIL:    return GSF_STATIONS;
+				case STATION_AIRPORT: return GSF_AIRPORTTILES;
+				default:              return GSF_INVALID;
+			}
+	}
+}
+
+GrfSpecFeature GetGrfSpecFeature(VehicleType type)
+{
+	switch (type) {
+		case VEH_TRAIN:    return GSF_TRAINS;
+		case VEH_ROAD:     return GSF_ROADVEHICLES;
+		case VEH_SHIP:     return GSF_SHIPS;
+		case VEH_AIRCRAFT: return GSF_AIRCRAFT;
+		default:           return GSF_INVALID;
+	}
+}
new file mode 100644
--- /dev/null
+++ b/src/table/newgrf_debug_data.h
@@ -0,0 +1,426 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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.
+ * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file newgrf_debug_data.cpp Data 'tables' for NewGRF debugging. */
+
+/* Helper for filling property tables */
+#define NIP(prop, base, variable, type, name) { name, cpp_offsetof(base, variable), cpp_sizeof(base, variable), prop, type }
+#define NIP_END() { NULL, 0, 0, 0, 0 }
+
+/* Helper for filling callback tables */
+#define NIC(cb_id, base, variable, bit) { #cb_id, cpp_offsetof(base, variable), cpp_sizeof(base, variable), bit, cb_id }
+#define NIC_END() { NULL, 0, 0, 0, 0 }
+
+/* Helper for filling variable tables */
+#define NIV(var, name) { name, var }
+#define NIV_END() { NULL, 0 }
+
+
+/*** NewGRF Vehicles ***/
+
+#define NICV(cb_id, bit) NIC(cb_id, Engine, info.callback_mask, bit)
+static const NICallback _nic_vehicles[] = {
+	NICV(CBID_TRAIN_WAGON_POWER,             CBM_TRAIN_WAGON_POWER),
+	NICV(CBID_VEHICLE_LENGTH,                CBM_VEHICLE_LENGTH),
+	NICV(CBID_VEHICLE_LOAD_AMOUNT,           CBM_VEHICLE_LOAD_AMOUNT),
+	NICV(CBID_VEHICLE_REFIT_CAPACITY,        CBM_VEHICLE_REFIT_CAPACITY),
+	NICV(CBID_VEHICLE_ARTIC_ENGINE,          CBM_VEHICLE_ARTIC_ENGINE),
+	NICV(CBID_VEHICLE_CARGO_SUFFIX,          CBM_VEHICLE_CARGO_SUFFIX),
+	NICV(CBID_TRAIN_ALLOW_WAGON_ATTACH,      CBM_NO_BIT),
+	NICV(CBID_VEHICLE_ADDITIONAL_TEXT,       CBM_NO_BIT),
+	NICV(CBID_VEHICLE_COLOUR_MAPPING,        CBM_VEHICLE_COLOUR_REMAP),
+	NICV(CBID_VEHICLE_START_STOP_CHECK,      CBM_NO_BIT),
+	NICV(CBID_VEHICLE_32DAY_CALLBACK,        CBM_NO_BIT),
+	NICV(CBID_VEHICLE_SOUND_EFFECT,          CBM_VEHICLE_SOUND_EFFECT),
+	NICV(CBID_VEHICLE_AUTOREPLACE_SELECTION, CBM_NO_BIT),
+	NICV(CBID_VEHICLE_MODIFY_PROPERTY,       CBM_NO_BIT),
+	NIC_END()
+};
+
+
+static const NIVariable _niv_vehicles[] = {
+	NIV(0x40, "position in consist and length"),
+	NIV(0x41, "position and length of chain of same vehicles"),
+	NIV(0x42, "transported cargo types"),
+	NIV(0x43, "player info"),
+	NIV(0x44, "aircraft info"),
+	NIV(0x45, "curvature info"),
+	NIV(0x46, "motion counter"),
+	NIV(0x47, "vehicle cargo info"),
+	NIV(0x48, "vehicle type info"),
+	NIV(0x49, "year of construction"),
+	NIV(0x60, "count vehicle id occurrences"),
+	NIV_END()
+};
+
+class NIHVehicle : public NIHelper {
+	bool IsInspectable(uint index) const                 { return Engine::Get(Vehicle::Get(index)->engine_type)->grffile != NULL; }
+	uint GetParent(uint index) const                     { const Vehicle *first = Vehicle::Get(index)->First(); return GetInspectWindowNumber(GetGrfSpecFeature(first->type), first->index); }
+	const void *GetInstance(uint index)const             { return Vehicle::Get(index); }
+	const void *GetSpec(uint index) const                { return Engine::Get(Vehicle::Get(index)->engine_type); }
+	void SetStringParameters(uint index) const           { this->SetSimpleStringParameters(STR_VEHICLE_NAME, index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetVehicleResolver(ResolverObject *ro, uint index); GetVehicleResolver(ro, index); }
+};
+
+static const NIFeature _nif_vehicle = {
+	NULL,
+	_nic_vehicles,
+	_niv_vehicles,
+	new NIHVehicle(),
+	0,
+	0
+};
+
+
+/*** NewGRF station (tiles) ***/
+
+#define NICS(cb_id, bit) NIC(cb_id, StationSpec, callback_mask, bit)
+static const NICallback _nic_stations[] = {
+	NICS(CBID_STATION_AVAILABILITY,     CBM_STATION_AVAIL),
+	NICS(CBID_STATION_SPRITE_LAYOUT,    CBM_NO_BIT),
+	NICS(CBID_STATION_TILE_LAYOUT,      CBM_STATION_SPRITE_LAYOUT),
+	NICS(CBID_STATION_ANIM_START_STOP,  CBM_NO_BIT),
+	NICS(CBID_STATION_ANIM_NEXT_FRAME,  CBM_STATION_ANIMATION_NEXT_FRAME),
+	NICS(CBID_STATION_ANIMATION_SPEED,  CBM_STATION_ANIMATION_SPEED),
+	NICS(CBID_STATION_LAND_SLOPE_CHECK, CBM_STATION_SLOPE_CHECK),
+	NIC_END()
+};
+
+static const NIVariable _niv_stations[] = {
+	NIV(0x40, "platform info and relative position"),
+	NIV(0x41, "platform info and relative position for individually built sections"),
+	NIV(0x42, "terrain and track type"),
+	NIV(0x43, "player info"),
+	NIV(0x44, "path signalling info"),
+	NIV(0x45, "rail continuation info"),
+	NIV(0x46, "platform info and relative position from middle"),
+	NIV(0x47, "platform info and relative position from middle for individually built sections"),
+	NIV(0x48, "bitmask of accepted cargoes"),
+	NIV(0x49, "platform info and relative position of same-direction section"),
+	NIV(0x4A, "current animation frame"),
+	NIV(0x60, "amount of cargo waiting"),
+	NIV(0x61, "time since last cargo pickup"),
+	NIV(0x62, "rating of cargo"),
+	NIV(0x63, "time spent on route"),
+	NIV(0x64, "information about last vehicle picking cargo up"),
+	NIV(0x65, "amount of cargo acceptance"),
+	NIV(0x66, "animation frame of nearby tile"),
+	NIV(0x67, "land info of nearby tiles"),
+	NIV(0x68, "station info of nearby tiles"),
+	NIV(0x69, "information about cargo accepted in the past"),
+	NIV_END()
+};
+
+class NIHStation : public NIHelper {
+	bool IsInspectable(uint index) const                 { return GetStationSpec(index) != NULL; }
+	uint GetParent(uint index) const                     { return GetInspectWindowNumber(GSF_FAKE_TOWNS, Station::GetByTile(index)->town->index); }
+	const void *GetInstance(uint index)const             { return NULL; }
+	const void *GetSpec(uint index) const                { return GetStationSpec(index); }
+	void SetStringParameters(uint index) const           { this->SetObjectAtStringParameters(STR_STATION_NAME, GetStationIndex(index), index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetStationResolver(ResolverObject *ro, uint index); GetStationResolver(ro, index); }
+};
+
+static const NIFeature _nif_station = {
+	NULL,
+	_nic_stations,
+	_niv_stations,
+	new NIHStation(),
+	0,
+	0
+};
+
+
+/*** NewGRF house tiles ***/
+
+#define NICH(cb_id, bit) NIC(cb_id, HouseSpec, callback_mask, bit)
+static const NICallback _nic_house[] = {
+	NICH(CBID_HOUSE_ALLOW_CONSTRUCTION,        CBM_HOUSE_ALLOW_CONSTRUCTION),
+	NICH(CBID_HOUSE_ANIMATION_NEXT_FRAME,      CBM_HOUSE_ANIMATION_NEXT_FRAME),
+	NICH(CBID_HOUSE_ANIMATION_START_STOP,      CBM_HOUSE_ANIMATION_START_STOP),
+	NICH(CBID_HOUSE_CONSTRUCTION_STATE_CHANGE, CBM_HOUSE_CONSTRUCTION_STATE_CHANGE),
+	NICH(CBID_HOUSE_COLOUR,                    CBM_HOUSE_COLOUR),
+	NICH(CBID_HOUSE_CARGO_ACCEPTANCE,          CBM_HOUSE_CARGO_ACCEPTANCE),
+	NICH(CBID_HOUSE_ANIMATION_SPEED,           CBM_HOUSE_ANIMATION_SPEED),
+	NICH(CBID_HOUSE_DESTRUCTION,               CBM_HOUSE_DESTRUCTION),
+	NICH(CBID_HOUSE_ACCEPT_CARGO,              CBM_HOUSE_ACCEPT_CARGO),
+	NICH(CBID_HOUSE_PRODUCE_CARGO,             CBM_HOUSE_PRODUCE_CARGO),
+	NICH(CBID_HOUSE_DENY_DESTRUCTION,          CBM_HOUSE_DENY_DESTRUCTION),
+	NICH(CBID_HOUSE_WATCHED_CARGO_ACCEPTED,    CBM_NO_BIT),
+	NICH(CBID_HOUSE_CUSTOM_NAME,               CBM_NO_BIT),
+	NICH(CBID_HOUSE_DRAW_FOUNDATIONS,          CBM_HOUSE_DRAW_FOUNDATIONS),
+	NICH(CBID_HOUSE_AUTOSLOPE,                 CBM_HOUSE_AUTOSLOPE),
+	NIC_END()
+};
+
+static const NIVariable _niv_house[] = {
+	NIV(0x40, "construction state of tile and pseudo-random value"),
+	NIV(0x41, "age of building in years"),
+	NIV(0x42, "town zone"),
+	NIV(0x43, "terrain type"),
+	NIV(0x44, "building counts"),
+	NIV(0x45, "town expansion bits"),
+	NIV(0x46, "current animation frame"),
+	NIV(0x47, "xy coordinate of the building"),
+	NIV(0x60, "other building counts (old house type)"),
+	NIV(0x61, "other building counts (new house type)"),
+	NIV(0x62, "land info of nearby tiles"),
+	NIV(0x63, "current animation frame of nearby house tile"),
+	NIV(0x64, "cargo acceptance history of nearby stations"),
+	NIV(0x65, "distance of nearest house matching a given criterion"),
+	NIV(0x66, "class and ID of nearby house tile"),
+	NIV(0x67, "GRFID of nearby house tile"),
+	NIV_END()
+};
+
+class NIHHouse : public NIHelper {
+	bool IsInspectable(uint index) const                 { return HouseSpec::Get(GetHouseType(index))->grffile != NULL; }
+	uint GetParent(uint index) const                     { return GetInspectWindowNumber(GSF_FAKE_TOWNS, GetTownIndex(index)); }
+	const void *GetInstance(uint index)const             { return NULL; }
+	const void *GetSpec(uint index) const                { return HouseSpec::Get(GetHouseType(index)); }
+	void SetStringParameters(uint index) const           { this->SetObjectAtStringParameters(STR_TOWN_NAME, GetTownIndex(index), index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetHouseResolver(ResolverObject *ro, uint index); GetHouseResolver(ro, index); }
+};
+
+static const NIFeature _nif_house = {
+	NULL,
+	_nic_house,
+	_niv_house,
+	new NIHHouse(),
+	0,
+	0
+};
+
+
+/*** NewGRF industry tiles ***/
+
+#define NICIT(cb_id, bit) NIC(cb_id, IndustryTileSpec, callback_mask, bit)
+static const NICallback _nic_industrytiles[] = {
+	NICIT(CBID_INDTILE_ANIM_START_STOP,  CBM_NO_BIT),
+	NICIT(CBID_INDTILE_ANIM_NEXT_FRAME,  CBM_INDT_ANIM_NEXT_FRAME),
+	NICIT(CBID_INDTILE_ANIMATION_SPEED,  CBM_INDT_ANIM_SPEED),
+	NICIT(CBID_INDTILE_CARGO_ACCEPTANCE, CBM_INDT_CARGO_ACCEPTANCE),
+	NICIT(CBID_INDTILE_ACCEPT_CARGO,     CBM_INDT_ACCEPT_CARGO),
+	NICIT(CBID_INDTILE_SHAPE_CHECK,      CBM_INDT_SHAPE_CHECK),
+	NICIT(CBID_INDTILE_DRAW_FOUNDATIONS, CBM_INDT_DRAW_FOUNDATIONS),
+	NICIT(CBID_INDTILE_AUTOSLOPE,        CBM_INDT_AUTOSLOPE),
+	NIC_END()
+};
+
+static const NIVariable _niv_industrytiles[] = {
+	NIV(0x40, "construction state of tile"),
+	NIV(0x41, "ground type"),
+	NIV(0x42, "current town zone in nearest town"),
+	NIV(0x43, "relative position"),
+	NIV(0x44, "animation frame"),
+	NIV(0x60, "land info of nearby tiles"),
+	NIV(0x61, "animation stage of nearby tiles"),
+	NIV(0x62, "get industry or airport tile ID at offset"),
+	NIV_END()
+};
+
+class NIHIndustryTile : public NIHelper {
+	bool IsInspectable(uint index) const                 { return GetIndustryTileSpec(GetIndustryGfx(index))->grf_prop.grffile != NULL; }
+	uint GetParent(uint index) const                     { return GetInspectWindowNumber(GSF_INDUSTRIES, GetIndustryIndex(index)); }
+	const void *GetInstance(uint index)const             { return NULL; }
+	const void *GetSpec(uint index) const                { return GetIndustryTileSpec(GetIndustryGfx(index)); }
+	void SetStringParameters(uint index) const           { this->SetObjectAtStringParameters(STR_INDUSTRY_NAME, GetIndustryIndex(index), index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetIndustryTileResolver(ResolverObject *ro, uint index); GetIndustryTileResolver(ro, index); }
+};
+
+static const NIFeature _nif_industrytile = {
+	NULL,
+	_nic_industrytiles,
+	_niv_industrytiles,
+	new NIHIndustryTile(),
+	0,
+	0
+};
+
+
+/*** NewGRF industries ***/
+
+static const NIProperty _nip_industries[] = {
+	NIP(0x10, Industry, produced_cargo[0], NIT_CARGO, "produced cargo 0"),
+	NIP(0x10, Industry, produced_cargo[1], NIT_CARGO, "produced cargo 1"),
+	NIP(0x11, Industry, accepts_cargo[0],  NIT_CARGO, "accepted cargo 0"),
+	NIP(0x11, Industry, accepts_cargo[1],  NIT_CARGO, "accepted cargo 1"),
+	NIP(0x11, Industry, accepts_cargo[2],  NIT_CARGO, "accepted cargo 2"),
+	NIP_END()
+};
+
+#define NICI(cb_id, bit) NIC(cb_id, IndustrySpec, callback_mask, bit)
+static const NICallback _nic_industries[] = {
+	NICI(CBID_INDUSTRY_AVAILABLE,            CBM_IND_AVAILABLE),
+	NICI(CBID_INDUSTRY_LOCATION,             CBM_IND_LOCATION),
+	NICI(CBID_INDUSTRY_PRODUCTION_CHANGE,    CBM_IND_PRODUCTION_CHANGE),
+	NICI(CBID_INDUSTRY_MONTHLYPROD_CHANGE,   CBM_IND_MONTHLYPROD_CHANGE),
+	NICI(CBID_INDUSTRY_CARGO_SUFFIX,         CBM_IND_CARGO_SUFFIX),
+	NICI(CBID_INDUSTRY_FUND_MORE_TEXT,       CBM_IND_FUND_MORE_TEXT),
+	NICI(CBID_INDUSTRY_WINDOW_MORE_TEXT,     CBM_IND_WINDOW_MORE_TEXT),
+	NICI(CBID_INDUSTRY_SPECIAL_EFFECT,       CBM_IND_SPECIAL_EFFECT),
+	NICI(CBID_INDUSTRY_REFUSE_CARGO,         CBM_IND_REFUSE_CARGO),
+	NICI(CBID_INDUSTRY_DECIDE_COLOUR,        CBM_IND_DECIDE_COLOUR),
+	NICI(CBID_INDUSTRY_INPUT_CARGO_TYPES,    CBM_IND_INPUT_CARGO_TYPES),
+	NICI(CBID_INDUSTRY_OUTPUT_CARGO_TYPES,   CBM_IND_OUTPUT_CARGO_TYPES),
+	NIC_END()
+};
+
+static const NIVariable _niv_industries[] = {
+	NIV(0x40, "waiting cargo 0"),
+	NIV(0x41, "waiting cargo 1"),
+	NIV(0x42, "waiting cargo 2"),
+	NIV(0x43, "distance to closest dry/land tile"),
+	NIV(0x44, "layout number"),
+	NIV(0x45, "player info"),
+	NIV(0x46, "industry construction date"),
+	NIV(0x60, "get industry tile ID at offset"),
+	NIV(0x61, "get random tile bits at offset"),
+	NIV(0x62, "land info of nearby tiles"),
+	NIV(0x63, "animation stage of nearby tiles"),
+	NIV(0x64, "distance on nearest industry with given type"),
+	NIV(0x65, "get town zone and Manhattan distance of closest town"),
+	NIV(0x66, "get square of Euclidean distance of closes town"),
+	NIV(0x67, "count of industry and distance of closest instance"),
+	NIV(0x68, "count of industry and distance of closest instance with layout filter"),
+	NIV_END()
+};
+
+class NIHIndustry : public NIHelper {
+	bool IsInspectable(uint index) const                 { return GetIndustrySpec(Industry::Get(index)->type)->grf_prop.grffile != NULL; }
+	uint GetParent(uint index) const                     { return GetInspectWindowNumber(GSF_FAKE_TOWNS, Industry::Get(index)->town->index); }
+	const void *GetInstance(uint index)const             { return Industry::Get(index); }
+	const void *GetSpec(uint index) const                { return GetIndustrySpec(Industry::Get(index)->type); }
+	void SetStringParameters(uint index) const           { this->SetSimpleStringParameters(STR_INDUSTRY_NAME, index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetIndustryResolver(ResolverObject *ro, uint index); GetIndustryResolver(ro, index); }
+};
+
+static const NIFeature _nif_industry = {
+	_nip_industries,
+	_nic_industries,
+	_niv_industries,
+	new NIHIndustry(),
+	cpp_lengthof(Industry, psa.storage),
+	cpp_offsetof(Industry, psa.storage)
+};
+
+
+/*** NewGRF rail types ***/
+
+static const NIVariable _niv_railtypes[] = {
+	NIV(0x40, "terrain type"),
+	NIV(0x41, "enhanced tunnels"),
+	NIV(0x42, "level crossing status"),
+	NIV_END()
+};
+
+class NIHRailType : public NIHelper {
+	bool IsInspectable(uint index) const                 { return true; }
+	uint GetParent(uint index) const                     { return UINT32_MAX; }
+	const void *GetInstance(uint index)const             { return NULL; }
+	const void *GetSpec(uint index) const                { return NULL; }
+	void SetStringParameters(uint index) const           { this->SetObjectAtStringParameters(STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_RAIL_TYPE, INVALID_STRING_ID, index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetRailTypeResolver(ResolverObject *ro, uint index); GetRailTypeResolver(ro, index); }
+};
+
+static const NIFeature _nif_railtype = {
+	NULL,
+	NULL,
+	_niv_railtypes,
+	new NIHRailType(),
+	0,
+	0
+};
+
+
+/*** NewGRF airport tiles ***/
+
+#define NICAT(cb_id, bit) NIC(cb_id, AirportTileSpec, callback_flags, bit)
+static const NICallback _nic_airporttiles[] = {
+	NICAT(CBID_AIRPTILE_DRAW_FOUNDATIONS, CBM_AIRT_DRAW_FOUNDATIONS),
+	NICAT(CBID_AIRPTILE_ANIM_START_STOP,  CBM_NO_BIT),
+	NICAT(CBID_AIRPTILE_ANIM_NEXT_FRAME,  CBM_AIRT_ANIM_NEXT_FRAME),
+	NICAT(CBID_AIRPTILE_ANIMATION_SPEED,  CBM_AIRT_ANIM_SPEED),
+	NIC_END()
+};
+
+class NIHAirportTile : public NIHelper {
+	bool IsInspectable(uint index) const                 { return AirportTileSpec::Get(GetAirportGfx(index))->grf_prop.grffile != NULL; }
+	uint GetParent(uint index) const                     { return GetInspectWindowNumber(GSF_FAKE_TOWNS, Station::GetByTile(index)->town->index); }
+	const void *GetInstance(uint index)const             { return NULL; }
+	const void *GetSpec(uint index) const                { return AirportTileSpec::Get(GetAirportGfx(index)); }
+	void SetStringParameters(uint index) const           { this->SetObjectAtStringParameters(STR_STATION_NAME, GetStationIndex(index), index); }
+	void Resolve(ResolverObject *ro, uint32 index) const { extern void GetAirportTileTypeResolver(ResolverObject *ro, uint index); GetAirportTileTypeResolver(ro, index); }
+};
+
+static const NIFeature _nif_airporttile = {
+	NULL,
+	_nic_airporttiles,
+	_niv_industrytiles, // Yes, they share this (at least now)
+	new NIHAirportTile(),
+	0,
+	0
+};
+
+
+/*** NewGRF towns ***/
+
+static const NIVariable _niv_towns[] = {
+	NIV(0x40, "larger town effect on this town"),
+	NIV(0x41, "town index"),
+	NIV(0x82, "population"),
+	NIV(0x94, "zone radius 0"),
+	NIV(0x96, "zone radius 1"),
+	NIV(0x98, "zone radius 2"),
+	NIV(0x9A, "zone radius 3"),
+	NIV(0x9C, "zone radius 4"),
+	NIV(0xB6, "number of buildings"),
+	NIV_END()
+};
+
+class NIHTown : public NIHelper {
+	bool IsInspectable(uint index) const                 { return false; }
+	uint GetParent(uint index) const                     { return UINT32_MAX; }
+	const void *GetInstance(uint index)const             { return Town::Get(index); }
+	const void *GetSpec(uint index) const                { return NULL; }
+	void SetStringParameters(uint index) const           { this->SetSimpleStringParameters(STR_TOWN_NAME, index); }
+	uint Resolve(uint index, uint var, uint param, bool *avail) const { return TownGetVariable(var, param, avail, Town::Get(index)); }
+};
+
+static const NIFeature _nif_town = {
+	NULL,
+	NULL,
+	_niv_towns,
+	new NIHTown(),
+	0,
+	0
+};
+
+/** Table with all NIFeatures. */
+static const NIFeature * const _nifeatures[] = {
+	&_nif_vehicle,      // GSF_TRAINS
+	&_nif_vehicle,      // GSF_ROADVEHICLES
+	&_nif_vehicle,      // GSF_SHIPS
+	&_nif_vehicle,      // GSF_AIRCRAFT
+	&_nif_station,      // GSF_STATIONS
+	NULL,               // GSF_CANALS (no callbacks/action2 implemented)
+	NULL,               // GSF_BRIDGES (no callbacks/action2)
+	&_nif_house,        // GSF_HOUSES
+	NULL,               // GSF_GLOBALVAR (has no "physical" objects)
+	&_nif_industrytile, // GSF_INDUSTRYTILES
+	&_nif_industry,     // GSF_INDUSTRIES
+	NULL,               // GSF_CARGOS (has no "physical" objects)
+	NULL,               // GSF_SOUNDFX (has no "physical" objects)
+	NULL,               // GSF_AIRPORTS (feature not implemented)
+	NULL,               // GSF_SIGNALS (feature not implemented)
+	NULL,               // GSF_OBJECTS (feature not implemented)
+	&_nif_railtype,     // GSF_RAILTYPES
+	&_nif_airporttile,  // GSF_AIRPORTTILES
+	&_nif_town,         // GSF_FAKE_TOWNS
+};
+assert_compile(lengthof(_nifeatures) == GSF_FAKE_END);
--- a/src/window_type.h
+++ b/src/window_type.h
@@ -106,6 +106,7 @@
 	WC_AI_DEBUG,
 	WC_AI_LIST,
 	WC_AI_SETTINGS,
+	WC_NEWGRF_INSPECT,
 
 	WC_INVALID = 0xFFFF
 };