C#
Overview
Outputs source code that uses Static Game Data, along with a binary file (basic.bytes) and binary-loading code.
How to Use
Initial Setup
- In the settings file, add
CSharptooutput_format. - In Unity, open Tools > Wiener > Open Converter, then select the settings YAML, data destination, and C# source destination.
Use a generated-only C# source destination
After a successful run, Converter recursively deletes the entire C# source destination and replaces it with generated code. Keep hand-written code outside it and use a dedicated folder such as Assets/Scripts/WienerGenerated. See Converter for details.
Steps
- Run the conversion that corresponds to the settings you want to use.
- When YAML or the data structure changes, regenerate the C# source and data from the same conversion result.
- In Unity, load the data with the generated code and
WienerManager.
Example
- When
basic.bytesis placed in the Resources folder
using UnityEngine;
using Wiener;
public class WienerSample : MonoBehaviour
{
public WienerManager Master { get; private set; } = new WienerManager();
void Start()
{
var textAsset = Resources.Load("basic") as TextAsset;
Master.LoadBasicPack(textAsset.bytes);
foreach (var v in Master.BasicList)
{
Debug.Log($"Id:{v.Id} Detail:{v.Detail}");
}
}
}
Output

Output Format Based on YAML Header Definition
SourceType
SourceType specifies how to output Static Game Data.
Normal
Output data classes and read data from basic.bytes.
Enum
Output in enum format.
Const
Output constants; data is embedded in source code.
ConstSource
Output constant files; data is embedded in source code.
ContainerType
ContainerType specifies the output data format.
List
Output data in array format.
value[] Name;
Dictionary
Output data in dictionary format.
The first key in Fields becomes the dictionary key.
Dictionary<key, value> Name;
DictionaryList
Output data where the value is an array in dictionary format.
The first key in Fields becomes the dictionary key.
Dictionary<key, value[]> Name;
WienerDictionary
It behaves like a Dictionary and searches sorted data by binary search.
Its memory structure is comparable to a List, but lookup is slower than Dictionary because it uses binary search.
The first key in Fields becomes the dictionary key.
WienerDictionary<key, value> Name;
WienerDictionaryList
It behaves like a DictionaryList and searches sorted data by binary search.
Its memory structure is comparable to a 2D List, but lookup is slower than DictionaryList because it uses binary search.
The first key in Fields becomes the dictionary key.
WienerDictionaryList<key, value> Name;
How to Use the C# Source Code
All loading and data access is performed through the WienerManager class.
Loading Static Game Data
LoadBasicPack
Load basic.bytes.
Specify a file path or binary buffer.
If there is a mismatch between the source and data at conversion time, loading fails.
Result LoadBasicPack(string path);
Result LoadBasicPack(byte[] buffer);
Runtime Data Trust Boundary
Load only data produced by Wiener through a trusted build pipeline. Checks in LoadBasicPack and hash matching in LoadBasicPackFromHash do not establish the safety or authenticity of an attacker-controlled file. If you distribute data through a CDN or remote update, add signature verification, size limits, and a safe failure fallback in the consuming project.
LoadBasicPackFromHash
Debug-only load method.
Load basic.bytes.
Loading is slower than LoadBasicPack.
Specify a file path or binary buffer.
Even if there is a mismatch between source and data at conversion time, loading succeeds.
Data that exists in the binary but not in the source is ignored. Source fields missing from the binary use default values; missing obfuscated fields may not contain usable values. Treat reported mismatches as incomplete development data and do not use that result for a production release.
You can get the list of masters that failed to load from onMissingTargets.
By registering a delegate with WienerManager.SetMissingDelegate, you can detect access to masters that failed to load.
Result LoadBasicPackFromHash(string path, Action<string[]> onMissingTargets);
Result LoadBasicPackFromHash(byte[] buffer, Action<string[]> onMissingTargets);
Accessing Static Game Data
After Static Game Data is loaded, you can access it from each property of WienerManager.
Properties are generated from Header.Name in the Header Definition.
The following sample shows the properties generated in WienerManager when BasicDictionary and BasicDictionaryList are created.
Definition YAML
header:
...
source_type: Normal
container_type: Dictionary
name: BasicDictionary
...
header:
...
source_type: Normal
container_type: DictionaryList
name: BasicDictionaryList
...
Generated WienerManager.cs
public partial class WienerManager
{
...
public Dictionary<int, BasicDictionaryData> BasicDictionary { get { if (BasicDictionary__ == null) onMissing?.Invoke("BasicDictionary"); return BasicDictionary__; }}
public Dictionary<int, BasicDictionaryListData[]> BasicDictionaryList { get { if (BasicDictionaryList__ == null) onMissing?.Invoke("BasicDictionaryList"); return BasicDictionaryList__; }}
...
}
Other
Receive notifications for unexpected obfuscated-field changes
Register a delegate with SetFalsifyDelegate to receive a notification when an obfuscated field changes unexpectedly after loading. This notification does not guarantee cryptographic tamper prevention or authenticity. See Lightweight Obfuscation & Change Detection for the security boundaries.
void SetFalsifyDelegate(Action<string> onFalsify);
Detecting access to mismatched Static Game Data at load time
If you register a delegate with the SetMissingDelegate method, when you load with LoadBasicPackFromHash, you can detect access to Static Game Data that could not be loaded correctly.
void SetMissingDelegate(Action<string> onMissing);
C# Classes
WienerDictionary Class
WienerDictionary<TKey, TValue> is a dictionary-like container for Unity that searches sorted data by binary search.
Like Dictionary<TKey, TValue>, the indexer throws KeyNotFoundException when the key does not exist. Enumeration returns KeyValuePair<TKey, TValue>.
namespace Wiener
{
public class WienerDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : IComparable
where TValue : IWienerDictionaryData<TKey>, new()
{
public int Count => values.Length;
public IEnumerable<TKey> Keys { get; }
public IEnumerable<TValue> Values { get; }
public TValue this[TKey key] { get; }
public WienerDictionary(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, bool hash = false);
public bool ContainsKey(TKey key);
public bool TryGetValue(TKey key, out TValue value);
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
}
}
WienerDictionaryList Class
WienerDictionaryList<TKey, TValue> is a dictionary-like container for grouped values in Unity. Its behavior is almost the same as Dictionary<TKey, TValue[]>; it searches sorted data by binary search and stores TValue[][] internally.
Like Dictionary<TKey, TValue[]>, the indexer throws KeyNotFoundException when the key does not exist. Enumeration returns KeyValuePair<TKey, TValue[]>.
namespace Wiener
{
public class WienerDictionaryList<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue[]>>
where TKey : IComparable
where TValue : IWienerDictionaryData<TKey>, new()
{
public int Count => values.Length;
public IEnumerable<TKey> Keys { get; }
public IEnumerable<TValue[]> Values { get; }
public TValue[] this[TKey key] { get; }
public WienerDictionaryList(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, bool hash = false);
public bool ContainsKey(TKey key);
public bool TryGetValue(TKey key, out TValue[] value);
public IEnumerator<KeyValuePair<TKey, TValue[]>> GetEnumerator();
}
}
WienerManager Class
namespace Wiener
{
public class WienerManager
{
/// <summary>
/// Loads basic.bytes
/// </summary>
/// <param name="path">Specify the path to basic.bytes</param>
/// <returns></returns>
public Result LoadBasicPack(string path);
/// <summary>
/// Loads basic.bytes
/// </summary>
/// <param name="buffer">Specify the binary data of basic.bytes</param>
/// <returns></returns>
public Result LoadBasicPack(byte[] buffer);
/// <summary>
/// Loads basic.bytes
///
/// This method is intended for development
/// Because values are read in generated-code field order, the source and binary must remain in sync,
/// but during development the source and binary can become out of sync
/// When using LoadBasicPackFromHash, it ignores mismatched data and succeeds in loading
/// onMissingTargets returns a list of masters that were mismatched during loading, so use it for logging, etc.
/// Also, by registering a delegate with SetMissingDelegate, you can detect access to mismatched masters
///
/// </summary>
/// <param name="path">Specify the path to basic.bytes</param>
/// <param name="onMissingTargets">Delegate that receives the load-failure list</param>
/// <returns></returns>
public Result LoadBasicPackFromHash(string path, Action<string[]> onMissingTargets);
/// <summary>
/// Loads basic.bytes
///
/// Binary-loading version of LoadBasicPackFromHash
///
/// </summary>
/// <param name="buffer">Specify the binary data of basic.bytes</param>
/// <param name="onMissingTargets">Delegate that receives the load-failure list</param>
/// <returns></returns>
public Result LoadBasicPackFromHash(byte[] buffer, Action<string[]> onMissingTargets);
/// <summary>
/// Register a delegate for unexpected changes to obfuscated fields
///
/// If you register a delegate with the SetFalsifyDelegate method,
/// it is called when an obfuscated field changes unexpectedly after loading
///
/// </summary>
/// <param name="onFalsify">Delegate notified of unexpected changes</param>
public void SetFalsifyDelegate(Action<string> onFalsify);
/// <summary>
/// Register detection for accessing unloaded Static Game Data
///
/// If you register a delegate with the SetMissingDelegate method,
/// when you load with LoadBasicPackFromHash, you can detect access to masters that could not be loaded
///
/// </summary>
/// <param name="onMissing">Specify the delegate for detecting access to unloaded Static Game Data</param>
public void SetMissingDelegate(Action<string> onMissing);
}
}
Example
Basic/Basic.xlsm#Dictionary
| A | B | C |
|---|---|---|
| Id | Name | |
| 1 | Rice | |
| 2 | Bread | |
| 3 | Pasta |
yaml
header:
input_path:
- path: Basic/Basic.xlsm
sheet: 辞書
row: 1
base_type: Normal
source_type: Normal
container_type: Dictionary
output_path: Basic
name: BasicDictionary
fields:
- key: Id
name: id
type: Int
- key: Name
name: name
type: String
...
Output C#
Because the source code does not include the Static Game Data values, you need to load basic.bytes.
/// <summary>
/// Automatically generated code
/// </summary>
using System;
using System.IO;
using System.Collections.Generic;
namespace Wiener
{
public partial class BasicDictionaryData
{
public int Id { get; private set; }
public string Name => stringList[NameStringIndex].Value;
public uint GroupId { get; private set; }
WienerString[] stringList;
uint NameStringIndex;
public static (Dictionary<int, BasicDictionaryData>, bool) Create(string path, WienerString[] stringList, DateTime?[] dateTimeList)
{
var buffer = File.ReadAllBytes(path);
var reader = new WienerDataReader(buffer);
return Create(reader, stringList, dateTimeList);
}
public static (Dictionary<int, BasicDictionaryData>, bool) Create(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList)
{
reader.GetKeyHashTable(false);
var length = reader.ReadInt32();
var value = new Dictionary<int, BasicDictionaryData>(length);
for (var i = 0; i < length; ++i)
{
var key = reader.ReadInt32();
var val = new BasicDictionaryData();
val.Load(reader, stringList, dateTimeList);
value.Add(key, val);
}
return (value, false);
}
public static (Dictionary<int, BasicDictionaryData>, bool) CreateFromHash(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, List<string> errorList)
{
var error = !reader.HashSeek(Hash);
if (error)
{
errorList.Add("BasicDictionaryData");
}
var hashTable = reader.GetKeyHashTable(true);
var length = reader.ReadInt32();
var value = new Dictionary<int, BasicDictionaryData>(length);
for (var i = 0; i < length; ++i)
{
var key = reader.ReadInt32();
var val = new BasicDictionaryData();
val.Load(reader, hashTable, stringList, dateTimeList);
value.Add(key, val);
}
return (value, error);
}
void Load(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList)
{
this.stringList = stringList;
Id = reader.ReadInt32();
NameStringIndex = reader.ReadUInt32();
GroupId = reader.ReadUInt32();
}
private static readonly string Hash = "04ac9e6b16032778741f344174dfc928";
static readonly Dictionary<string, Action<BasicDictionaryData, WienerDataReader>> HashLoadDict;
static BasicDictionaryData()
{
HashLoadDict = new Dictionary<string, Action<BasicDictionaryData, WienerDataReader>>()
{
["462479fdd1ee4f66f2488ac46a489fff"] = LoadId,
["e3a369b99caa37cd8990e202315e8eff"] = LoadName,
["f9f3f95ae38a75495795f08f7dfdec99"] = LoadGroupId,
};
}
public void Load(WienerDataReader reader, List<(string hash, byte type)> keyHashTable, WienerString[] stringList, DateTime?[] dateTimeList)
{
this.stringList = stringList;
foreach (var v in keyHashTable)
{
if (HashLoadDict.TryGetValue(v.hash, out var action))
{
action(this, reader);
}
else
{
reader.SkipReader(v.type);
}
}
}
static void LoadId(BasicDictionaryData self, WienerDataReader reader)
{
self.Id = reader.ReadInt32();
}
static void LoadName(BasicDictionaryData self, WienerDataReader reader)
{
self.NameStringIndex = reader.ReadUInt32();
}
static void LoadGroupId(BasicDictionaryData self, WienerDataReader reader)
{
self.GroupId = reader.ReadUInt32();
}
}
}