WienerWiener
  • Introduction
  • Compatibility
  • Release Notes
  • Comparison
  • Pricing
  • FAQ
  • Use Cases
  • License
  • Unity Asset vs Standalone
  • Lightweight Obfuscation & Change Detection
  • Mismatch Detection & Debug
  • Localization
  • Versioning
  • Google Sheets Input
  • Excel
  • YAML
  • Settings
  • Data Format
  • ValueOnly Format
  • Startup
  • Samples
  • Converter
  • YAML Editor
  • Setting Editor
  • 日本語
  • English
  • Introduction
  • Compatibility
  • Release Notes
  • Comparison
  • Pricing
  • FAQ
  • Use Cases
  • License
  • Unity Asset vs Standalone
  • Lightweight Obfuscation & Change Detection
  • Mismatch Detection & Debug
  • Localization
  • Versioning
  • Google Sheets Input
  • Excel
  • YAML
  • Settings
  • Data Format
  • ValueOnly Format
  • Startup
  • Samples
  • Converter
  • YAML Editor
  • Setting Editor
  • 日本語
  • English
  • Data Format

    • Data Formats
    • C#
    • Json
    • PHP
    • SQL
    • CSV
    • Manual (HTML format)

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

  1. In the settings file, add CSharp to output_format.
  2. 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

  1. Run the conversion that corresponds to the settings you want to use.
  2. When YAML or the data structure changes, regenerate the C# source and data from the same conversion result.
  3. In Unity, load the data with the generated code and WienerManager.

Example

  • When basic.bytes is 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

Unity Console output from loading C# Static Game Data

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

ABC
IdName
1Rice
2Bread
3Pasta

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();
		}
	}
}
Last Updated:: 8/9/26, 11:18 PM
Prev
Data Formats
Next
Json