Matchstick (MGDF) is a framework designed to make developing, installing, distributing, and updating games on Windows (Windows 7+ supported) easier. The framework uses DirectX 11 or 12 for graphics, RawInput/XInput for user input (has full support for the XBox 360 controller), and OpenAL for audio (supports 3d positional audio and audio streaming using Ogg Vorbis). The framework also provides a virtual file system which allows easy access to resources stored in zipped archives (new archive formats can also be plugged in).
MGDF makes developing games in c++ faster and easier as you no longer have to build and rebuild the same boilerplate code for initializing DirectX/Audio etc, loading preferences and setting up a render loop every single time you make a game. This means you can spend more time making your game. It also makes distributing games easier as it includes an auto update mechanism with full versioning support.
MGDF is comprised of two main components (the second of which is not required for running games, but for online updating/distribution)
This is written in c++ and is the part of the framework which actually runs the games. The core works by initializing the graphics/input/audio devices, loading up user preferences and setting up a multithreaded render/simulation loop before loading the user supplied game dll (known as a game module) and passing control on to that module. Once up and running the game module can then access the framework's functionality via a series of c++ interfaces (see the API reference for more details)
These interfaces include the ability to create audio samples and streams which under the hood are powered by the openAL audio system. Also included is the ability to access user interfaces devices including the mouse, keyboard, and any connected xbox 360 controllers. File system access is performed by a virtual file system wrapper which allows simple access to normal files on disk as well as files within compressed archives. The module accesses the selected D3D11 or D3D12 device through the host interfaces. The framework also provides interfaces for graphics settings.
This is written in C# (.NET 4.8) and consists a windows forms application which provides a minimalist launcher that checks for game updates and displays friendly error messages if the core crashes. It is also responsible for sending game statistics if this feature is used. In addition to this, the GamesManager also can be used by an installer to handle the usual tasks of windows program installation such as adding a installed programs entry in the registry, a start menu entry, and a desktop icon (see the command line options for more details)
NOTE This component is optional. If you plan on using your own solution for launching the game and managing updates (Steam, Itch.io, etc.), you can omit this piece of the framework from your game and start your game by running core.exe directly instead of launching it via GamesManager.exe
MGDF takes a convention over configuration approach for the most part and assumes that a number of files will be provided by the game and that these files will exist in a standard folder structure inside the root MGDF install folder. The standard folder layout with the relevant locations highlighted are as follows.
NOTE The MGDF redistributable included in the SDK is already layed out in this format, all you need to add when building a new game is the contents of the /game folder.
/install root
core.exe
GamesManager.exe
GamesManager.FrameworkUpdater.exe
... other framework dlls
/resources
/dependencies
All games files should be placed inside this folder
All game executable dlls should be placed inside this folder
This dll is required and provides an entry point for the host to create an instance of the game module.
Any game data files should go in here. This folder serves as the root of the virtual file system and content outside this folder is not accessable
This JSON file contains metadata including the name, version, and preference settings for the game. This file is required and the game will not run unless it is present.
This 48x48 PNG file is used to display the game logo in the GamesManager UI as well as the installed programs registry entry, and the desktop icon. This file is optional, and if not specified a default MGDF icon will be used instead.
By default the PNG image above will be converted automatically into an icon file for display in parts of the system that cannot use PNGs. If you don't wish to use the automatically converted icon file (sometimes the conversion results in messy transparency), and instead want to provide one, you can do so here.
As mentioned above, this file is responsible for telling the GamesManager and core everything they need to know about running your game. A basic manifest file is shown below. For full information on all the fields and their meanings, check out the API reference for more information.
{
"gameUid":"a unique id",
"gameName":"A human readable name",
"version":"0.1",
"interfaceVersion":"1",
"developerName":"Human readable developer name",
"homepage":"https://www.example.com",
"supportType":"email",
"supportUrl":"[email protected]"
}Module.dll is the actual entry point into your game. This dll requires a number of functions to be exported, and must be able to provide the mgdf host with an instance of the IMGDFModule interface in order for the host to be able to run your game code.
MGDF is multithreaded and runs two main threads, the Sim thread and the Render thread. The Render thread is responsible for all rendering, and the Sim thread is responsible for handling input, audio, game logic. It is important to handle the sharing of data between these threads in a threadsafe manner for a game to work correctly. For the most part this means minimizing the amount of shared state between the Sim and Rendering parts of your game and ensuring that appropriate locking strategies are in place.
One method for managing this is to use a Double Buffered game state, i.e. the Sim thread modifies the game state and on each tick makes a copy of the current game state (or as much of a copy as the renderer needs to do its job) and copies it into a buffer which the Render thread can pick up when it's ready to render the next frame. In this way the Sim and Render threads can operate mostly independantly of each other without having to worry about concurrency issues. By default the Render thread will not run more than once per Sim tick, so you don't have to worry about buffer underrun in this case either (though this behaviour is configurable - if you have a very slow Sim framerate and want to interpolate a single game state over several render frames - check out the host.interpolateFrames preference option in the Game.json API reference)
NOTE The SDK includes a sample visual studio game project in src/samples/EmptyGame. This project contains a game.json file and will create a compatible module.dll with a stub module class.
Implement each method in IMGDFModule. The host calls ST methods on the simulation thread. It calls RT methods on the render thread. It can call Panic on either thread. MGDF interfaces use COM reference counting. Release each interface when you no longer need it.
This example uses D3D11. Call IMGDFCommonHost::GetGraphicsAPI to read the selected API. Query the host for IMGDFD3D11Host to get the D3D11 device from any thread. Query the render host for IMGDFD3D11RenderHost to get the back buffer, depth buffer, texture descriptions, or GPU counters. Use those render methods only on the render thread. IMGDFRenderHost::GetBackBufferInfo returns the back buffer width, height, format, and sample count for either graphics API.
The example queries the interfaces in RTDraw. Its local MGDF::ComObject values release their references when the method returns. If you keep resources between frames, release back buffer references in RTBeforeBackBufferChange. Release device resources in RTBeforeDeviceReset and RTShutDown.
#pragma once
#include <crtdbg.h>
#include <cstring>
#include <MGDF/MGDF.h>
#include <MGDF/ComObject.hpp>
class Module: public MGDF::ComBase<IMGDFModule>
{
public:
virtual ~Module() = default;
BOOL __stdcall STNew(IMGDFSimHost *host) final;
BOOL __stdcall STUpdate(IMGDFSimHost *host, double elapsedTime) final;
void __stdcall STShutDown(IMGDFSimHost *host) final;
BOOL __stdcall RTBeforeFirstDraw(IMGDFRenderHost *host) final;
BOOL __stdcall RTDraw(IMGDFRenderHost *host, double alpha) final;
void __stdcall RTAfterPresent(IMGDFRenderHost *host) final;
BOOL __stdcall RTBeforeBackBufferChange(IMGDFRenderHost *host) final;
BOOL __stdcall RTBackBufferChange(IMGDFRenderHost *host) final;
BOOL __stdcall RTBeforeDeviceReset(IMGDFRenderHost *host) final;
BOOL __stdcall RTDeviceReset(IMGDFRenderHost *host) final;
void __stdcall RTShutDown(IMGDFRenderHost *host) final;
void __stdcall Panic() final;
};#include "Module.hpp"
BOOL __stdcall Module::STNew(IMGDFSimHost *host)
{
// Initialize the simulation.
return TRUE;
}
BOOL __stdcall Module::STUpdate(IMGDFSimHost *host, double elapsedTime)
{
// Update the game state.
return TRUE;
}
void __stdcall Module::STShutDown(IMGDFSimHost *host)
{
host->ShutDown();
}
BOOL __stdcall Module::RTBeforeFirstDraw(IMGDFRenderHost *host)
{
// Initialize rendering resources.
return TRUE;
}
BOOL __stdcall Module::RTDraw(IMGDFRenderHost *host, double alpha)
{
if (host->GetGraphicsAPI() != MGDF_GRAPHICS_API_D3D11) return FALSE;
MGDF::ComObject<IMGDFD3D11Host> d3d11;
if (FAILED(host->QueryInterface(__uuidof(IMGDFD3D11Host),
reinterpret_cast<void **>(d3d11.Assign())))) return FALSE;
MGDF::ComObject<ID3D11Device> device;
d3d11->GetD3D11Device(device.Assign());
MGDF::ComObject<IMGDFD3D11RenderHost> render;
if (FAILED(host->QueryInterface(__uuidof(IMGDFD3D11RenderHost),
reinterpret_cast<void **>(render.Assign())))) return FALSE;
MGDF::ComObject<ID3D11Texture2D> backBuffer;
render->GetBackBuffer(backBuffer.Assign());
MGDFBackBufferInfo info = {};
host->GetBackBufferInfo(&info);
// Draw with device and backBuffer here.
return TRUE;
}
void __stdcall Module::RTAfterPresent(IMGDFRenderHost *host)
{
// The host calls this after Present on each render frame.
}
BOOL __stdcall Module::RTBeforeBackBufferChange(IMGDFRenderHost *host)
{
// Release saved back buffer references and views here.
return TRUE;
}
BOOL __stdcall Module::RTBackBufferChange(IMGDFRenderHost *host)
{
// Recreate resources that depend on the back buffer size.
return TRUE;
}
BOOL __stdcall Module::RTBeforeDeviceReset(IMGDFRenderHost *host)
{
// Release saved device resources here.
return TRUE;
}
BOOL __stdcall Module::RTDeviceReset(IMGDFRenderHost *host)
{
// Recreate device resources here.
return TRUE;
}
void __stdcall Module::RTShutDown(IMGDFRenderHost *host)
{
// Release saved rendering resources here.
}
void __stdcall Module::Panic()
{
// Clean up after a fatal error without calling host methods.
}Export GetGraphicsRequirements to declare the graphics requirements. This export replaces GetCompatibleFeatureLevels. Set APICount to a value from 1 to 4. Fill APIs with supported MGDFGraphicsAPI values in preference order. The values are MGDF_GRAPHICS_API_D3D11 and MGDF_GRAPHICS_API_D3D12. Set MinFeatureLevel to the minimum D3D feature level. MinShaderModel holds a D3D_SHADER_MODEL value for D3D12. D3D11 ignores this field.
The host reads these requirements before it calls GetModule. The host.graphicsAPI preference can select an API from the list. This example requests only D3D11.
#include "Module.hpp"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
extern "C" __declspec(dllexport) HRESULT
GetGraphicsRequirements(MGDFGraphicsRequirements *requirements) {
if (!requirements) return E_POINTER;
*requirements = {};
requirements->APICount = 1;
requirements->APIs[0] = MGDF_GRAPHICS_API_D3D11;
requirements->MinFeatureLevel = D3D_FEATURE_LEVEL_11_0;
return S_OK;
}
extern "C" __declspec(dllexport) HRESULT GetModule(IMGDFModule **module) {
auto m = MGDF::MakeCom<Module>();
m.AddRawRef(module);
return S_OK;
}Export GetGraphicsRequirements with MGDF_GRAPHICS_API_D3D12 in its API list. This example requests D3D12 with feature level 11.0 and shader model 6.0.
#include <MGDF/MGDF.h>
extern "C" __declspec(dllexport) HRESULT
GetGraphicsRequirements(MGDFGraphicsRequirements *requirements) {
if (!requirements) return E_POINTER;
*requirements = {};
requirements->APICount = 1;
requirements->APIs[0] = MGDF_GRAPHICS_API_D3D12;
requirements->MinFeatureLevel = D3D_FEATURE_LEVEL_11_0;
requirements->MinShaderModel = D3D_SHADER_MODEL_6_0;
return S_OK;
}A module that implements both APIs can list both values in preference order. The host.graphicsAPI preference can select either listed API. Check GetGraphicsAPI before you query an API interface. QueryInterface exposes D3D11 interfaces only when D3D11 is selected. It exposes D3D12 interfaces only when D3D12 is selected.
Query any host interface for IMGDFD3D12Host. Its methods are safe to call from any thread. GetD3D12Device returns an ID3D12Device10 reference. GetDirectQueue, GetComputeQueue, and GetCopyQueue return command queue references. The module owns these returned references. Release them before you acknowledge a device reset. The device can be null during a reset.
Query the render host for IMGDFD3D12RenderHost. Use its methods only on the render thread. GetCurrentFrame fills an MGDFFrameInfo structure. GetFrameFence returns an owned reference to the host's frame fence. Release that reference when you no longer need it. Only the host signals this fence.
MGDFFrameInfo supplies the current BackBuffer, BackBufferRTV, BackBufferDesc, and BackBufferIndex. The back buffer pointer is borrowed. Release any references you add before RTBeforeBackBufferChange returns. Get the current frame again after a back buffer change. The D3D12 swap chain uses one sample per pixel. The module creates its own depth buffers and multisample targets.
GetCurrentFrame in RTDraw. The back buffer enters this callback in D3D12_RESOURCE_STATE_PRESENT.D3D12_RESOURCE_STATE_PRESENT.RTDraw. The module submits its own lists. The back buffer must be in PRESENT state when those lists complete.Present after RTDraw. It then calls void RTAfterPresent(IMGDFRenderHost *host). Use this callback to submit background work that does not use the presented back buffer.RTAfterPresent returns, the host resolves GPU counters. It then signals the frame fence on the direct queue with FrameOrdinal.FrameOrdinal and FrameSlot stay fixed through RTAfterPresent. Work on the compute or copy queue needs explicit synchronization. Signal a module fence on that queue after its work. Add a wait for that value on the direct queue before RTAfterPresent returns. The host's frame fence covers that work only after this synchronization.
The host permits two frames in flight. FramesInFlight is 2. FrameSlot equals FrameOrdinal % FramesInFlight. Keep a command allocator and other temporary resources for each frame slot. Use FrameSlot to select them. BackBufferIndex identifies the swap chain image and is separate from the frame slot.
The host waits for the previous use of a frame slot before it calls RTDraw. You can then reset that slot's command allocator and reuse its resources. This applies to work covered by the host's frame fence. Track other queue work with a module fence.
Record a resource's last FrameOrdinal when you submit its final use. Keep the resource in a deferred release list. CompletedOrdinal is the highest completed frame fence value when the host prepares the frame. Release the resource only when CompletedOrdinal >= lastUseOrdinal. Apply the same rule before you reuse upload memory or GPU descriptor storage. CPU return from RTDraw does not indicate GPU completion.
Use IMGDFD3D12RenderHost::CreateGPUCounter to time commands on a direct queue list. Pass an IMGDFMetric, the native command list, and an output pointer for IMGDFPerformanceCounter. Compute and copy lists are not supported.
For lists used with GPU counters, include MGDF/D3D12CommandList.hpp from include/MGDF/D3D12CommandList.hpp in the SDK. Use MGDF::D3D12CommandList::Create to create each list. Call Reset on the helper to start a new recording. Call Close on the helper to finish recording. Use Get to record commands, create counters, and submit the native list. Do not call Reset or Close through the native pointer. The counter needs the helper's recording state. An untracked list returns E_INVALIDARG.
Begin each counter scope while its list is recording. Release the scope before you close or reset the list. Execute that list in the same frame before RTAfterPresent returns. The host logs and discards a scope that ends after its list closes, resets, or passes the end of its frame.
This helper records and submits a timed clear from RTDraw. The caller supplies an open command list for the current frame slot. Keep that list and its allocator alive until the frame completes. Create the metric with IMGDFCommonHost::CreateGaugeMetric.
#include <crtdbg.h>
#include <cstring>
#include <MGDF/MGDF.h>
#include <MGDF/D3D12CommandList.hpp>
HRESULT DrawTimedClear(IMGDFD3D12RenderHost *host,
ID3D12CommandQueue *directQueue,
MGDF::D3D12CommandList &commands,
IMGDFMetric *metric) {
MGDFFrameInfo frame = {};
host->GetCurrentFrame(&frame);
auto list = commands.Get();
MGDF::ComObject<IMGDFPerformanceCounter> counter;
HRESULT result = host->CreateGPUCounter(metric, list, counter.Assign());
if (FAILED(result)) return result;
{
MGDF::ComObject<IMGDFPerformanceCounterScope> scope;
result = counter->Begin(nullptr, scope.Assign());
if (FAILED(result)) return result;
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = frame.BackBuffer;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
list->ResourceBarrier(1, &barrier);
const float color[4] = {0.1f, 0.2f, 0.3f, 1.0f};
list->ClearRenderTargetView(frame.BackBufferRTV, color, 0, nullptr);
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
list->ResourceBarrier(1, &barrier);
} // Release the scope while the list is still recording.
result = commands.Close();
if (FAILED(result)) return result;
ID3D12CommandList *lists[] = {list};
directQueue->ExecuteCommandLists(1, lists);
return S_OK;
}Call Create(device, allocator) before the list's first use. This leaves the list open. For each later use, reset the completed frame slot's allocator first. Then call Reset(allocator) on the helper before DrawTimedClear. Return TRUE from RTDraw after a successful submission.
Build D3D12 modules with Windows SDK 10.0.26100 or later. The MGDF headers need no other D3D12 headers. The redistributable includes the D3D12 Agility SDK runtime (1.619.5) in the D3D12 folder beside core.exe, so modules don't distribute it. A module needs the Agility SDK headers only to use D3D12 APIs that its Windows SDK doesn't declare. Use headers no newer than 1.619.5, because the host loads that runtime.
When distributing a game to users, one simply has to put the game content and module in the prescribed locations inside the framework redistributable (as shown here). Users can then run the game by running GamesManager.exe.
However, having to keep your game inside the redistributable folder can be annoying during development, so there are a few other options available to make running the game in development easier.
Firstly, in development don't bother running GamesManager.exe, run core.exe directly instead. The GamesManager only wraps core.exe and does update checking and some other services that are not needed for testing development builds. Also running core.exe directly allows you to easily attach the Visual Studio debugger when running your game.
Secondly, you can actually run a game that exists anywhere on disk by passing in the following command line parameters when running core.exe
core.exe -gamediroverride "some folder on disk"
Provided that the folder specified has the usual /bin and /content structure that the /game folder in the framework would usually have, things will work as if the folder was in the /game folder in the framework redistributable folder.
Another useful commandline flag is
-userdiroverride
This changes the location that MGDF stores logs and per user data such as saved games and preferences from its usual folder in %appdata%/local/MGDF/1/<GameUid> to a /user folder in the parent folder of the current /game directory. For example, if the -gamediroverride was set to c:\mygame\game, and -userdiroverride was also set, then c:\mygame\user would be used to store all user data.
In order to distribute your game to users it is recommended to bundle your game into an installer, rather than just provide a .zip archive containing a framework redistributable combined with your game module and content files.
The main advantages to bundling everything in an installer is that the GamesManager depends on the .NET framework 4.8 runtime, which is not guaranteed to be present on all users systems. Because of this an installer may be necessary to install the framework prior to running the GamesManager
The general procedure for integrating an MGDF game with an installer is as follows
The installer should then invoke the following command
GamesManager.exe -register
This will cause the GamesManager to
;Includes
!include "MUI2.nsh"
!include "FileFunc.nsh"
;--------------------------------
; The name of the installer
Name "New game"
OutFile "Setup.exe"
; The default installation directory
InstallDir "$PROGRAMFILES\NewGame"
RequestExecutionLevel admin
;--------------------------------
;Constants
!define CSIDL_APPDATA '0x1A' ;Application Data path
;--------------------------------
;Pages
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
; check for and install .net 4.8
Section "Install .NET Framework 4.8"
IfSilent Ignoredotnetsetup
Call CheckAndInstallDotNet
Ignoredotnetsetup:
SectionEnd
;--------------------------------
; Remove lastupdate file so we can check for updates as
; soon as we next start up (this installer may be out of date)
Section "Remove lastUpdate check"
Delete $LOCALAPPDATA\MGDF\1\NewGame\.lastupdate
SectionEnd
;--------------------------------
; the core engine components
Section "Install NewGame"
;--- core engine components ---
SetOutPath $INSTDIR
File /r "path to the MGDF root folder"
;--- write out the add/remove program's registry keys & install any framework dependencies
ExecWait '"$INSTDIR\GamesManager.exe" -register'
SetRegView 64
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MGDF1_NewGame" "UninstallString" "$INSTDIR\Uninstall.exe"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MGDF1_NewGame" "EstimatedSize" "$0"
WriteUninstaller $INSTDIR\Uninstall.exe
SectionEnd
;--------------------------------
; uninstall the core engine
Section "Uninstall"
;-- remove registry keys, shortcuts etc.
ExecWait '"$INSTDIR\GamesManager.exe" -deregister'
;--- remove uninstaller ---
Delete $INSTDIR\Uninstall.exe
RMDIR /r $INSTDIR
SectionEnd
Function CheckAndInstallDotNet
;--- https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_d
ClearErrors
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Release"
IfErrors NotDetected
${If} $0 >= 528040
DetailPrint "Microsoft .NET Framework 4.8 is installed ($0)"
${Else}
NotDetected:
DetailPrint "Installing Microsoft .NET Framework 4.8"
SetDetailsPrint listonly
SetOutPath $TEMP\Junkship
File ndp48-web.exe
ExecWait '"$TEMP\Junkship\ndp48-web.exe" /q /norestart' $0
${If} $0 == 3010
${OrIf} $0 == 1641
DetailPrint "Microsoft .NET Framework 4.8 installer requested reboot"
SetRebootFlag true
${EndIf}
SetDetailsPrint lastused
DetailPrint "Microsoft .NET Framework 4.8 installer returned $0"
Delete $TEMP\Junkship\ndp48-web.exe
${EndIf}
FunctionEnd
The MGDF GamesManager can check for available updates to either the game or framework upon launching the game (this update check will occur at most once per day). To enable this feature, you need to add an updateservice property to your game.json file, listing a url pointing to a game update manifest file. The JSON format for the manifest url is shown below, listing the latest version of your game, and the MGDF framework version your games supports.
{
"framework": {
"version": "0.11.22",
"url": "https://s3.matchstickframework.org/MGDF_0.11.22_x64.zip",
"md5": "be4573e2058fa6106093443e6c7857d1"
},
"latest": {
"version": "0.2.4",
"url": "http://www.example.com/game_0.2.4.zip",
"md5": "8059891b6663315a72a954c282a963c8"
},
"updateOlderVersions": [
{
"url": "http://www.example.com/game_0.2.4_update.zip",
"md5": "eac5db79ad1fa8f61d20283fef37ad33",
"fromVersion": "0.2.2",
"toVersion": "0.2.3"
}
]
}There are three main objects in the update manifest, the first is the framework object which allows you to specify which version of the MGDF framework your game supports, this allows you to upgrade the framework independently to your own game module and content. The url used for this object should point to an MGDF redistributable zip file (such as those included in the SDK)
The other two objects are the latest object which should point to a zipped game package (more on how to create these packages shortly). Finally the updateOlderVersions is an optional object which lists an array of zipped partial update packages that include only the files to upgrade from a particular version (or range of versions) to the version specified in the Latest object.
As mentioned above, in order to provide updates to your game, it has to be packaged into an update package. There are two types of update packages, full, and partial. A full update package is just a zip file containing everything in the /game folder whereas a partial update package contains only files which are different between two full update packages.
In order to make it easier to create these packages, the SDK includes a tool that can generate either full or partial update packages. In the simplest case of creating a full update package, the usage is as shown below (for more information see the API reference)
PackageGen "framework /game folder" -o "game package file"
NOTE The program output includes the version number and MD5 hash of the generated package, which you can use to fill out the field information in the game update manifest.
MGDF also has the ability to record and upload statistics from users play sessions. The user is prompted if a game wishes to gather statistics and can allow or deny the statistics gathering.
If permitted, statistics are recorded as key/value pairs. These pairs also include a timestamp which represents the time in seconds since that game session began. All statistics gathered in a game session also include a unique non-identifiable session identifier id so that all the statistics that were posted in a given session can be correlated.
In order to enable statistics tracking you need to add the following properties to your game.json file
You can then upload statistics (as key value pairs) using the IMGDFStatisticsManager API. The GamesManager will upload any statistics recorded once a users play session has ended.