+
+**Yarhl** is a set of libraries that helps to **implement and convert file
+formats**. It provides a virtual file system, format conversion APIs, full
+featured binary IO and plugin support to support common formats. It's built in
+**C# / .NET** and works in any OS that supports the .NET runtime.
+
+- :books: **Format implementation** architecture and guidelines
+- :recycle: **Format conversion** API
+- :open_file_folder: **Virtual file system** with format transformations
+- :1234: **Enhanced binary IO API**
+ - Custom `Stream` with **sub-stream supports** (memory and disk efficient!)
+ - Full feature binary and text readers and writers
+ - Simple binary (de)serializer by attributes in the model.
+- :page_with_curl: Standard text formats
+ - Industry-standard localization format: **GNU gettext PO**
+ - Table text replacements
+ - **Common encodings**: euc-jp, token-escaped encoding
+ - **API for simple encoding implementations**
+
+## Get started
+
+Check out the [documentation site](https://scenegate.github.io/Yarhl/index.html)
+to start learning the power of _Yarhl_.
Feel free to ask any question in the
-[project Discussion site!](https://github.com/SceneGate/Yarhl/discussions).
-
-Check our on-line API overview:
-[Yarhl in a nutshell](https://scenegate.github.io/Yarhl/guides/Yarhl-nutshell.html)
-and the complete API documentation
-[here](https://scenegate.github.io/Yarhl/api/Yarhl.html).
+[project discussions](https://github.com/SceneGate/Yarhl/discussions).
-## Install
+## Usage
-Stable releases are available from nuget.org:
+This project provides the following libraries as NuGet packages (via nuget.org).
+The libraries support the latest version of .NET and its LTS.
-- [Yarhl](https://www.nuget.org/packages/Yarhl)
-- [Yarhl.Media.Text](https://www.nuget.org/packages/Yarhl.Media.Text)
+- [![Yarhl](https://img.shields.io/nuget/v/Yarhl?label=Yarhl&logo=nuget)](https://www.nuget.org/packages/Yarhl):
+ core, format conversion, file system and binary reading / writing (IO).
+- [![Yarhl.Media.Text](https://img.shields.io/nuget/v/Yarhl.Media.Text?label=Yarhl.Media.Text&logo=nuget)](https://www.nuget.org/packages/Yarhl.Media.Text):
+ text formats (Po) and encodings.
+- [![Yarhl.Plugins](https://img.shields.io/nuget/v/Yarhl.Plugins?label=Yarhl.Plugins&logo=nuget)](https://www.nuget.org/packages/Yarhl.Plugins):
+ discover formats and converters from .NET assemblies.
-The libraries only support the latest version of .NET and its LTS (.NET 6).
-
-Preview releases can be found in this
+**Preview releases** can be found in this
[Azure DevOps package repository](https://dev.azure.com/SceneGate/SceneGate/_packaging?_a=feed&feed=SceneGate-Preview).
To use a preview release, create a file `nuget.config` in the same directory of
-your solution (.sln) file with the following content:
+your solution file (.sln) with the following content:
```xml
+
+
+
+
+
+
+
+
+
+
```
-## Build
+Then restore / install as usual via Visual Studio, Rider or command-line. You
+may need to restart Visual Studio for the changes to apply.
+
+## Showcase
+
+Some cool projects built with _Yarhl_:
-The project requires to build .NET 6.0 SDK and .NET Framework 4.8 or latest
-Mono. If you open the project with VS Code and you did install the
+- [**Texim**](https://github.com/SceneGate/Texim): experimental API for image
+ file formats.
+- [**Ekona**](https://scenegate.github.io/Ekona/): support Nintendo DS file
+ formats.
+- [**Lemon**](https://scenegate.github.io/Lemon/): support Nintendo 3DS file
+ formats.
+- [**LayTea**](https://www.pleonex.dev/LayTea/): modding tools for _Professor
+ Layton_ games.
+- [**Attack of Friday Monsters tools**](https://github.com/pleonex/AttackFridayMonsters):
+ modding tools for _Attack of the Friday Monsters_ game.
+- [**Metatron**](https://github.com/TraduSquare/Metatron): translation framework
+ for _Shin Megami Tensei_ saga games.
+
+## Contributing
+
+The repository requires to build .NET 6.0 SDK and .NET Framework 4.8 or latest
+Mono (for DocFX). If you open the project with VS Code and you did install the
[VS Code Remote Containers](https://code.visualstudio.com/docs/remote/containers)
extension, you can have an already pre-configured development environment with
Docker or Podman.
@@ -60,7 +122,7 @@ To build, test and generate artifacts run:
# Only required the first time
dotnet tool restore
-# Default target is Stage-Artifacts
+# Default target is "Default" that builds, runs tests, build doc and create the NuGets
dotnet cake
```
@@ -69,3 +131,13 @@ To just build and test quickly, run:
```sh
dotnet cake --target=BuildTest
```
+
+Additionally you can use _Visual Studio_ or _JetBrains Rider_ as any other .NET
+project.
+
+To contribute follow the [contributing guidelines](CONTRIBUTING.md).
+
+## License
+
+The software is licensed under the terms of the
+[MIT license](https://choosealicense.com/licenses/mit/).
diff --git a/build.cake b/build.cake
index 08d0bba8..83401498 100644
--- a/build.cake
+++ b/build.cake
@@ -11,9 +11,31 @@ Task("Define-Project")
info.AddTestProjects("Yarhl.UnitTests");
info.AddTestProjects("Yarhl.IntegrationTests");
+ info.ChangelogFile = "docs/articles/Changelog.md";
+
info.PreviewNuGetFeed = "https://pkgs.dev.azure.com/SceneGate/SceneGate/_packaging/SceneGate-Preview/nuget/v3/index.json";
});
+Task("DocFx-BuildDoc")
+ .Does(info =>
+{
+ if (!FileExists(info.DocFxFile)) {
+ Warning("There isn't documentation.");
+ return;
+ }
+
+ string args = $"-o {info.ArtifactsDirectory}/_site";
+ if (info.WarningsAsErrors) {
+ args += " --warningsAsErrors";
+ }
+
+ DotNetTool($"docfx {info.DocFxFile} {args}");
+
+ Zip(
+ $"{info.ArtifactsDirectory}/_site",
+ $"{info.ArtifactsDirectory}/docs.zip");
+});
+
Task("Prepare-IntegrationTests")
.Description("Prepare the integration tests by copying an example of DLL")
.IsDependeeOf("Test")
@@ -53,7 +75,13 @@ public IEnumerable GetTargetFrameworks(string projectPath)
}
Task("Default")
- .IsDependentOn("Stage-Artifacts");
+ .IsDependentOn("Stage-Artifacts-NewDocs");
+
+Task("Stage-Artifacts-NewDocs")
+ .IsDependentOn("BuildTest")
+ .IsDependentOn("DocFx-BuildDoc")
+ .IsDependentOn("Pack-Libs")
+ .IsDependentOn("Pack-Apps");
string target = Argument("target", "Default");
RunTarget(target);
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..83cc63a9
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,12 @@
+###############
+# folder #
+###############
+/**/DROP/
+/**/TEMP/
+/**/packages/
+/**/bin/
+/**/obj/
+_site
+
+# DrawIO
+.$*.drawio*
diff --git a/docs/api/.gitignore b/docs/api/.gitignore
new file mode 100644
index 00000000..e8079a3b
--- /dev/null
+++ b/docs/api/.gitignore
@@ -0,0 +1,5 @@
+###############
+# temp file #
+###############
+*.yml
+.manifest
diff --git a/docs/dev/Changelog.md b/docs/articles/Changelog.md
similarity index 100%
rename from docs/dev/Changelog.md
rename to docs/articles/Changelog.md
diff --git a/docs/articles/core/binary/attr-serialization.md b/docs/articles/core/binary/attr-serialization.md
new file mode 100644
index 00000000..0914beaa
--- /dev/null
+++ b/docs/articles/core/binary/attr-serialization.md
@@ -0,0 +1,3 @@
+# (De)serialization via attributes
+
+TODO
diff --git a/docs/articles/core/binary/binaryformat.md b/docs/articles/core/binary/binaryformat.md
new file mode 100644
index 00000000..e39fbfd8
--- /dev/null
+++ b/docs/articles/core/binary/binaryformat.md
@@ -0,0 +1,3 @@
+# Binary format
+
+TODO
diff --git a/docs/articles/core/binary/binaryreader-writer.md b/docs/articles/core/binary/binaryreader-writer.md
new file mode 100644
index 00000000..67a5bf70
--- /dev/null
+++ b/docs/articles/core/binary/binaryreader-writer.md
@@ -0,0 +1,3 @@
+# Binary reader and writer
+
+TODO
diff --git a/docs/articles/core/binary/custom-streams.md b/docs/articles/core/binary/custom-streams.md
new file mode 100644
index 00000000..fb37f0f0
--- /dev/null
+++ b/docs/articles/core/binary/custom-streams.md
@@ -0,0 +1,3 @@
+# Custom streams
+
+TODO
diff --git a/docs/articles/core/binary/datastream.md b/docs/articles/core/binary/datastream.md
new file mode 100644
index 00000000..55525a66
--- /dev/null
+++ b/docs/articles/core/binary/datastream.md
@@ -0,0 +1,276 @@
+# DataStream
+
+[`DataStream`](xref:Yarhl.IO.DataStream) is an enhanced version of .NET
+[`Stream`](xref:System.IO.Stream) class. It inherits `Stream`, being compatible
+with any existing API that uses `Stream` as input.
+
+Its main feature is the ability to create thread-safe **_sub-streams_**. It
+allows to work with a segment of a `Stream` without copying or allocating any
+memory.
+
+## Sub-streams
+
+A _sub-stream_ is _view_ of a part of a regular **Stream**. Just like in .NET
+[`Span`](xref:System.Span`1) allows to work with a segment of an array,
+`DataStream` allows to do it with a `Stream` but keeping the same API as any
+other regular `Stream`.
+
+When you create a `DataStream` from another `Stream` you can specify its
+_offset_ and _length_ to perform IO operations (reading / writing) only in that
+segment.
+
+Let's imagine we have a binary format for a container (like a _zip_ or _tar_
+file without compression). In this format the content for the first file is in
+the position `0x100`. The content for the second file is at `0x3C0`.
+
+![Container format with a header and 3 files inside](./images/datastream-container.drawio.png)
+
+If we want to only work with the data of the second file, we could create a
+_sub-stream_ that starts at `0x3C0` and has the file length. It will allow to
+have a `Stream` object based on the parent `Stream`. We can now pass this new
+stream to any other API, for instance to a JSON deserializer to read its
+content, without having to _export_ or do any prior copy.
+
+![Container format with assigned substreams to each file](./images/datastream-container-substreams.drawio.png)
+
+Another use case is reading a binary format with sections. By creating a
+`DataStream` for each individual section, the application can have a design more
+modular and safe. It would prevent reading data outside the range of the
+section.
+
+## Factory
+
+The constructors of `DataStream` takes a `Stream` with optional offset and
+lengths for _sub-streams_. You can also create a `DataStream` from other source
+via the [`DataStreamFactory`](xref:Yarhl.IO.DataStreamFactory).
+
+It can quickly initialize a new `DataStream` from memory:
+
+```csharp
+using var stream = DataStreamFactory.FromMemory();
+```
+
+> [!TIP]
+> Internally it uses the memory-efficient API from Microsoft:
+> [`Microsoft.IO.RecyclableMemoryStream`](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream).
+> It keeps a range of buffers on memory allowing to re-use them without having
+> to allocate memory twice. It may however consume more memory even when it's
+> not used.
+
+or from an array of bytes:
+
+```csharp
+byte[] dataBuffer = ...
+
+using var stream = DataStreamFactory.FromArray(dataBuffer);
+```
+
+or from an existing or new file:
+
+```csharp
+using var stream = DataStreamFactory.FromFile("input/file.bin", FileOpenMode.Read);
+
+// or even a part of a file: only 0x40 bytes from 0x100.
+using var stream = DataStreamFactory.FromFile("input/file.bin", FileOpenMode.Read, 0x100, 0x40);
+```
+
+where [`FileOpenMode`](xref:Yarhl.IO.FileOpenMode) is an enumeration that
+internally maps to the .NET enumerations `FileMode` and `FileAccess` as follow:
+
+- `FileOpenMode.Read`: read a file, throwing an exception if it doesn't exists.
+ Maps to `FileMode.Open` and `FileAccess.Read`.
+- `FileOpenMode.Write`: write a file, creating a new if it doesn't exist or
+ truncating it it does. Maps to `FileMode.Create` and `FileAccess.Write`.
+- `FileOpenMode.ReadWrite`: allow to read and write to a file. If it doesn't
+ exist it will create a new one but it does **not truncate if the file exists**
+ (to allow reading). Maps to `FileMode.OpenOrCreate` and
+ `FileAccess.ReadWrite`.
+- `FileOpenMode.Append`: append data to the end, if it doesn't exist it will
+ throw an exception. Maps to `FileMode.Append` and `FileAccess.Write`
+
+> [!NOTE]
+> The file is _lazily open_. This means it will not actually open the file until
+> the first read or write operation happens on the `Stream`. It allows having
+> hundreds of `DataStream` on different files ready without running out of
+> resources in the operative system.
+
+> [!TIP]
+> `FileOpenMode` is handy enumeration that covers most use cases. If you require
+> any other combination of `FileMode` and `FileAccess` you can create the
+> `FileStream` by hand and pass it to the `DataStream` constructor or
+> `FromStream` methods.
+
+Finally the factory also provides the methods
+[`FromStream`]()
+similar to the parameters accepted by the constructor of
+[`DataStream`](xref:Yarhl.IO.DataStream).
+
+## IO operations
+
+`DataStream` supports the same read and write operations as any other standard
+.NET `Stream`. It also provides some additional methods.
+
+### Writing to a file
+
+The [`WriteTo`]() API allows to
+write the entire content of the stream into a file on the given path. This
+applies to the content that targets this `DataStream`, not the entire parent
+`Stream`.
+
+> [!NOTE]
+> The method ignores the current position, it will always start writing from the
+> start to the end. It will **restore** the current position after comparing.
+
+The path should point to the output file. If there is any directory that doesn't
+exist, it will create them first.
+
+```csharp
+using var stream = new DataStream(...);
+// ...
+stream.Position = 80;
+// ...
+
+// Write the entire Stream from 0 to the end.
+// It creates the folder 'output' if it doesn't exist.
+stream.WriteTo("output/file.bin");
+```
+
+### Comparing data
+
+The method [`Compare`]()
+allows to compare byte-to-byte the content of the current stream against
+another. If will return `false` if any byte between the two streams are
+different. It will also return `false` if the length does not match.
+
+> [!NOTE]
+> The method ignores the current position. It will always start reading **both
+> Streams** from the start to the end. It will **restore** the current position
+> after comparing.
+
+```csharp
+using var myStream = new DataStream(...);
+myStream.Position = 80;
+
+using var otherFile = new FileStream(...);
+
+// Compare both streams from 0 to end
+bool result = myStream.Compare(otherFile);
+```
+
+### Positions stack
+
+Inspired by the _Unix commands `pushd` and `popd`_, `DataStream` provides a set
+of APIs that simplifies the use cases of changing position to read / write some
+data and go back.
+
+For instance, imagine the format has a table with file names in a separate
+section. We could quickly read the string without having to store the current
+position:
+
+```csharp
+stream.Position = 0x100;
+
+// Save current position and move to 0x800
+stream.PushToPosition(0x800);
+string name = ReadFilename(stream);
+stream.PopPosition(); // return to 0x100, keep reading from there
+```
+
+[`PushToPosition`]()
+will move to the given position, saving the current position in an internal
+stack. Calling [`PopPosition`](xref:Yarhl.IO.DataStream.PopPosition) will return
+to our original position.
+
+The class also provides
+[`PushCurrentPosition`](xref:Yarhl.IO.DataStream.PushCurrentPosition). It saves
+the current position into the stack. For instance, before calling an external
+method that may modify our position.
+
+Finally, the method
+[`RunInPosition`]()
+allows to run an action such as a lambda expression or a method saving and
+restoring our current position. We could re-write the previous example as:
+
+```csharp
+stream.Position = 0x100;
+
+string name;
+stream.RunInPosition(() => name = ReadFilename(stream), 0x800);
+```
+
+### Writing segments
+
+The
+[`WriteSegmentTo`]()
+APIs allows to write a part of our `DataStream` into `Stream` or file on disk.
+This is a shortcut to create a temporary `DataStream` and run the regular
+`WriteTo` method.
+
+```csharp
+var stream = new DataStream(...);
+
+// Write starting at 0x100 to the end
+stream.WriteSegmentTo(0x100, otherStream);
+stream.WriteSegmentTo(0x100, "outputs/data.bin");
+
+// Write 0x40 bytes starting at 0x800
+stream.WriteSegmentTo(0x800, 0x40, otherStream);
+stream.WriteSegmentTo(0x800, 0x40, "outputs/data.bin");
+```
+
+### Closing / Disposing
+
+By default a `DataStream` takes the _ownership_ of the base `Stream` object
+passed. By disposing the `DataStream` it will dispose as well its parent
+`Stream`.
+
+If we are working with _sub-streams_ the behavior is different. The API will
+keep a **count of `DataStream` using a `Stream` object.** Disposing a
+`DataStream` will decrease the count. Creating a new `DataStream` (via
+constructor or factory) will increase the count. When the count reaches 0 in a
+`Dispose` object, it will also dispose its parent.
+
+In the above format example, disposing the `DataStream` for file 1 and 2 will
+not still dispose its parent `FileStream`, as we have still one for file 80.
+Calling `Dispose` for the `DataStream` of file 80, will dispose the `FileStream`
+as well.
+
+```csharp
+var parentStream = new FileStream(...);
+
+var file1Stream = new DataStream(parentStream, 0x100, 0x2C0);
+var file2Stream = new DataStream(parentStream, 0x3C0, 0x80);
+var file80Stream = new DataStream(parentStream, 0x8400, 0x100);
+
+file1Stream.Dispose();
+file2Stream.Dispose();
+
+// parentStream is still open. We can do read and write from it or file80Stream
+
+file80Stream.Dispose(); // <-- It will also dispose parentStream
+```
+
+You can control this behavior by using the
+[constructor that takes the argument `bool transferOwnership`]().
+Pass a `false` value to never dispose the parent `Stream`.
+
+> [!TIP]
+> You can get the total number of `DataStream` created via the static property
+> [`ActiveStreams`](xref:Yarhl.IO.DataStream.ActiveStreams).
+
+## Thread-safety
+
+The `DataStream` is thread-safe at the level of the sub-stream.
+
+For instance, it is safe to use several `DataStreams` over the same base
+`Stream` in parallel. `DataStream` will wait for their turn to use the parent
+`Stream` for each IO operation. Ensure they are at the required position always.
+
+The type **is not thread-safe for its methods**. For instance, it is **NOT safe
+to use the same `DataStream`** in different threads at the same time. It's not
+safe to change the position of the `DataStream` in one thread while another is
+reading. **Please create a new `DataStream` for each thread.**
+
+The thread-safety is based in that there isn't any other part of the application
+accessing to the parent `Stream` directly. Every IO operation should happen over
+a `DataStream`.
diff --git a/docs/articles/core/binary/images/datastream-container-substreams.drawio.png b/docs/articles/core/binary/images/datastream-container-substreams.drawio.png
new file mode 100644
index 00000000..e9472de9
Binary files /dev/null and b/docs/articles/core/binary/images/datastream-container-substreams.drawio.png differ
diff --git a/docs/articles/core/binary/images/datastream-container.drawio.png b/docs/articles/core/binary/images/datastream-container.drawio.png
new file mode 100644
index 00000000..fd84acbb
Binary files /dev/null and b/docs/articles/core/binary/images/datastream-container.drawio.png differ
diff --git a/docs/articles/core/binary/textreader-writer.md b/docs/articles/core/binary/textreader-writer.md
new file mode 100644
index 00000000..c7c3ecab
--- /dev/null
+++ b/docs/articles/core/binary/textreader-writer.md
@@ -0,0 +1,3 @@
+# Text reader and writer
+
+TODO
diff --git a/docs/articles/core/formats/cloneable-format.md b/docs/articles/core/formats/cloneable-format.md
new file mode 100644
index 00000000..840ba5e0
--- /dev/null
+++ b/docs/articles/core/formats/cloneable-format.md
@@ -0,0 +1,26 @@
+# Cloneable format
+
+.NET does not provide an interface to guarantee a
+[deep clone](https://learn.microsoft.com/en-us/dotnet/api/system.icloneable?view=net-7.0#remarks)
+implementation.
+
+The [`ICloneableFormat`](xref:Yarhl.FileFormat.ICloneableFormat) gives the
+possibility to a format implementation to specify how it should _deep_ clone its
+data into a new format. This could be as simple as copying its properties into a
+new object or in the case of binary data, copying all its bytes into a new
+stream.
+
+[!code-csharp[cloneable](./../../../../src/Yarhl.Examples/Formats/Formats.cs?name=CloneableFormat)]
+
+The interface already implements `IFormat` so it's not needed to add both.
+
+> [!IMPORTANT]
+> This interface is not required to be implemented by every format but some APIs
+> of the library relies on it. For instance it's only possible to clone a
+> [node via its constructor]()
+> if it has a format that implements
+> [`ICloneableFormat`](xref:Yarhl.FileFormat.ICloneableFormat).
+
+> [!TIP]
+> The built-in formats from _Yarhl_ implements
+> [`ICloneableFormat`](xref:Yarhl.FileFormat.ICloneableFormat).
diff --git a/docs/articles/core/formats/converters-usecases.md b/docs/articles/core/formats/converters-usecases.md
new file mode 100644
index 00000000..cafee377
--- /dev/null
+++ b/docs/articles/core/formats/converters-usecases.md
@@ -0,0 +1,156 @@
+# Advanced uses cases for converters
+
+The [converters](./converters.md) topic covers the standard use case: _convert
+one format into another_. Often you may run into scenarios a bit more advanced.
+The following sections tries to provide some architecture guidance.
+
+## Convert multiple formats into one
+
+**Requirement**: generate a format from more than one input formats.
+
+Depending on the use case the following patterns could help:
+
+- Implement a converter that converts one format (main) and **use parameters**
+ to pass the additional formats.
+- Use the [import data](#updating--importing-data-in-a-format) pattern.
+
+Let's try the first pattern with a couple of examples:
+
+### Serialize a font file from an image and JSON
+
+We have exported a font information into multiple files: one file containing an
+image with all the font glyphs and a JSON file with the metadata and charset
+map.
+
+In this case we can identify the _main_ format as the JSON structure, as it
+contains most of the information required to create the font. We will pass the
+image as a parameter to be used for the glyphs of the font.
+
+[!code-csharp[Font2Binary example](../../../../src/Yarhl.Examples/Formats/AdvancedConverters.cs?name=ManyToOneFont&highlight=5)]
+
+> [!NOTE]
+> Instead of passing the JSON as binary data, pre-convert it already into its
+> structure / class. It will simplify the implementation of the converter and it
+> could be it can be re-used for more cases (e.g. in the future you decide to
+> support YAML).
+
+### Convert an indexed image with a palette into a RGB image
+
+The _main_ format would be the indexed image as contains more information
+representing the target format. A palette is required to transform the pixel
+indexes into a final RGB color.
+
+[!code-csharp[IndexedImage2RgbImage example](../../../../src/Yarhl.Examples/Formats/AdvancedConverters.cs?name=ManyToOneIndexedImage&highlight=5)]
+
+### Additional patterns for many to one
+
+Below other implementations that may fit some use cases. In our experience they
+don't work as good as the previous mentioned _parameter_ approach.
+
+#### Intermediary types
+
+Create an intermediary type that groups all the required formats to convert. For
+instance you could create a class `IndexedImageWithPalette` to gather the
+`IndexedImage` and `Palette`. Then create a converter for
+`IConverter`.
+
+This may simplify your converter but it can create more complex APIs. Now users
+will need to _convert_ their two formats into this intermediary representation
+before they can use the converter.
+
+It may prevent a _fluent-like_ usage of the converters when used with the
+[node](../virtual-file-system/nodes.md#format-conversion) APIs. It won't allow
+to convert one _node_ passing other _node_ as parameters.
+
+#### Using tuples as input type
+
+This similar to the above case. It has the further limitation that it can't
+evolve over the time. If you need an additional format or parameter in the
+future you will breaking the API for the users making it a bit more messy.
+
+## Convert one format into many
+
+**Requirement**: convert the format into more than one output formats.
+
+Depending on the format you may want to:
+
+- **Convert the format into a container type `NodeContainerFormat`** that
+ contains a child per output format.
+- Create a **separate** converter for each target format.
+
+> [!TIP]
+> Check-out the [node](../virtual-file-system/nodes.md) topic to learn more
+> about containers.
+
+### Convert an RGB image into indexed image and palette
+
+Reverse operation from
+[convert an indexed image with palette into RGB image](#convert-an-indexed-image-with-a-palette-into-a-rgb-image).
+The converter generates a palette and an image with _indexed pixels_. It needs
+to return both formats as they are generated at the same time.
+
+The approach is to return a container that has two nodes: `image` and `palette`.
+
+[!code-csharp[RgbImage2IndexedImage example](../../../../src/Yarhl.Examples/Formats/AdvancedConverters.cs?name=OneToManyIndexedImage)]
+
+We can extract the formats from this container as follow:
+
+[!code-csharp[Using previous converter](../../../../src/Yarhl.Examples/Formats/AdvancedConverters.cs?name=OneToManyIndexedImageProgram)]
+
+### Export a font into information and image
+
+In this case it could be a better approach to separate the converters:
+
+1. A `Font2BinaryInfo` converter that serializes the charset map and other
+ information into JSON / YAML `Stream`.
+2. A `Font2Image` converter that exports the glyphs into an image.
+
+Each converter runs a different process to generate the output. These two output
+formats are not generated at the same time (as it was the case above).
+
+By splitting it allows users to run the one they need when they need it. It may
+not be required to generate an image all the time or vice-versa.
+
+## Convert multiple formats into many
+
+This use case would be covered by the two previous cases: combining converting
+[multiple formats into one](#convert-multiple-formats-into-one) and
+[one format into many](#convert-one-format-into-many).
+
+## Updating / Importing data in a format
+
+Sometimes you may run a process that modifies existing data of a format
+**without creating a new format**.
+
+In these cases we can create a converter that **returns the same input
+instance** after processing. We can pass the data to import as a **parameter**.
+
+### Importing a font file
+
+One example would be importing data from multiple formats over the same object.
+For instance, if we need to _import_ / create a `Font` object from a JSON file
+and an `Image` with the glyphs. We could do this scenario in two steps:
+
+1. One converter that creates the `Font` object from the JSON file: _binary ->
+ Font_ with `IConverter`.
+2. Then, one converter that imports the glyphs images over the same `Font`
+ object: _Font -> Font_ with `IConverter`
+
+The structure of the second converter could look as follow:
+
+[!code-csharp[FontImporter example](../../../../src/Yarhl.Examples/Formats/AdvancedConverters.cs?name=FontImporter)]
+
+### Updating texts of an executable file
+
+Another scenario is changing the text of an unknown or complex binary format
+like an executable. In that case we want to maintain all the existing bytes and
+overwrite the ones containing text with new data.
+
+[!code-csharp[ExecutableTextImporter example](../../../../src/Yarhl.Examples/Formats/AdvancedConverters.cs?name=ExecutableTextImporter)]
+
+> [!TIP]
+> It could be a good idea to create a **new `BinaryFormat` and copy the input
+> before overwriting data**. In that case you would be returning a new binary
+> format but with the existing content. In this way you don't modify the
+> existing file on disk but create a new one in case something wrong happens and
+> you want to run it again.
diff --git a/docs/articles/core/formats/converters.md b/docs/articles/core/formats/converters.md
new file mode 100644
index 00000000..15269146
--- /dev/null
+++ b/docs/articles/core/formats/converters.md
@@ -0,0 +1,187 @@
+# Converters
+
+You can convert a [formats](./formats.md) (model) into another format by using a
+_converter_ class. A _Yarhl converter_ implements the interface
+[`IConverter`](xref:Yarhl.FileFormat.IConverter`2) and provides the
+method [`TDst Convert(TSrc)`]().
+This method creates a new object in the target type _converting_ the data from
+the input.
+
+For instance the converter [`Po2Binary`](xref:Yarhl.Media.Text.Po2Binary)
+implements `IConverter`. It allows to convert a
+[`Po`](xref:Yarhl.Media.Text.Po) model format into a
+[_binary_ format](xref:Yarhl.IO.BinaryFormat). This is also known as
+**serialization**. You can later write this binary data into a file on disk.
+
+In a similar way, the converter [`Binary2Po`](xref:Yarhl.Media.Text.Binary2Po)
+implements `IConverter` to convert binary data into a
+[`Po`](xref:Yarhl.Media.Text.Po) model (also known as _reading_ or
+_deserializing_).
+
+We could have more conversions between formats. For instance
+`IConverter` or `IConverter`. This is sometimes referred
+as _exporting_ and _importing_ formats. _Converters_ simplify all these
+operations by their common denominator: **converting models.**
+
+Let's see how to _serialize_ / convert a _Po_ model into binary data to write on
+disk:
+
+[!code-csharp[serialize PO](./../../../../src/Yarhl.Examples/Formats/Converters.cs?name=SerializePo)]
+
+## Implementing a new converter
+
+To create a new converter, create a new class and implement the interface
+[``](xref:Yarhl.FileFormat.IConverter`2). `TSrc` is the
+type (or base type / interface) you are going to convert into a new object of
+`TDst` type.
+
+> [!NOTE]
+> It is possible to have a class implementing more than one converter at a type.
+> However this can be confusing for the user. Our recommendation is that each
+> class implements only one
+> [`IConverter`](xref:Yarhl.FileFormat.IConverter`2) interface. For
+> instance, create `Po2Binary` and `Binary2Po` instead of just `Binary2Po`
+> having the two implementations.
+
+As an example, let's implement a new converter that reads binary data and
+creates a [container type](../virtual-file-system/nodes.md) (like a file
+system).
+
+First we create a new class for our converter: `BinaryArchive2Container` to do
+the operation _binary data_ -> _container class_ (deserializing).
+
+```csharp
+public class BinaryArchive2Container : IConverter
+{
+ // TODO: Implement interface.
+}
+```
+
+Now let's add the required method `Convert` for the interface.
+
+```csharp
+public NodeContainerFormat Convert(IBinary source)
+{
+ var container = new NodeContainerFormat();
+ // TODO: do something with the source data.
+ return container;
+}
+```
+
+Finally let's read some data to fill the container. This example binary format
+contains a set of binary files inside.
+
+```csharp
+public class BinaryArchive2Container : IConverter
+{
+ public NodeContainerFormat Convert(IBinary source)
+ {
+ // Format: number of files + table with "name + offset + size", then file data.
+ var reader = new DataReader(source.Stream);
+ var container = new NodeContainerFormat();
+
+ int numFiles = reader.ReadInt32();
+ for (int i = 0; i < numFiles; i++)
+ {
+ string name = reader.ReadString(bytesCount: 0x10, encoding: Encoding.UTF8);
+ uint offset = reader.ReadUInt32();
+ uint size = reader.ReadUInt32();
+
+ // Create a sub-stream for the child, a stream from a region
+ // of the parent stream without making any read/write or copies.
+ Node child = NodeFactory.FromSubstream(name, source.Stream, offset, size);
+ container.Root.Add(child);
+ }
+
+ return container;
+ }
+}
+```
+
+And voilร . To use our new converter we just need to create a new instance and
+pass some binary data.
+
+```csharp
+// Convert the binary file into a virtual folder (no disk writing).
+var fileStream = DataStreamFactory.FromFile("myArchive.bin", FileOpenMode.Read);
+using var binaryFormat = new BinaryFormat();
+
+var binary2Container = new BinaryArchive2Container();
+using var container = binary2Container.Convert(binaryFormat);
+
+// Now we can inspect or extract the content of the container
+Node child = container.Children["text.json"]
+child.Stream.WriteTo(child.Name);
+```
+
+## Parameters
+
+Frequently your converter may require additional parameters than just the input
+object to do the conversion. For instance in a compressor you may need to ask
+your users to provide the level of compression to do. Or you may need to know
+the line ending for a text format. You may need to know if the target CPU is big
+or little endian or the text encoding.
+
+In any of these cases, you can ask the user to provide this required or optional
+information in the constructor of the converter class.
+
+> [!TIP]
+> If your converter can run with some _default_ parameters, provide a
+> parameter-less constructor to simplify its usage for common use cases.
+
+```csharp
+public class RgbImage2IndexedImage : IConverter
+{
+ private readonly IColorQuantization quantization;
+
+ // Parameter-less constructors for a default value that can be used in most cases.
+ public RgbImage2IndexedImage()
+ {
+ quantization = new ColorQuantization();
+ }
+
+ // Allow the user to customize the converter to their needs.
+ public RgbImage2IndexedImage(IColorQuantization customQuantization)
+ {
+ quantization = customQuantization;
+ }
+
+ public IndexedImage Converter(RgbImage source)
+ {
+ // Use the quantization instance to convert RGB colors into an indexed image
+ // ...
+ }
+}
+```
+
+## `IConverter` interface
+
+> [!IMPORTANT]
+> Normally the [`IConverter`](xref:Yarhl.FileFormat.IConverter) (no generics
+> version) is for internal use only. Unless writing a new framework or generic
+> tools, use always
+> [`IConverter`](xref:Yarhl.FileFormat.IConverter`2).
+
+You may notice that there is also an
+[`IConverter`](xref:Yarhl.FileFormat.IConverter) interface that takes no
+generics. The [`IConverter`](xref:Yarhl.FileFormat.IConverter`2)
+_implements_ this base interface.
+
+This is an empty interface used only internally to enforce some basic
+type-safety when due to technical reason we can't know the types of the
+converter, so we can't use
+[`IConverter`](xref:Yarhl.FileFormat.IConverter`2).
+
+For instance `Node.TransformWith(IConverter converter)` uses the base interface
+to provide a simple API. Requiring the fully typed interface would make users to
+specify to repeat the types:
+`node.TransformWith(myConverter)` as the compiler
+cannot guess these types at compile-type. By having the simple interface we can
+just use `node.TransformWith(myConverter)`.
+
+Note that when the API uses [`IConverter`](xref:Yarhl.FileFormat.IConverter) it
+will run reflection run-time checks to ensure the argument is valid. It will
+check that the variable or type implements
+[`IConverter`](xref:Yarhl.FileFormat.IConverter`2) and that the
+input object is valid for this type. Although it may hit some nanoseconds of
+performance, it provides better error messages.
diff --git a/docs/articles/core/formats/formats.md b/docs/articles/core/formats/formats.md
new file mode 100644
index 00000000..efd17180
--- /dev/null
+++ b/docs/articles/core/formats/formats.md
@@ -0,0 +1,91 @@
+# Formats
+
+In Yarhl, _formats_ are _.NET classes_ that represents a model that can be
+converted and/or assigned into virtual files (_nodes_).
+
+## Implement a new format
+
+A format is just a regular programming model, a _.NET class_ (or _record_),
+usually with properties and methods. The only requirement to have a _Yarhl
+format-compatible_ is to implement the empty interface
+[`IFormat`](xref:Yarhl.FileFormat.IFormat).
+
+[!code-csharp[format implementation](./../../../../src/Yarhl.Examples/Formats/Formats.cs?name=FormatImpl)]
+
+## Converting formats
+
+> [!NOTE]
+> Check-out the [converters](./converters.md) topic to learn more about them.
+
+The _converters_ classes are responsible to convert one format into a new one.
+To use it, create a new instance and call its
+[`Convert(source)`]() method.
+
+[!code-csharp[serialize PO](./../../../../src/Yarhl.Examples/Formats/Converters.cs?name=SerializePo)]
+
+### Fluent API
+
+An easier way, it's to use the extension method on formats
+`ConvertWith(converter)`. As it returns the new format, it allows to _chain
+conversions_:
+
+```csharp
+FullImage fontImage = binaryFont
+ .ConvertWith(new Binary2Font(FontKind.Debug)) // binary -> font model
+ .ConvertWith(new Font2Image()); // font -> image
+```
+
+### Converting without knowing converter at compile-time
+
+Sometimes the application doesn't know the converter type at compile-time. This
+could be the case of generic tools that loads assemblies in a plugin-style and
+select the converter type via configuration file or user interface.
+
+The static class [`ConvertFormat`](xref:Yarhl.FileFormat.ConvertFormat) provides
+the APIs to convert formats by passing its type object. The API uses reflection
+to validate the converter type and its arguments so that it throws an exception
+when:
+
+- the type does not implement `IConverter`
+- the converter cannot convert the type of the input.
+- the parameters does not match any constructor signature.
+
+```csharp
+object inputFormat;
+
+// UI / config file loaded from same or external assembly
+Type converterType;
+object[] converterArgs;
+
+object outputFormat = ConvertFormat.With(converterType, inputFormat, converterArgs);
+```
+
+> [!IMPORTANT]
+> Note that as it uses reflection it's not as performant as other APIs. It also
+> lose the ability to have type-safe code. If one of the converter change its
+> interfaces or parameters between versions it may throw unexpected exceptions.
+
+## `IFormat` interface
+
+The converter interface does not have any requirements for the types it could
+convert. You can theoretically implement `IConverter`. However, in
+order to provide some features the library expects that every format implements
+the _empty_ interface [`IFormat`](xref:Yarhl.FileFormat.IFormat).
+
+By using the [`IFormat`](xref:Yarhl.FileFormat.IFormat) interface it allows the
+APIs to:
+
+- Provide extension methods that applies to formats only (like `ConvertWith`).
+- Provide type discovery for _formats_ via _Yarhl.Plugins_.
+- Prevent unboxing performance issues.
+
+## Working with existing models
+
+Models should implement the [`IFormat`](xref:Yarhl.FileFormat.IFormat)
+interface. If you have a model and cannot be modified to inherit from the
+interface, then it's possible to create a _format wrapper_.
+
+For instance, let's see how to provide a format-compatible class for a
+third-party sound format `ThirdPartyWave`:
+
+[!code-csharp[format wrapper](./../../../../src/Yarhl.Examples/Formats/Formats.cs?name=FormatWrapper)]
diff --git a/docs/articles/core/getting-started/architecture.md b/docs/articles/core/getting-started/architecture.md
new file mode 100644
index 00000000..b2a4c305
--- /dev/null
+++ b/docs/articles/core/getting-started/architecture.md
@@ -0,0 +1,19 @@
+# Framework architecture
+
+TODO: base goal and requirements
+
+## Reading, deserializing, exporting
+
+TODO
+
+## Core library and plugins
+
+TODO
+
+## Related tools
+
+TODO
+
+## End game
+
+TODO
diff --git a/docs/articles/core/getting-started/images/deserializer_output.png b/docs/articles/core/getting-started/images/deserializer_output.png
new file mode 100644
index 00000000..d293ec2f
Binary files /dev/null and b/docs/articles/core/getting-started/images/deserializer_output.png differ
diff --git a/docs/articles/core/getting-started/images/goal_overview.drawio.png b/docs/articles/core/getting-started/images/goal_overview.drawio.png
new file mode 100644
index 00000000..3b94320d
Binary files /dev/null and b/docs/articles/core/getting-started/images/goal_overview.drawio.png differ
diff --git a/docs/articles/core/getting-started/images/hex_view.png b/docs/articles/core/getting-started/images/hex_view.png
new file mode 100644
index 00000000..39c52ab1
Binary files /dev/null and b/docs/articles/core/getting-started/images/hex_view.png differ
diff --git a/docs/articles/core/getting-started/images/po_poedit.png b/docs/articles/core/getting-started/images/po_poedit.png
new file mode 100644
index 00000000..30486751
Binary files /dev/null and b/docs/articles/core/getting-started/images/po_poedit.png differ
diff --git a/docs/articles/core/getting-started/images/po_vscode.png b/docs/articles/core/getting-started/images/po_vscode.png
new file mode 100644
index 00000000..c98fcc5f
Binary files /dev/null and b/docs/articles/core/getting-started/images/po_vscode.png differ
diff --git a/docs/articles/core/getting-started/net-languages.md b/docs/articles/core/getting-started/net-languages.md
new file mode 100644
index 00000000..03d7e80c
--- /dev/null
+++ b/docs/articles/core/getting-started/net-languages.md
@@ -0,0 +1,3 @@
+# Yarhl in other .NET languages
+
+TODO
diff --git a/docs/articles/core/getting-started/resources/texts.bin b/docs/articles/core/getting-started/resources/texts.bin
new file mode 100644
index 00000000..353ee15b
Binary files /dev/null and b/docs/articles/core/getting-started/resources/texts.bin differ
diff --git a/docs/articles/core/getting-started/tutorial.md b/docs/articles/core/getting-started/tutorial.md
new file mode 100644
index 00000000..204ea85b
--- /dev/null
+++ b/docs/articles/core/getting-started/tutorial.md
@@ -0,0 +1,285 @@
+# Getting started guide
+
+**_Welcome to Yarhl!_** ๐
+This step-by-step guide covers how to create your first program that works with
+custom file formats.
+
+You will learn how to:
+
+- ๐ง setup a C# project with the _Yarhl_ dependencies.
+- ๐ analyze a specification and **implement a file format**.
+- ๐งฎ **read binary data** and convert it into a format.
+- โป๏ธ **convert a text format** into a standard like GNU Gettext PO.
+
+By the end of this guide you will have a program that converts a binary file
+into an editable text file!
+
+![Diagram showing hexadecimal viewer with some bytes and an arrow pointing to a PO text file](images/goal_overview.drawio.png)
+
+Prepare your self a good cup of ๐ต or โ and let's dive into it! ๐คฟ
+And if you have any question along the way don't hesitate to
+[ask for help](https://github.com/SceneGate/Yarhl/discussions).
+
+> [!NOTE]
+> This guide covers how to create the project in the **.NET language C#**, but
+> it should be fairly similar in any other .NET languages like F# or Visual
+> Basic .NET.
+> Unfortunately Yarhl is not supported outside .NET languages.
+
+> [!TIP]
+> If you aer new into .NET development, start by learning with the free course
+> of C# from [Microsoft](https://aka.ms/selfguidedcsharp).
+
+## Pre-requisites
+
+Before starting you need to setup your computer to create .NET projects. You
+will need:
+
+- Latest version of the [.NET SDK](https://dotnet.microsoft.com/en-us/download)
+- A programming IDE for C# development.
+ - On Windows you can use
+ [Visual Studio Community](https://visualstudio.microsoft.com/vs/community/)
+ - [Visual Studio Code](https://code.visualstudio.com/) with the C# extension
+ is also great and works in every major platform.
+ - [JetBrains Rider](https://www.jetbrains.com/rider/) is a cool alternative to
+ Visual Studio and works on every platform, but it's a paid product.
+- (Optional) Hexadecimal viewer. Some options are:
+ - [ImHex](https://github.com/WerWolv/ImHex): cross-platform.
+ - [HxD](https://mh-nexus.de/en/downloads.php): Windows only.
+ - [Okteta](https://apps.kde.org/en-gb/okteta/): Linux with KDE.
+
+Also download the following binary file that our program will take as input:
+[texts.bin](./resources/texts.bin).
+
+## Project setup
+
+Let's start by creating a new C# console project. It depends with the type of
+IDE you are using.
+
+### [Visual Studio](#tab/vs)
+
+[Microsoft docs](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console)
+describe how to setup a new project with the latest version of their IDE.
+
+### [Visual Studio Code](#tab/vscode)
+
+[Visual Studio Code docs](https://code.visualstudio.com/docs/csharp/get-started#_create-a-hello-world-app)
+describe how to setup and create a new C# project within the IDE.
+
+### [Terminal](#tab/terminal)
+
+In general, one of the easiest way to create a C# projects is to use the
+terminal. Open a new window and move to the folder where you want to create the
+project. Then type: `dotnet new console --name MyFirstConverterTool`
+
+---
+
+Now we have an empty project let's add the Yarhl dependency. Open your project
+file (file with extension `.csproj`) and add a new _package reference_. It will
+download the dependencies from
+[nuget.org](https://www.nuget.org/packages?q=Yarhl).
+
+```xml
+
+
+
+
+```
+
+Nice, we have an application ready to add some code.
+
+> [!TIP]
+> Check-out [nuget.org](https://www.nuget.org/packages?q=Yarhl) for the latest
+> version available of Yarhl.
+
+## Format specification
+
+Our goal is to make a program that converts the downloaded file `texts.bin` into
+a new file in a format that we can easily open, like a text file `.txt`. We can
+inspect the content in its pure format, bytes, by using programs such as
+_hexadecimal viewers_.
+
+![File opened with an hex viewer where there is some text along _random_ bytes](./images/hex_view.png)
+
+We can see there isn't only text but bytes with other meanings. This file
+matches the following specification:
+
+| Offset | Type | Description |
+| ------ | ----------- | ------------------------ |
+| 0x0000 | `char[4]` | Format identifier `TXTI` |
+| 0x0004 | Int32 | Number of texts |
+| 0x0008 | TextEntry[] | List of text entries |
+
+where `TextEntry` is:
+
+| Offset | Type | Description |
+| ------ | ------ | ------------------------------------ |
+| 0x0000 | Int16 | Entry ID |
+| 0x0002 | String | Text null-ended with UTF-16 encoding |
+
+## Format implementation
+
+From the above specification we are interested in keeping a **list of _entries_
+with their identifier number and text content**. Next step is to create a new
+class to represent this format.
+
+Let's create a new class named `TxtiFormat`. It will have a property with the
+collection of entries (initialized for convenience).
+
+[!code-csharp[TxtiFormat](../../../../src/Yarhl.Examples/Tutorial/TxtiFormat.cs?name=Class&highlight=4)]
+
+Did you notice the **inheritance with `IFormat`**?
+Format models should implement the interface `IFormat`. In this way Yarhl knows
+that this type implements a format. The interface is empty, it doesn't need to
+implement any specific method or property, it acts as a marker.
+
+Now let's create a second class to represent the `TextEntry`:
+
+[!code-csharp[TextEntry](../../../../src/Yarhl.Examples/Tutorial/TextEntry.cs?name=Class&highlight=1)]
+
+This time we don't need to inherit from `IFormat` as this class does not
+represent a file format itself. It's part of one.
+
+๐ Cool, we have our first **file format implemented!** It's time to add some
+code to fill these classes from the file `texts.bin`.
+
+## Converting binary data into the format
+
+First we will do what it's named **deserialization**: reading binary data from a
+file to fill a model.
+
+To do so we will create a **converter** class. It's goal is to _convert a binary
+format, the data from the file, into the `TxtiFormat` model_.
+
+```mermaid
+graph LR
+ A["Binary"] --> |Binary2Txti| B["Txti"]
+```
+
+Create a new class `Binary2Txti` and implement the interface
+`IConverter`.
+
+[!code-csharp[Binary2Txti class definition](../../../../src/Yarhl.Examples/Tutorial/Binary2Txti.cs?name=Class&highlight=5)]
+
+The interface asks to have a method that performs the conversion:
+`TxtiFormat Convert(IBinary source)`. It takes as an input any binary format and
+produces as an output the model `TxtiFormat`. Add the method to our class.
+
+It's time to start adding code to read data from the file. First we need a
+_binary reader_ class that will help us to read integers and strings from binary
+content. Yarhl provides an enhanced version of .NET `BinaryReader` with the
+class `DataReader`.
+
+[!code-csharp[Binary2Txti reading first 4 chars](../../../../src/Yarhl.Examples/Tutorial/Binary2Txti.cs?name=ValidateHeader&highlight=1,5)]
+
+As a start, we read the first 4 characters of the file that according to our
+[specification](#format-specification) they must be `TXTI`. This guarantees we
+are reading the format we expect.
+
+Now it's time to get the number of entries and iterate for each of them. As we
+know, each entry has an ID followed by the text. We will add them to a new
+instance of our target format `TxtiFormat`. Finally we return it from the
+`Convert` method.
+
+[!code-csharp[Binary2Txti reading text entries](../../../../src/Yarhl.Examples/Tutorial/Binary2Txti.cs?name=ReadEntries)]
+
+And voilร ๐. You have your first **format converter**.
+
+## Use the converter
+
+Let's try our cool format and converter. Going back to `Program.cs` let's add
+some code to the `Main` method, our application entrypoint, so it opens the file
+and convert it.
+
+First we need to open the file on disk and create our source format: a binary
+format. We will take the file path from the program command-line arguments.
+
+[!code-csharp[Creating a stream from a file](../../../../src/Yarhl.Examples/Tutorial/Program.cs?name=OpenFile)]
+
+Next, let's create an instance of our converter and convert/read that file!
+
+[!code-csharp[Converting binary data](../../../../src/Yarhl.Examples/Tutorial/Program.cs?name=Deserialize)]
+
+If we run our program now we should see the following output:
+
+![deserializer output showing 3 entries and text 'Hello World!'](images/deserializer_output.png)
+
+## Converting Txti into a standard PO format
+
+So far we have been able to read the binary file into a model. If our intention
+is to be able to modify it or use it outside the program, we will need to
+convert it into another format. Hopefully this time more standard that other
+programs can open.
+
+To deal with _translatable content_ one of the industry standards is
+[GNU Gettext PO](https://en.wikipedia.org/wiki/Gettext)
+
+Our goal now is to do something we may call **exporting**: convert `TxtiFormat`
+into a standard format like `Po`, so we can write it on a file later.
+
+```mermaid
+graph LR
+ A["Txti"] --> |Txti2Po| B
+ B["Po"] -->|Po2Binary| C["Binary"]
+```
+
+Repeating the process from before, we create a class `Txti2Po` to implement this
+time `IConverter`. Let's add some basic implementation:
+
+[!code-csharp[Converter Txti to PO](../../../../src/Yarhl.Examples/Tutorial/Txti2Po.cs?name=Converter)]
+
+Our converter returns the data into another format model: `Po`. We will need one
+additional step before we can save the data to disk: converting the `Po` model
+to binary (serializing). To do this task, `Yarhl.Media.Text` provides the
+converter `Po2Binary` that implements `IConverter`.
+
+## Use a Node to chain converters
+
+There is a second way to use the _converters_ that could be easier to read when
+we need to **chain conversions**.
+
+We can use `Node`s. They represent a _file_ on a virtual, non-existing, file
+system that it's in our program. A `Node` has a format. If you create one from a
+file, it will have `BinaryFormat` (`IBinary`) to start with. We can then
+_convert_ the binary format of our node using the API `TransformWith`:
+
+[!code-csharp[Chaining conversions](../../../../src/Yarhl.Examples/Tutorial/Program.cs?name=ExportNodes)]
+
+Congrats! You just finished the program. ๐๐
+Feel free to open your output file with any text editor or PO-specific software
+like [PO Edit](https://poedit.net/) or [Weblate](https://weblate.org).
+
+![PO opened in VSCode](images/po_vscode.png)
+
+![PO opened in PoEdit](images/po_poedit.png)
+
+## Wrap up
+
+In this guide we saw how to implement a file format and two converters. We were
+able to read a binary file and generate a standard format that allows us to
+inspect the content and edit it.
+
+```mermaid
+graph LR
+ A["Binary TXTI"] --> |Binary2Txti| B
+ B["Txti"] --> |Txti2Po| C
+ C["Po"] -->|Po2Binary| D["Binary PO"]
+```
+
+Check-out the docs to learn more details about Yarhl and its features
+
+- [Format](../formats/formats.md)
+- [Converters](../formats/converters.md)
+- [`DataStream`](../binary/datastream.md)
+- [Nodes](../virtual-file-system/nodes.md)
+
+> [!NOTE]
+> We could have taken a shortcut and create a converter to do _binary TXTI_ to
+> _PO_. Creating the intermediate conversion into the model `TxtiFormat` makes
+> our application more extensible. In the future we may need to export into a
+> _XLIFF_ file instead of _PO_.
+
+> [!TIP]
+> Do you want to keep playing?
+> Feel free to try to implement now the reverse operation: from a (modified) PO
+> file generate the binary TXTI file.
diff --git a/docs/articles/core/toc.yml b/docs/articles/core/toc.yml
new file mode 100644
index 00000000..d9910d28
--- /dev/null
+++ b/docs/articles/core/toc.yml
@@ -0,0 +1,49 @@
+- name: โจ Getting started
+- name: Introduction
+ href: ../../index.md
+- name: Getting started guide
+ href: ./getting-started/tutorial.md
+- name: ๐ง Framework architecture
+ href: ./getting-started/architecture.md
+- name: ๐ง Yarhl in other .NET languages
+ href: ./getting-started/net-languages.md
+
+- name: โป Formats and converters
+- name: Formats
+ href: ./formats/formats.md
+- name: Converters
+ href: ./formats/converters.md
+- name: Advanced
+ items:
+ - name: Cloneable format
+ href: ./formats/cloneable-format.md
+ - name: Use cases for converters
+ href: ./formats/converters-usecases.md
+
+- name: ๐ Virtual file system
+- name: Nodes
+ href: ./virtual-file-system/nodes.md
+- name: Node factory
+ href: ./virtual-file-system/node-factory.md
+- name: ๐ง Node navigation
+ href: ./virtual-file-system/navigate-nodes.md
+- name: ๐ง Container format
+ href: ./virtual-file-system/container-format.md
+
+- name: ๐งฎ Binary formats
+- name: DataStream
+ href: ./binary/datastream.md
+- name: ๐ง Binary format
+ href: ./binary/binaryformat.md
+- name: ๐ง Binary reader and writer
+ href: ./binary/binaryreader-writer.md
+- name: ๐ง Text reader and writer
+ href: ./binary/textreader-writer.md
+- name: ๐ง (De)serialization via attributes
+ href: ./binary/attr-serialization.md
+- name: ๐ง Custom streams
+ href: ./binary/custom-streams.md
+
+- name: ๐ Plugins
+- name: ๐ง Overview
+ href: ../plugins/overview.md
diff --git a/docs/articles/core/virtual-file-system/container-format.md b/docs/articles/core/virtual-file-system/container-format.md
new file mode 100644
index 00000000..3e9d0a43
--- /dev/null
+++ b/docs/articles/core/virtual-file-system/container-format.md
@@ -0,0 +1,3 @@
+# Container format
+
+TODO
diff --git a/docs/articles/core/virtual-file-system/images/node-children.drawio.png b/docs/articles/core/virtual-file-system/images/node-children.drawio.png
new file mode 100644
index 00000000..180ff1b2
Binary files /dev/null and b/docs/articles/core/virtual-file-system/images/node-children.drawio.png differ
diff --git a/docs/articles/core/virtual-file-system/images/node-simple.drawio.png b/docs/articles/core/virtual-file-system/images/node-simple.drawio.png
new file mode 100644
index 00000000..2354d4e0
Binary files /dev/null and b/docs/articles/core/virtual-file-system/images/node-simple.drawio.png differ
diff --git a/docs/articles/core/virtual-file-system/navigate-nodes.md b/docs/articles/core/virtual-file-system/navigate-nodes.md
new file mode 100644
index 00000000..4a49dc18
--- /dev/null
+++ b/docs/articles/core/virtual-file-system/navigate-nodes.md
@@ -0,0 +1,3 @@
+# Navigate the nodes
+
+TODO
diff --git a/docs/articles/core/virtual-file-system/node-factory.md b/docs/articles/core/virtual-file-system/node-factory.md
new file mode 100644
index 00000000..7a267c3c
--- /dev/null
+++ b/docs/articles/core/virtual-file-system/node-factory.md
@@ -0,0 +1,129 @@
+# Node factory
+
+The [`Node`](xref:Yarhl.FileSystem.Node) constructor requires at least a
+**non-null name**. It's not required to provide an _extension_ in the name. Its
+format can be initially `null`, you can [change it later](nodes.md#format).
+
+[!code-csharp[node constructors](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=Constructor)]
+
+> [!NOTE]
+> There is another overload of the `Node` constructor with a `Node` parameter.
+> It's covered in the [node clone](nodes.md#cloning-a-node) topic.
+
+To cover common use cases, the
+[`NodeFactory`](xref:Yarhl.FileSystem.NodeFactory) provides APIs to create nodes
+with a format quickly.
+
+## Create node with binary data
+
+Similar to the [`DataStreamFactory`](xref:Yarhl.IO.DataStreamFactory), we can
+create a node with binary data from different sources. In all these cases the
+node will have the format [`BinaryFormat`](xref:Yarhl.IO.BinaryFormat).
+
+- New memory buffer:
+ [`FromMemory(name)`]()
+- Byte array:
+ [`FromArray(name, data)`]()
+- Segment of a byte array:
+ [`FromArray(name, data, offset, length)`]()
+- .NET `Stream`:
+ [`FromStream(name, stream)`]()
+- Segment of a .NET `Stream`:
+ [`FromSubstream(name, stream, offset, lengt)`]()
+
+[!code-csharp[from binary](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=BinaryData)]
+
+> [!IMPORTANT]
+> The APIs `FromStream` and `FromSubstream` will take ownership of the stream
+> argument. Do not dispose the stream variable directly. If you don't want that
+> the format takes the ownership, create a
+> [BinaryFormat](xref:Yarhl.IO.BinaryFormat) with
+> [DataStreamFactory.FromStreamKeepingOwnership]().
+
+## Create nodes from files
+
+The `FromFile` overloads allows to create a new node to access the data from a
+file on disk. The node will have the format
+[`BinaryFormat`](xref:Yarhl.IO.BinaryFormat).
+
+The
+[`FromFile(path, mode)`]()
+creates the node with the name from file. This includes the file extension as
+well.
+
+The overload
+[`FromFile(path, name, mode)`]()
+allows to set a specific name for the node that differs from the name of the
+file on disk.
+
+If the path points to a symbolic link from _Windows_ or _Unix_, it will resolve
+to the target.
+
+[!code-csharp[from files](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=Files)]
+
+> [!TIP]
+> The new node will have a [tag](nodes.md#tags) named `FileInfo` containing an
+> instance of .NET
+> [`FileInfo`](https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo)
+> for the given path. If the path was pointing to a symbolic link, the file info
+> will contain information of the link, not the actual target.
+
+> [!TIP]
+> Check-out the [DataStream](../binary/datastream.md#factory) topic for
+> information about the `FileOpenMode` arguments.
+
+## Create a container node
+
+The
+[`CreateContainer(name)`]()
+method allows to create a node with the given node for _container_ usage. It
+will have the format `NodeContainerFormat`.
+
+[!code-csharp[container](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=Container)]
+
+## Create nodes from directories
+
+The factory contains a set of APIs to create a node hierarchy that replicates
+the files and folders from a given path on disk.
+
+The first method is
+[`FromDirectory(path, filter, mode)`]().
+By default the `filter` is `*`. It creates a new node with children **for each
+file** on the given path. **It does not iterate recursively and it will ignore
+folders on the path**. It opens the files with the provided `mode`. Optionally
+it's possible to pass a `string` with a simple _filter_ or search pattern. More
+information about the filter from the .NET API
+[`Directory.GetFiles`]().
+The node will have the name of the root directory from the given path.
+
+[!code-csharp[directory](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=Directory1)]
+
+The method
+[`FromDirectory(path, filter, name, iterateDirectories, mode)`]()
+behaves similar but allows to specify the name of the node. It also has a new
+argument to specify if it should **iterate recursively through any directory**
+and create the full hierarchy.
+
+[!code-csharp[directory](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=Directory2)]
+
+To provide advanced filtering capabilities, there are two more methods that
+behaves similar to above. In these cases instead of a `string` to filter, you
+can specify a function (lambda or method) that takes as an argument a file path
+and returns a boolean to accept or not the file.
+
+[!code-csharp[directory](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=Directory3)]
+
+## Create node hierarchy
+
+Given a _root_ node and a child with an associated path, you may want to
+_insert_ it in the hierarchy. The API
+[`CreateContainersForChild(root, path, child)`]()
+adds the child in the given path from the _root_ node, creating any necessary
+intermediary container node.
+
+As an example, we have a scenario were we just created our _root_ node and we
+have a child that we want to add in `data/gfx/scene1/`. The method will create
+the node containers `data`, `gfx` and `scene1`. It will add them to the _root_
+node and then add our child to `scene1`.
+
+[!code-csharp[hierarchy](./../../../../src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs?name=CreateHierarchy)]
diff --git a/docs/articles/core/virtual-file-system/nodes.md b/docs/articles/core/virtual-file-system/nodes.md
new file mode 100644
index 00000000..39dd614f
--- /dev/null
+++ b/docs/articles/core/virtual-file-system/nodes.md
@@ -0,0 +1,172 @@
+# Node
+
+In Yarhl, a **node** is an entity that has a **name** and a
+[**format**](../formats/formats.md). The concept is similar to a file or folder
+on a computer hard-drive, but in this case is virtual, it's not a physical file
+in the disk. Its format can be some bytes in a `Stream` like a disk file, but it
+could also be any class that implements the
+[`IFormat`](xref:Yarhl.FileFormat.IFormat) interface.
+
+![node with name, format and path properties](images/node-simple.drawio.png)
+
+If the _format_ of the node is `NodeContainerFormat`, then we say that the node
+is a **container**. This is the equivalent of a folder in our file system. These
+nodes may have one or more node **children**.
+
+By having nodes with children we can create a hierarchy similar to the file
+system of our drives. We could navigate identify and navigate by their **path**.
+
+![previous node with three children nodes each with different set of properties](images/node-children.drawio.png)
+
+Nodes is a core feature of Yarhl. It allows to represent with a hierarchy a set
+of formats. Running [converters](../formats/converters.md) we can transform the
+content of the nodes, for instance by reading, writing, unpacking, etc.
+
+Combining nodes, [converters](../formats/converters.md) and the
+[_sub-stream_ concept of `DataStream`](../binary/datastream.md#sub-streams), we
+can represent a complex file system even when all the file data points to a
+single `Stream` from a disk file.
+
+Let's see it with an example from the
+[Ekona](https://scenegate.github.io/Ekona/index.html) library that provides
+implementation for _Nintendo DS_ formats.
+
+[!code-csharp[overview](./../../../../src/Yarhl.Examples/FileSystem/NodeExamples.cs?name=Overview)]
+
+## Children
+
+A node may have children if it has a container type. Children are stored as
+references in a collection and can be accessed via the property `Children`.
+
+It's possible to iterate `Children` with a `foreach` or a regular `for` and get
+the number of children with `node.Children.Count`.
+
+To access to a child use its index, `Children[3]`, or its name,
+`Children["image.png"]`. You can chain this operation to navigate the hierarchy:
+`node.Children[1].Children["map1.scr"]`.
+
+[!code-csharp[children](./../../../../src/Yarhl.Examples/FileSystem/NodeExamples.cs?name=AccessChildren)]
+
+> [!TIP]
+> You can find more ways to iterate or navigate nodes across a hierarchy in the
+> [`Navigator`](xref:Yarhl.FileSystem.Navigator) class.
+
+### Add or remove
+
+It's possible to add or remove children from its parent node. Use the method
+`Add` to add one or more nodes as its children.
+
+Use the `Remove` method to remove a child from its parent by instance reference
+or node name. The method will **not throw an exception** if the node to remove
+is not found. The method will return `false` in those cases.
+
+The method `RemoveChildren` removes all the children. Additionally its parameter
+allow to dispose them.
+
+[!code-csharp[add remove](./../../../../src/Yarhl.Examples/FileSystem/NodeExamples.cs?name=AddRemove)]
+
+> [!NOTE]
+> A removed child **is not disposed**. Consider freeing its formats, especially
+> if they have binary type or are containers. `RemoveChildren` does offer the
+> possibility to dispose the children.
+
+> [!IMPORTANT]
+> A node cannot have two children with the same name. Adding a node with the
+> same name will replace the node.
+
+> [!IMPORTANT]
+> You should not add or remove while iterating the `Children` property.
+
+## Format
+
+A node can have any [format](../formats/formats.md) type that implements the
+interface `IFormat`. It can also have a `null` format.
+
+The `Format` property gets access to the format by returning an instance of type
+`IFormat`. For convenience, there is also the method `GetFormatAs()` that
+tries to cast the node format to the desired type. It will **return `null`** if
+the casting is not possible.
+
+The property `IsContainer` returns `true` when the node have a type that allows
+having children. This would be `NodeContainerFormat` or `null`.
+
+The property `Stream` is a shortcut to `GetFormatAs().Stream` and it
+will also return `null` if the format is not an implementation of `IBinary`.
+
+### Changing format
+
+The node can change its format via the method `ChangeFormat(format)`.
+
+If the current format is a container, first it will remove any children from
+this node. If the future format is a container, it will move the children from
+the format to the node.
+
+Additionally there is an optional argument to indicate if the method should
+dispose the current format before changing. By default is `true`, meaning it
+will call the method `Dispose` from the current format if it implements
+`IDisposable`.
+
+## Format conversion
+
+Apart from the changing the format API, a _node_ also provides methods to
+_transform_ the format by using a [converter](../formats/converters.md).
+
+If the converter does not need any
+[parameter](../formats/converters.md#parameters) and it has a public
+parameterless constructor (default case) you can use the short API
+[`TransformWith()`](xref:Yarhl.FileSystem.Node.TransformWith``1)
+
+If it takes parameters or the instance needs to be created in a different way
+(e.g. factory), pass the converter object via
+[`TransformWith(converter)`]().
+
+[!code-csharp[transform](./../../../../src/Yarhl.Examples/FileSystem/NodeExamples.cs?name=Transform)]
+
+The `TransformWith()` method are a shortcut method to run a converter with the
+current node's format and then call `ChangeFormat()` with the result. Note the
+considerations of [changing format](#changing-format) like what it would happen
+for container formats.
+
+The method returns the same instance of the node. This allows a syntax
+fluent-like for chaining conversions.
+
+[!code-csharp[transform chaining](./../../../../src/Yarhl.Examples/FileSystem/NodeExamples.cs?name=TransformChain)]
+
+## Tags
+
+Nodes can store additional metadata via the generic dictionary `Tags`. Each tag
+has a `string` as a key and it can have any type as value. Use it to store
+metadata of the node, outside of its regular format.
+
+Converters may use the `Tags` to provide additional information about the nodes.
+For instance, the _Ekona_ library adds to every node the tag
+`scenegate.ekona.id` with the internal ID of the file in the game file.
+
+> [!NOTE]
+> **Avoid depending on _tags_ in a converter.**
+> It could be that the node has a given tag just after running a converter. But
+> if it was created from a file on disk it may not have it again.
+
+## Cloning a node
+
+The constructor
+[`Node(node)`]()
+allows to do a _deep_ clone of a node. This includes name, format and tags.
+
+If the format of the source node is not null, **it must implement
+[`ICloneableFormat`](../formats/cloneable-format.md)**. As children are also
+_deep_ cloned, all of them must have a format that implements
+`ICloneableFormat`.
+
+As `NodeContainerFormat` implements `ICloneableFormat`, the children of the node
+will also be deep cloned.
+
+## Dispose
+
+`Node` implements `IDisposable`. The `Dispose` method will remove and dispose
+all of its children, recursively. It will also dispose its format if it
+implements `IDisposable`.
+
+> [!IMPORTANT]
+> A node cannot be used anymore after calling `Dispose`. This also affects to
+> all its children (recursively).
diff --git a/docs/articles/media-text/encodings.md b/docs/articles/media-text/encodings.md
new file mode 100644
index 00000000..7886b4b6
--- /dev/null
+++ b/docs/articles/media-text/encodings.md
@@ -0,0 +1,3 @@
+# Encodings
+
+TODO
diff --git a/docs/articles/media-text/po-format.md b/docs/articles/media-text/po-format.md
new file mode 100644
index 00000000..c79c6d55
--- /dev/null
+++ b/docs/articles/media-text/po-format.md
@@ -0,0 +1,3 @@
+# PO translation format
+
+TODO
diff --git a/docs/articles/media-text/tables.md b/docs/articles/media-text/tables.md
new file mode 100644
index 00000000..88971339
--- /dev/null
+++ b/docs/articles/media-text/tables.md
@@ -0,0 +1,3 @@
+# Tables
+
+TODO
diff --git a/docs/articles/media-text/toc.yml b/docs/articles/media-text/toc.yml
new file mode 100644
index 00000000..e386a32d
--- /dev/null
+++ b/docs/articles/media-text/toc.yml
@@ -0,0 +1,7 @@
+- name: ๐ Text formats
+- name: ๐ง GNU Gettext PO format
+ href: ./po-format.md
+- name: ๐ง Encodings
+ href: ./encodings.md
+- name: ๐ง Tables
+ href: ./tables.md
diff --git a/docs/articles/plugins/overview.md b/docs/articles/plugins/overview.md
new file mode 100644
index 00000000..6a1cfb1c
--- /dev/null
+++ b/docs/articles/plugins/overview.md
@@ -0,0 +1,3 @@
+# Plugins overview
+
+TODO
diff --git a/docs/dev/toc.yml b/docs/dev/toc.yml
deleted file mode 100644
index caf18e7e..00000000
--- a/docs/dev/toc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-- name: Changelog
- href: Changelog.md
-
-- name: API
- href: ../api/toc.yml
diff --git a/docs/docfx.json b/docs/docfx.json
index 976fafdd..2776af6d 100644
--- a/docs/docfx.json
+++ b/docs/docfx.json
@@ -1,67 +1,67 @@
{
- "metadata": [
- {
- "src": [
- {
- "files": [
- "Yarhl/bin/**/Yarhl.dll",
- "Yarhl.Media.Text/bin/**/Yarhl.Media.Text.dll"
- ],
- "src": "../src"
- }
- ],
- "dest": "api",
- "filter": "filterConfig.yml",
- "disableGitFeatures": false,
- "disableDefaultFilter": false
- }
- ],
- "build": {
- "content": [
- {
- "files": [ "api/**.yml", "dev/**" ]
- },
- {
- "files": [ "toc.yml", "index.md" ]
- },
- {
- "files": [ "guides/**" ]
- },
- {
- "files": ["README.md", "CONTRIBUTING.md"],
- "src": "../"
- }
- ],
- "resource": [
- {
- "files": ["images/**"]
- }
- ],
- "overwrite": [
+ "metadata": [
+ {
+ "src": [
{
"files": [
- "apidoc/**.md"
+ "Yarhl/*.csproj",
+ "Yarhl.Media.Text/*.csproj"
],
- "exclude": [
- "obj/**",
- "_site/**"
- ]
+ "src": "../src"
}
],
- "dest": "_site",
- "globalMetadataFiles": ["global_metadata.json"],
- "fileMetadataFiles": [],
- "template": [
- "default",
- "statictoc",
- "default-widescreen"
- ],
- "postProcessors": ["ExtractSearchIndex"],
- "markdownEngineName": "markdig",
- "noLangKeyword": false,
- "keepFileLink": false,
- "cleanupCacheHistory": false,
+ "dest": "api",
+ "includePrivateMembers": false,
"disableGitFeatures": false,
- "xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ]
+ "disableDefaultFilter": false,
+ "noRestore": false,
+ "namespaceLayout": "nested",
+ "memberLayout": "samePage",
+ "EnumSortOrder": "alphabetic",
+ "allowCompilationErrors": false
+ }
+ ],
+ "build": {
+ "content": [
+ {
+ "files": [
+ "api/**.yml",
+ "api/index.md" // TODO: homepage with namespace overview
+ ]
+ },
+ { "files": "**/*.{md,yml}", "src": "articles", "dest": "docs" },
+ { "files": [ "toc.yml", "*.md" ] }
+ ],
+ "resource": [
+ {
+ "files": [ "**/images/**", "**/resources/**" ],
+ "exclude": [ "_site/**", "obj/**" ]
+ }
+ ],
+ "output": "_site",
+ "globalMetadata": {
+ "_appTitle": "Yarhl",
+ "_appName": "Yarhl",
+ "_appFooter": "Part of the SceneGate framework. Docs made with docfx",
+ "_appLogoPath": "images/logo-50.png",
+ "_appFaviconPath": "images/favicon.ico",
+ "_enableSearch": true,
+ "_enableNewTab": true,
+ "_lang": "en"
+ },
+ "fileMetadataFiles": [],
+ "template": [
+ "default",
+ "modern",
+ "template"
+ ],
+ "postProcessors": [],
+ "keepFileLink": false,
+ "disableGitFeatures": false,
+ "sitemap": {
+ "baseUrl": "https://scenegate.github.io/Yarhl",
+ "priority": 0.5,
+ "changefreq": "monthly"
}
+ }
}
diff --git a/docs/filterConfig.yml b/docs/filterConfig.yml
deleted file mode 100644
index 4d53c6ad..00000000
--- a/docs/filterConfig.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiRules:
- # Remove object method inheritance
- - exclude:
- uidRegex: ^System\.Object
-type: Type
\ No newline at end of file
diff --git a/docs/global_metadata.json b/docs/global_metadata.json
deleted file mode 100644
index 70bf6b98..00000000
--- a/docs/global_metadata.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "_appTitle": "SceneGate Yarhl",
- "_appFooter": "Copyright (c) 2018 SceneGate",
- "_appLogoPath": "images/favicon-48.png",
- "_appFaviconPath": "images/favicon.ico",
- "_enableSearch": true,
- "_enableNewTab": true,
- "_gitContribute": {
- "apiSpecFolder": "docs/apidoc",
- "repo": "https://github.com/SceneGate/Yarhl",
- "branch": "develop"
- }
-}
\ No newline at end of file
diff --git a/docs/guides/Contributing.md b/docs/guides/Contributing.md
deleted file mode 100644
index 00bde098..00000000
--- a/docs/guides/Contributing.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-uid: CONTRIBUTING
----
-
-[!include[CONTRIBUTING](../../CONTRIBUTING.md)]
diff --git a/docs/guides/Cookbook.md b/docs/guides/Cookbook.md
deleted file mode 100644
index 8a0f7505..00000000
--- a/docs/guides/Cookbook.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Cookbook
-
-## IO
-
-### Padding
-
-```csharp
-// Add 0s until position is divisible by 0x4
-filesWriter.WritePadding(0x00, 0x4);
-
-// Add 0s until position is divisible by 0x10
-myDataWriter.WritePadding(0x00, 0x10);
-```
-
-## FileSystem
-
-### Using converters with parameters
-
-```csharp
-public class ConverterWithParameter :
- IInitializer,
- IConverter,
- IConverter
- {
-
- public int Parameter { get; set; }
-
- public void Initialize(int param)
- {
- Parameter = param;
- }
-
- public Po Convert(BinaryFormat source)
- {
- // Converter
- }
-
- public BinaryFormat Convert(Po source)
- {
- // Converter
- }
- }
-```
-
-Then you can use `TransformWith(3)`.
-
-You can use a custom class too:
-
-```csharp
-public class ConverterWithParameter :
- IInitializer,
- IConverter,
- IConverter
-
-TransformWith(myClassInstance)
-```
-
-### Creating directory structure
-
-```csharp
-Node root = new Node("root");
-
-string path = "/parent1/parent2/";
-Node child = new Node("child");
-
-// This will create /root/parent1/parent2/child
-NodeFactory.CreateContainersForChild(root, path, child);
-```
-
-### Iterating children nodes
-
-```csharp
-foreach (Node node in Navigator.IterateNodes(source.Root)) {
- if (!node.IsContainer) {
- //This is your child, take care of him
- }
-}
-```
-
-### Find a node
-
-```csharp
-// You can use full paths (starting with '/') or relative paths.
-Navigator.SearchNode(nodeParent, "Child/SubChild");
-```
-
-###ย Deleting nodes
-
-```csharp
-var parent = new Node("Parent");
-var child1 = new Node("Child1");
-var child2 = new Node("Child2");
-node.Add(child1);
-node.Add(child2);
-
-// If you remove passing a reference, the node is removed but it is NOT disposed
-node.Remove(child1);
-
-// If you remove passing a name (you don't have the reference),
-// the node is removed AND disposed.
-node.Remove("Child2");
-```
diff --git a/docs/guides/Yarhl-nutshell.md b/docs/guides/Yarhl-nutshell.md
deleted file mode 100644
index 2c4b2131..00000000
--- a/docs/guides/Yarhl-nutshell.md
+++ /dev/null
@@ -1,586 +0,0 @@
-๏ปฟ# Yarhl in a nutshell
-
-![Yarhl Logo](../images/logo.png)
-
-**Yarhl** - _Yet Another ROM Hacking Library_ - is a library for _ROM Hacking_
-and fan-translation projects. It provides a virtual file system, file format,
-and format conversion features and plugin support.
-
-But what it really has to offer? Why should you use it? And how? This tutorial
-will teach you how to use Yarhl and how to take advantage of the 100% of it.
-
-Remember that if you have any question you can create GitHub issues, contact the
-contributors by email, Twitter or Discord. But first make sure you've read the
-whole docs.
-
-## Your first steps: Reading and Writing
-
-Oh, hi! I'm Mister Yarhl (or M.Y.). Nice to meet you. I will be your guide!
-Erm... y-you can... picture me like this:
-
-![Mister Yarhl](../images/mister.png)
-
-Let's get started! The first module I'm teaching you is
-[`Yarhl.IO`](xref:Yarhl.IO) (IO stands for _Input/Output_), which is similar to
-.NET standard `System.IO` but with specific features to work with binary files.
-
-This module is divided into binary and text files. Easy-peasy! Let's go deeper
-into these classes!
-
-### DataStream
-
-[`DataStream`](xref:Yarhl.IO.DataStream) wraps any kind of .NET `Stream`.
-
-#### Reuse of Stream
-
-It allows to reuse a parent `Stream` to have substreams to reduce the number of
-resources to use. For instance, to unpack a file you would just need to create
-`DataStream` instances from the same parent `DataStream` having different
-offsets and lengths.
-
-Disposing the last instance of a `DataStream` that has a reference to a `Stream`
-will dispose the `Stream` too.
-
-#### Comparison
-
-The `DataStream` class provides the
-[`Compare`]() method to
-check if two streams are identical.
-
-#### Push and pop positions
-
-Similar to the terminal commands `pushd` and `popd`, our `DataStream` provides
-methods for moving temporarily to a position to perform an operation and then
-restore the position. This is very useful when you need to read or write a few
-fields in another section of the file. It works with an stack so you can push
-several positions.
-
-- [`PushCurrentPosition`](xref:Yarhl.IO.DataStream.PushCurrentPosition): save
- the current position.
-- [`PushToPosition`](xref:Yarhl.IO.DataStream.PushToPosition*): save the current
- position and move.
-- [`PopPosition`](xref:Yarhl.IO.DataStream.PopPosition): restore the last saved
- position.
-- [`RunInPosition`]():
- push, run the lambda expression and pop again.
-
-#### Read and Write
-
-We have also the typical read and write methods for arrays of bytes. And don't
-forget about the [`WriteTo`]()
-methods that allows to write a full `DataStream` in another `DataStream` or in a
-file in your disk. Very useful mate!
-
-### DataReader and DataWriter
-
-[`DataReader`](xref:Yarhl.IO.DataReader) is the equivalent of the .NET
-`BinaryReader` and [`DataWriter`](xref:Yarhl.IO.DataWriter) of `BinaryWriter.
-Apart from the typical read and write methods, they provide the following very
-useful features.
-
-#### Endianness
-
-By properties or constructor you can specify if the endianness of the stream is
-little or big. This will affect to all the read and write operations.
-
-#### Strings
-
-By using the different overloads of `ReadString` and `Write` you can read and
-write strings with different encodings, fixed sizes, null terminated or not or
-in the format _size + content_ style. I recommend you to take a look into them,
-they cover most of the cases you will need to work with files.
-
-#### Padding
-
-Are you tired of writing logic to skip or write padding bytes? Well, we too! If
-you are reading a file and you want to skip padding bytes, you can call
-[`SkipPadding`]() and if you
-need to write padding bytes, then
-[`WritePadding`]()
-will be your friend.
-
-### TextDataReader and TextDataWriter
-
-So far, `DataReader` and `DataWriter` have been very useful when you are dealing
-with a file that contains some integer fields for size or offset, arrays of
-bytes and maybe null-terminated strings. But, what about if you need to work
-with a file that only contains text and you are interested in reading line by
-line? In that case, you need [`TextDataReader`](xref:Yarhl.IO.TextDataReader)
-and [`TextDataWriter`](xref:Yarhl.IO.TextDataWriter).
-
-#### New lines
-
-By default, `TextDataWriter` uses always (Windows too) the new line `\n`. It
-doesn't use `\r\n`. The reason is that most file formats uses `\n` and in some
-games having the `\r` may crash. It's sometimes difficult to notice that. If you
-want to use any other new line string (you can even use ` `), you just need
-to change the [`NewLine`](xref:Yarhl.IO.TextDataWriter.NewLine) property.
-
-In the case of the `TextDataReader` the behavior is different. The default value
-for the [`NewLine`](xref:Yarhl.IO.TextDataReader.NewLine) property depends on
-the OS (Windows: `\r\n`, Unix: `\n`). In addition, we provided with an automatic
-mechanism enabled by default:
-[`AutoNewLine`](xref:Yarhl.IO.TextDataReader.AutoNewLine*). If it's enabled, you
-don't need to know the line ending in advance because we will stop at `\n` and
-remove the last `\r` if present. This is also useful if a file mix both line
-endings. And remember, by setting the `NewLine` property `AutoNewLine` is
-disabled.
-
-#### Encoding
-
-The encoding can only by specified in the constructor. We believe that it
-doesn't have sense to change the encoding once you start using the reader
-because a text file must not mix encodings.
-
-#### Peeking
-
-Do you need to read a line without actually moving the position of the stream?
-Maybe you want to check if the line contains a token but you are not sure and
-don't want to keep the current position all the time. Well, in that case you
-have the `Peek*` methods.
-
-#### Preambles / BOM
-
-Some encodings may have a specific
-[BOM](https://en.wikipedia.org/wiki/Byte_order_mark) (_Byte Order Mark_) (or
-_preamble_ in the .NET world). These are some bytes at the beginning of the
-stream that confirms the encoding of the file. For instance, when using UTF-16,
-the file will begin with the bytes `0xFEFF`. It also specifies if the encoding
-is little-ending or big-endian (needed for UTF-16).
-
-Our `TextDataReader` will skip the BOM (_if it's present_) at the beginning of
-the file. In the case of the `TextDataWriter`, the behavior is defined by the
-property [`AutoPreamble`](xref:Yarhl.IO.TextDataWriter.AutoPreamble) which is
-set to `false` by default (again, some games may see it as unexpected bytes).
-When enabled, the first write call will also write the BOM. You can also write
-it manually by calling
-[`WritePreamble()`](xref:Yarhl.IO.TextDataWriter.WritePreamble) (but remember,
-only if you are at the beginning of the stream).
-
-I know... I talk too much... Let's continue!
-
-### Examples
-
-#### Reading / writing a binary file
-
-```csharp
-public void LoadFile(string path)
-{
- using (var stream = DataStreamFactory.FromFile(path, FileOpenMode.Read)) {
- var reader = new DataReader(stream) {
- DefaultEncoding = new EscapeOutRangeEncoding("ascii"),
- Endianness = EndiannessMode.BigEndian,
- };
-
- string id = reader.ReadString(4);
- int offset = reader.ReadInt32();
- reader.SkipPadding(32);
- double myDouble = reader.ReadDouble();
-
- string name;
- stream.RunInPosition(
- () => name = reader.ReadString(),
- offset);
- }
-}
-
-public void SaveFile(string path)
-{
- using (var stream = DataStreamFactory.FromFile(path, FileOpenMode.Read)) {
- var writer = new DataWriter(stream);
-
- writer.Write("TEX0", false);
- writer.Write(0xCAFE);
- writer.Write(0x00);
- writer.WritePadding(0xFF, 32);
- writer.Write("My long text of 80 bytes", 80);
-
- stream.PushToPosition(0x08);
- writer.Write(0x65402);
- stream.PopPosition();
- }
-}
-```
-
-#### Reading / writing a text file
-
-```csharp
-public void LoadFile(DataStream stream)
-{
- var reader = new TextDataReader(stream, Encoding.Unicode);
-
- string firstLine = reader.ReadLine();
- char[] someChars = reader.Read(4);
- string beforeToken = reader.ReadToToken("#");
-
- if (reader.Peek() == ':')
- reader.ReadLine();
- string restFile = reader.ReadToEnd();
-}
-
-public void SaveFile(DataStream stream)
-{
- var writer = new TextDataWriter(stream) {
- AutoPreamble = true,
- };
-
- writer.WriteLine("Hello world!");
- writer.WriteLine("Count is {0}", 42);
- writer.Write("No new line");
-}
-```
-
-## Implementing file formats
-
-Every game contains many files, which have a specific formats. For example files
-with extension `.nclr` are a palettes, or `.aar` are a package files. Yarhl
-helps you to code type as you were actually coding a game format.
-
-To implement a file format, you just need to create a new class that implements
-the (empty) [`IFormat`](xref:Yarhl.FileFormat.IFormat) interface. In this class
-you just need to add the fields of your format. In more programming terms, your
-format it's just a data model.
-
-Let's go for a quick example! Take a look into the following bytes from a file
-that seems to have text from a game menu:
-
-![Hex view of example file](../images/hex_example.png)
-
-This file seems to follow the following specification in little endian:
-
-| Size | Name |
-| ---- | ------------------------- |
-| 4 | Magic ID |
-| 2 | Number of sentences |
-| 2 | Size of the file |
-| \* | Null-terminated sentences |
-
-So given this format, we would implement the following class that maps the
-specification:
-
-```csharp
-public class MenuSentences : IFormat
-{
- public MenuSentences()
- {
- Sentences = new Collection();
- }
-
- public uint MagicID { get; set; }
-
- public ushort FileSize { get; set; }
-
- public Collection Sentences { get; private set; }
-}
-```
-
-Easy! Don't worry about how to convert that format, we will talk about that
-later.
-
-### BinaryFormat
-
-[`BinaryFormat`](xref:Yarhl.IO.BinaryFormat) is the most basic format since it
-just represents raw bytes, a stream. It's... a _binary format_. This format is
-assigned automatically when we open a file from Yarhl as we will see later.
-
-Its only property [`Stream`](xref:Yarhl.IO.BinaryFormat.Stream*) allows you to
-access to its inner stream.
-
-### NodeContainerFormat
-
-You may wonder... what about package formats like `.zip`? They are represented
-with the format (or by inheriting it)
-[`NodeContainerFormat`](xref:Yarhl.FileSystem.NodeContainerFormat). This format
-contains a root folder, also known as `Node`. So let's see what a `Node` is.
-
-## Entering the virtual world: Nodes
-
-This is the main feature of Yarhl and the most important one, no doubt, 10/10
-Yarhl users would say so1. Yarhl has a virtual file system to handle
-your files while maintaining your computer intact, you can now delete your
-"tests" folder and clean your desktop after-ages.
-
-1 None of Yarhl users wants to talk with me anymore. This may
-not be 100% accurate.
-
-### Nodes
-
-A [`Node`](xref:Yarhl.FileSystem.Node) is a virtual file. It's like having a
-file system with files and folder but only in memory for the duration of your
-program. You can dynamically add and remove files / folders. These files and
-folders are called nodes in Yarhl.
-
-A node may have child nodes like a folder may have folders and files. You can
-add the _subnodes_ with the
-[`Add`]() method and you iterate
-and access to its children with the
-[`Children`](xref:Yarhl.FileSystem.NavigableNode`1.Children) property.
-
-The node [`Name`](xref:Yarhl.FileSystem.NavigableNode`1.Name) must be unique.
-You can also get the full path to the node in this new virtual filesystem. That
-is, if you have a _root_ node with name `MyRoot` and you add a node `Node1`, the
-[`Path`](xref:Yarhl.FileSystem.NavigableNode`1.Path) property for `Node1` will
-be `/MyRoot/Node1`.
-
-Ah, one more thing before I forget. Regular files in your disk have some bytes
-associated, right? Well, in the case of nodes they have a
-[Format](#implementing-file-formats) that we were talking before. That is, it
-doesn't have to have bytes but it could be a type to represent image, texture,
-text, font, ... The actual type of the node. For instance, let's say we create a
-node from a disk file, it will have a `BinaryFormat` because for now it's just a
-bunch of bytes. But if those bytes store a set of menu texts, we could transform
-its format and associate its actual content type: `MenuSentences`. To the node
-[`Format`](xref:Yarhl.FileSystem.Node.Format*) property you can set any type
-that implements the [`IFormat`](xref:Yarhl.FileFormat.IFormat) interface.
-
-By the way, there is a property to get the inner `DataStream` when the format of
-the node is a `BinaryFormat`: [`Stream`](xref:Yarhl.FileSystem.Node.Stream). It
-will return `null` if the type is not `BinaryFormat`. We added it because to do
-cool things like:
-
-```csharp
-node.Stream.WriteTo("/home/mister_yarhl/my_node.bin");
-```
-
-If the type is different, you may want to check the method
-[`GetFormatAs()`](xref:Yarhl.FileSystem.Node.GetFormatAs``1).
-
-### Why nodes?
-
-Well, imagine that you have a Nintendo DS game `.nds`, you could open it with
-Yarhl and access to its files and folders without actually extracting the files
-in your disk. All access would be in memory, probably even sharing the same
-stream thanks to the `sub-DataStreams`.
-
-### Files vs folders
-
-As said, folders in our virtual file system are also nodes. They always have the
-`NodeContainerFormat` or any class that inherits it. This format is just a root
-node folder that becomes the children of the node. You can use the property
-[`IsContainer`](xref:Yarhl.FileSystem.Node.IsContainer*) to check if the node is
-a node container, that is, if it's a folder.
-
-It's more clear with a picture:
-
-![A directory named with tho files inside](../images/node_example.png)
-
-`mastering` would be our root node with format `NodeContainerFormat`. While
-`example.example` and `example2.example` would be our two child nodes. By
-default they will have a `BinaryFormat` format.
-
-### Navigating the sea of nodes
-
-Do you have many nodes? I guess it will be difficult to navigate through all of
-them. Then, you want to review the class
-[`Navigator`](xref:Yarhl.FileSystem.Navigator). It provides with features like
-iterators and finders.
-
-To iterate over the node children you would just do:
-
-```csharp
-foreach (var node in root.Children) {
- // Something
-}
-```
-
-But if you want to iterate over the full tree of nodes, that is, including the
-children of your children (like `node` in the above example), you can use the
-`Navigator`.
-
-```csharp
-foreach (var node in Navigator.IterateNodes(root)) {
- // Something
-}
-```
-
-You can even specify how to do the iteration: going first deeper into each node
-([_depth-first search_](https://en.wikipedia.org/wiki/Depth-first_search)) or
-getting first every children from the current node
-([_breadth-first search_](https://en.wikipedia.org/wiki/Breadth-first_search)).
-
-If you just want to search a specific node given a full path, you want to use
-[`Navigator.SearchNode(path)`]().
-
-### Creating the file system
-
-So you want to start using node right now, eh. Well, it will be easy. You will
-want to check out the static helper class:
-[`NodeFactory`](xref:Yarhl.FileSystem.NodeFactory). Let's check some of its
-methods:
-
-```csharp
-// Manual
-var node1 = new Node("name", new BinaryFormat(filePath));
-
-// Factory
-var node2 = NodeFactory.FromFile(filePath); // Node name is the file name
-```
-
-So, what about creating a node from a folder you would say.
-
-```csharp
-var emptyFolder = NodeFactory.CreateContainer("name");
-
-var folderFromDisk = NodeFactory.FromDirectory(folderPath);
-```
-
-Yeeeah! That's the face I was looking for! You can create a virtual file that
-quick!
-
-Don't forget to review the overloads and the method
-[`FromMemory`]() to
-create a node with data in memory.
-
-Yarhl is way more interesting now, right!?
-
-## Converters: putting together all the pieces
-
-Finally! We have _formats_, _nodes_ and some classes for _IO_ operations. Now
-everything begin to fall into place, you'll see.
-
-Creating a tool to work with files usually require to work with different
-formats, right? We need to convert from `.dat` to `.txt`, from `.bin` into a a
-palette or unpacking several files from a `.pak` file.
-
-Well, that's easy to do. Yarhl is all about converting formats, let's see an
-example:
-
-```csharp
-public void ExportFontImage(string fontPath, string outputPath)
-{
- using (var binary = new BinaryFormat(fontPath)) {
- var font = (Font)ConvertFormat.With(binary);
- var image = (Image)ConvertFormat.With(font);
- image.Save(outputPath);
- }
-}
-```
-
-1. We start creating a new `BinaryFormat` from a file path.
-2. We convert the `BinaryFormat` (reading its `Stream`) into a `Font` type.
-3. We convert the `Font` format into an `Image` type.
-4. We save it to a physical file in the hard-drive.
-
-_"But what's `Font2Binary` and `Font2Image`, **Mister Yarhl**?"_, you would say.
-They are **converters**! A converter is a class which implements the
-[IConverter](xref:Yarhl.FileFormat.IConverter`2) interface.
-
-You can check some converter examples from the tools to translate _Pokรฉmon
-Conquest_. For instance:
-[Font2Binary](https://github.com/pleonex/PokemonConquest/blob/master/AmbitionConquest/AmbitionConquest/Fonts/Font2Binary.cs)
-and
-[Font2Image](https://github.com/pleonex/PokemonConquest/blob/master/AmbitionConquest/AmbitionConquest/Fonts/Font2Image.cs).
-
-### Converting formats
-
-But let's come back to our example of `MenuSentences` from the
-[format](#implementing-file-formats) section. It's turn to create a converter to
-fill the `MenuSentences` class from a file. That it's to _read_ a file with that
-format.
-
-```csharp
-public class Binary2MenuSentences : IConverter
-{
- public MenuSentences Convert(BinaryFormat source)
- {
- var menu = new MenuSentences();
- var reader = new DataReader(source.Stream);
-
- menu.MagicID = reader.ReadUInt32();
- ushort numSentences = reader.ReadUInt16();
- menu.FileSize = reader.ReadUInt16();
-
- for (int i = 0; i < numSentences; i++) {
- menu.Sentences.Add(reader.ReadString());
- }
-
- return menu;
- }
-}
-```
-
-So now we can get our menu instance with:
-
-```csharp
-public void ReadMenuFile(string filePath)
-{
- using (var binary = new BinaryFormat(filePath)) {
- var menu = (MenuSentences)ConvertFormat.With(binary);
- // Do something with the menu instance
- }
-}
-```
-
-Since there is just one converter `BinaryFormat -> MenuSentences` we can
-simplify it even more:
-
-```csharp
-public void ReadMenuFile(string filePath)
-{
- using (var binary = new BinaryFormat(filePath)) {
- MenuSentences menu = ConvertFormat.To(binary);
- // Do something with the menu instance
- }
-}
-```
-
-Do you want to write your _new_ or _updated_ menu instance? Let's write a
-converter to convert from `MenuSentences` to `BinaryFormat` and save in a file
-in disk.
-
-```csharp
-public class MenuSentences2Binary : IConverter
-{
- public BinaryFormat Convert(MenuSentences menu)
- {
- var binary = new BinaryFormat();
- var writer = new DataWriter(binary.Stream);
-
- writer.Write(menu.MagicID);
- writer.Write((ushort)menu.Sentences.Count);
- writer.Write((ushort)0x00); // Placeholder size to override later
-
- foreach (string sentence in menu.Sentences) {
- writer.Write(sentence);
- }
-
- binary.Stream.Position = 0x06;
- writer.Write((ushort)binary.Stream.Length);
-
- return binary;
- }
-}
-```
-
-And that's it! I'm pretty sure you've got enough of converters
-
-### Transforming nodes
-
-Don't forget that a node can have a format. How do we _convert_ the format from
-a node? We could use the approach from before, but there are two methods that
-will **convert and update** the format of the node:
-[`TransformTo`](xref:Yarhl.FileSystem.Node.TransformTo*) and
-[`TransformWith`](xref:Yarhl.FileSystem.Node.TransformWith*). They will also
-dispose the old format so we don't need to do anything, just transform several
-times the format of our node until it's the one we want.
-
-```csharp
-var node = NodeFactory.FromFile(path);
-node.TransformTo();
-
-// Now node.Format is MenuSentences
-```
-
-or from the first example:
-
-```csharp
-public void ExportFontImage(string fontPath, string outputPath)
-{
- using (var node = NodeFactory.FromFile(fontPath)) {
- node.TransformWith()
- .TransformWith();
-
- node.GetFormatAs().Save(outputPath);
- }
-}
-```
diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml
deleted file mode 100644
index 8f1775ad..00000000
--- a/docs/guides/toc.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-- name: Guides
- items:
- - name: Yarhl in a nutshell
- href: Yarhl-nutshell.md
-
- - name: Cookbook
- href: Cookbook.md
-
-- name: Contributing
- items:
- - name: Guidelines
- href: Contributing.md
diff --git a/docs/images/favicon-48.png b/docs/images/favicon-48.png
deleted file mode 100644
index e49e90f4..00000000
Binary files a/docs/images/favicon-48.png and /dev/null differ
diff --git a/docs/images/favicon.png b/docs/images/favicon.png
deleted file mode 100644
index afcdd0bb..00000000
Binary files a/docs/images/favicon.png and /dev/null differ
diff --git a/docs/images/hex_example.png b/docs/images/hex_example.png
deleted file mode 100644
index 1029316d..00000000
Binary files a/docs/images/hex_example.png and /dev/null differ
diff --git a/docs/images/logo-50.png b/docs/images/logo-50.png
new file mode 100644
index 00000000..f70cb01e
Binary files /dev/null and b/docs/images/logo-50.png differ
diff --git a/docs/images/logo-large.png b/docs/images/logo-large.png
new file mode 100644
index 00000000..7c91792f
Binary files /dev/null and b/docs/images/logo-large.png differ
diff --git a/docs/images/logo.png b/docs/images/logo.png
index 7c91792f..06ba5be5 100644
Binary files a/docs/images/logo.png and b/docs/images/logo.png differ
diff --git a/docs/images/mister.png b/docs/images/mister.png
deleted file mode 100644
index 06ba5be5..00000000
Binary files a/docs/images/mister.png and /dev/null differ
diff --git a/docs/images/node_example.png b/docs/images/node_example.png
deleted file mode 100644
index d5a8f466..00000000
Binary files a/docs/images/node_example.png and /dev/null differ
diff --git a/docs/index.md b/docs/index.md
index d1950d12..3aeca610 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,5 +1,118 @@
----
-uid: README
----
+# Yarhl, A format ResearcH Library ![SceneGate awesome](https://img.shields.io/badge/SceneGate-awesome%20%F0%9F%95%B6-blue?logo=csharp)
-[!include[README](../README.md)]
+![Yarhl logo](./images/logo-large.png)
+
+
+
+
+_Yarhl_ is a set of libraries that helps to **implement and convert file
+formats** It empowers you with...
+
+- โป๏ธ ... APIs to easily **convert** between custom formats.
+- ๐ ... **guidelines** to implement and test custom format converters.
+- ๐ข ... advance **binary and text** reading / writing, encoding and
+ serialization.
+- ๐ ... **standard formats** implementation like **PO** for translations.
+- ๐ ... virtual **file system** to unpack and pack containers efficiently.
+
+## Usage
+
+The project has the following .NET libraries (NuGet packages via nuget.org). The
+libraries only support the latest .NET LTS version: **.NET 6.0**.
+
+- [![Yarhl](https://img.shields.io/nuget/v/Yarhl?label=Yarhl&logo=nuget)](https://www.nuget.org/packages/Yarhl)
+ - `Yarhl.FileFormat`: format conversion APIs.
+ - `Yarhl.FileSystem`: virtual file system.
+ - `Yarhl.IO`: streams, binary and text reading / writing.
+- [![Yarhl.Media.Text](https://img.shields.io/nuget/v/Yarhl.Media.Text?label=Yarhl.Media.Text&logo=nuget)](https://www.nuget.org/packages/Yarhl.Media.Text)
+ - `Yarhl.Media.Text`: translation formats and converters (Po), table replacer.
+ - `Yarhl.Media.Text.Encoding`: _euc-jp_ and token-escaped encodings.
+- [![Yarhl.Plugins](https://img.shields.io/nuget/v/Yarhl.Plugins?label=Yarhl.Plugins&logo=nuget)](https://www.nuget.org/packages/Yarhl.Plugins)
+ - `Yarhl.Plugins`: discover formats and converters from .NET assemblies.
+
+> [!NOTE]
+> _Are you planning to try a preview version?_
+> Check-out the
+> [GitHub project readme](https://github.com/SceneGate/Yarhl#install) for
+> details on how to setup the NuGet preview feed for SceneGate projects.
+
+## Quick demo
+
+_Yarhl_ allows you to work with different file formats with an unified API for
+conversion into binary (serialization / deserialization). Let's try to create a
+new translatable file format
+[PO](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) from
+[Yarhl.Media.Text](./articles/media-text/po-format.md) and save it into disk.
+
+[!code-csharp[Demo PO](./../src/Yarhl.Examples/Introduction.cs?name=Demo_Po)]
+
+It's frequent to find formats that are _containers_. _Yarhl_ allows to have a
+_virtual file system_ to work with its content without having to extract it into
+disk (saving space and time). For instance, let's open a game file from
+_Nintendo DS_ that contains thousand of files. Then we will navigate through its
+files, unpacking, decompressing and finally converting one file into _PO_. We
+can use the following libraries for this task:
+
+- [Ekona](https://scenegate.github.com/Ekona/): support of NDS game file system.
+- [LayTea](https://github.com/pleonex/LayTea): support of formats from
+ _Professor Layton_ games.
+
+[!code-csharp[Demo containers](./../src/Yarhl.Examples/Introduction.cs?name=Demo_Containers)]
+
+## Showcase
+
+Some cool projects built with _Yarhl_:
+
+- [**Texim**](https://github.com/SceneGate/Texim): experimental API for image
+ file formats.
+- [**Ekona**](https://scenegate.github.io/Ekona/): support Nintendo DS file
+ formats.
+- [**Lemon**](https://github.com/SceneGate/Lemon/): support Nintendo 3DS file
+ formats.
+- [**LayTea**](https://www.pleonex.dev/LayTea/): modding tools for _Professor
+ Layton_ games.
+- [**Attack of Friday Monsters tools**](https://github.com/pleonex/AttackFridayMonsters):
+ modding tools for _Attack of the Friday Monsters_ game.
+- [**Metatron**](https://github.com/TraduSquare/Metatron): translation framework
+ for _Shin Megami Tensei_ saga games.
+
+## License
+
+The software is licensed under the terms of the
+[MIT license](https://choosealicense.com/licenses/mit/).
diff --git a/docs/template/public/main.css b/docs/template/public/main.css
new file mode 100644
index 00000000..fca2c28f
--- /dev/null
+++ b/docs/template/public/main.css
@@ -0,0 +1,42 @@
+/* Changing the site font */
+@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@100;400;700&display=swap");
+/* @import url('https://fonts.googleapis.com/css2?family=Inconsolata&display=swap'); */
+@import url("https://fonts.googleapis.com/css2?family=Fira Code&display=swap");
+
+/*@import url('https://fonts.cdnfonts.com/css/cascadia-code');*/
+/* @font-face {
+ font-family: 'Cascadia Code';
+ font-style: normal;
+ font-weight: 100;
+ src: local('Cascadia Code'), url('https://fonts.cdnfonts.com/s/29131/Cascadia.woff') format('woff');
+} */
+
+:root {
+ --bs-font-sans-serif: "Nunito";
+ --bs-font-monospace: "Fira Code";
+}
+
+/* Hide breadcrum bar on large screen as it only links to itself */
+@media (min-width: 768px) {
+ .actionbar {
+ display: none !important;
+ }
+}
+
+/* Give more space for section separation in a doc */
+h2 {
+ margin-top: 2rem !important;
+}
+
+/* Improve TOC with a line for categories (entries without link) */
+.toc span.name-only {
+ border-bottom-color: var(--bs-tertiary-color) !important;
+ border-bottom-width: 2px !important;
+ border-bottom-style: solid !important;
+ margin-bottom: 0 !important;
+ margin-top: 0.6rem !important;
+}
+
+.toc span.name-only:first() {
+ margin-top: 0.4rem !important;
+}
diff --git a/docs/template/public/main.js b/docs/template/public/main.js
new file mode 100644
index 00000000..74aae83e
--- /dev/null
+++ b/docs/template/public/main.js
@@ -0,0 +1,9 @@
+export default {
+ iconLinks: [
+ {
+ icon: "github",
+ href: "https://github.com/SceneGate/Yarhl",
+ title: "GitHub",
+ },
+ ],
+};
diff --git a/docs/templates/material b/docs/templates/material
deleted file mode 160000
index 2cc98b18..00000000
--- a/docs/templates/material
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 2cc98b18b70bcb5f7639269c58ab1e148eb12415
diff --git a/docs/toc.yml b/docs/toc.yml
index 5df0e0c0..f19f033e 100644
--- a/docs/toc.yml
+++ b/docs/toc.yml
@@ -1,11 +1,14 @@
-- name: Home
- href: index.md
+- name: Core
+ href: articles/core/
-- name: Guides
- href: guides/
+- name: Text formats
+ href: articles/media-text/
- name: API
- href: dev/
+ href: api/
+
+- name: Changelog
+ href: articles/Changelog.md
- name: GitHub
href: https://github.com/SceneGate/Yarhl
diff --git a/src/.editorconfig b/src/.editorconfig
index 64703e4b..f19da40b 100644
--- a/src/.editorconfig
+++ b/src/.editorconfig
@@ -5,7 +5,7 @@ root = true
[*.cs]
## Generic options
-charset = utf-8-bom
+charset = utf-8
indent_size = 4
indent_style = space
tab_width = 8
@@ -14,7 +14,7 @@ end_of_line = lf
insert_final_newline = true
## .NET style rules
-### Organize usings
+### Organize using
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset # too long to set here
@@ -122,39 +122,39 @@ csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
### Indentation preferences
-csharp_indent_case_contents = true:warning
-csharp_indent_switch_labels = true:warning
-csharp_indent_labels = one_less_than_current:warning
-csharp_indent_block_contents = true:warning
-csharp_indent_braces = false:warning
-csharp_indent_case_contents_when_block = false:warning
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents_when_block = false
### Spacing preferences
-csharp_space_after_cast = false:warning
-csharp_space_after_keywords_in_control_flow_statements = true:warning
-csharp_space_between_parentheses = false:warning
-csharp_space_before_colon_in_inheritance_clause = true:warning
-csharp_space_after_colon_in_inheritance_clause = true:warning
-csharp_space_around_binary_operators = before_and_after:warning
-csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning
-csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning
-csharp_space_between_method_call_parameter_list_parentheses = false:warning
-csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning
-csharp_space_between_method_call_name_and_opening_parenthesis = false:warning
-csharp_space_after_comma = true:warning
-csharp_space_before_comma = false:warning
-csharp_space_after_dot = false:warning
-csharp_space_before_dot = false:warning
-csharp_space_after_semicolon_in_for_statement = true:warning
-csharp_space_before_semicolon_in_for_statement = false:warning
-csharp_space_around_declaration_statements = false:warning
-csharp_space_before_open_square_brackets = false:warning
-csharp_space_between_empty_square_brackets = false:warning
-csharp_space_between_square_brackets = false:warning
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_after_comma = true
+csharp_space_before_comma = false
+csharp_space_after_dot = false
+csharp_space_before_dot = false
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_around_declaration_statements = false
+csharp_space_before_open_square_brackets = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_square_brackets = false
### Wrapping preferences
-csharp_preserve_single_line_statements = false:warning
+csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true
## Naming styles rules
@@ -163,6 +163,8 @@ dotnet_diagnostic.IDE1006.severity = warning
## Code analyzers
### .NET SDK
dotnet_diagnostic.CA1303.severity = none # We don't translate exception and log messages from English
+dotnet_diagnostic.IDE0058.severity = suggestion # Expression value is never used
+dotnet_diagnostic.SA1025.severity = none # Allow spaces in comments to structure info
### StyleCop
dotnet_diagnostic.SA1101.severity = none # Do not force to prefix local calls with 'this'
@@ -174,8 +176,8 @@ dotnet_diagnostic.SA1633.severity = none # No XML-format header in source files
### SonarAnalyzer
dotnet_diagnostic.S1135.severity = suggestion # It's almost inevitable to have TODO but add bug ID
-# Special rules for test projects
-[*Tests/**]
+# Special rules for test and example projects
+[{*Tests/**,*Examples/**}]
dotnet_diagnostic.CS1591.severity = none # Disable documentation
dotnet_diagnostic.CA1001.severity = none # No need to implement IDisposable in test classes with cleanup.
dotnet_diagnostic.CA1034.severity = none # Public types in test classes for testing implementations
@@ -185,5 +187,20 @@ dotnet_diagnostic.CA1305.severity = none # No culture method for quick test code
dotnet_diagnostic.CA1307.severity = none # No culture method for quick test code
dotnet_diagnostic.SA0001.severity = none # Disable documentation
dotnet_diagnostic.SA1600.severity = none # Disable documentation
+dotnet_diagnostic.SA1201.severity = none # Allow enums inside classes
dotnet_diagnostic.S2699.severity = none # Assert may be in helper methods
dotnet_diagnostic.S3966.severity = none # Dispose twice to test implementation
+
+[*Examples/**]
+dotnet_diagnostic.SA1123.severity = none # Allow regions to insert code snippets in markdown
+dotnet_diagnostic.SA1124.severity = none # Allow regions to insert code snippets in markdown
+dotnet_diagnostic.S1481.severity = none # Allow unused variables
+dotnet_diagnostic.IDE0059.severity = none # Allow unused variables
+dotnet_diagnostic.IDE0060.severity = none # Allow unused variables
+dotnet_diagnostic.RCS1163.severity = none # Allow unused variables
+dotnet_diagnostic.SA1515.severity = none # Allow comments after #region
+dotnet_diagnostic.SA1512.severity = none # Allow spaces after comments
+
+# TODO: Reconsider after bumping versions, seems to be buggy
+dotnet_diagnostic.SA1313.severity = suggestion # Seems to be a bug with records
+dotnet_diagnostic.SA1000.severity = none # Space before parentheses in new()
diff --git a/src/.idea/.idea.Yarhl/.idea/.gitignore b/src/.idea/.idea.Yarhl/.idea/.gitignore
new file mode 100644
index 00000000..165216ed
--- /dev/null
+++ b/src/.idea/.idea.Yarhl/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/.idea.Yarhl.iml
+/projectSettingsUpdater.xml
+/modules.xml
+/contentModel.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/src/.idea/.idea.Yarhl/.idea/.name b/src/.idea/.idea.Yarhl/.idea/.name
new file mode 100644
index 00000000..87f19058
--- /dev/null
+++ b/src/.idea/.idea.Yarhl/.idea/.name
@@ -0,0 +1 @@
+Yarhl
\ No newline at end of file
diff --git a/src/.idea/.idea.Yarhl/.idea/CakeRider.xml b/src/.idea/.idea.Yarhl/.idea/CakeRider.xml
new file mode 100644
index 00000000..e921cbb8
--- /dev/null
+++ b/src/.idea/.idea.Yarhl/.idea/CakeRider.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/.idea.Yarhl/.idea/indexLayout.xml b/src/.idea/.idea.Yarhl/.idea/indexLayout.xml
new file mode 100644
index 00000000..bc2da5d3
--- /dev/null
+++ b/src/.idea/.idea.Yarhl/.idea/indexLayout.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ ../../yarhl
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/.idea.Yarhl/.idea/vcs.xml b/src/.idea/.idea.Yarhl/.idea/vcs.xml
new file mode 100644
index 00000000..8a1a68bc
--- /dev/null
+++ b/src/.idea/.idea.Yarhl/.idea/vcs.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 2cb8a764..75793b38 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -13,7 +13,7 @@
https://scenegate.github.io/Yarhl/https://github.com/SceneGate/Yarhlicon.png
- translation;localization;romhacking;fan-translation
+ binary;encoding;text;translation;localization;reverse-engineeringREADME.md
diff --git a/src/Yarhl.Examples/FileSystem/NodeExamples.cs b/src/Yarhl.Examples/FileSystem/NodeExamples.cs
new file mode 100644
index 00000000..87bdbeb6
--- /dev/null
+++ b/src/Yarhl.Examples/FileSystem/NodeExamples.cs
@@ -0,0 +1,138 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.FileSystem;
+
+using Yarhl.FileFormat;
+using Yarhl.FileSystem;
+using Yarhl.IO;
+using Yarhl.Media.Text;
+
+public static class NodeExamples
+{
+ public static void Overview()
+ {
+ #region Overview
+ // Create node from a disk file
+ using Node game = NodeFactory.FromFile("game.nds", FileOpenMode.Read);
+
+ // Use the `Binary2NitroRom` converter to convert the binary format
+ // into node containers (virtual file system tree).
+ game.TransformWith();
+
+ // Now we can access to every game file. For instance, we can export one file
+ Node gameFile = game.Children["data"].Children["graphics"].Children["map.bin"];
+
+ // Same FileStream but reading from different offsets.
+ // No disk writing was required.
+ bool isSame = gameFile.Stream.BaseStream == game.Stream.BaseStream;
+ #endregion
+ }
+
+ public static void Children()
+ {
+ #region AccessChildren
+ using Node node = NodeFactory.FromDirectory("inputs/");
+
+ Node childByIndex = node.Children[0];
+ Node childByName = node.Children["menu.txt"];
+ Node subChild = node.Children["maps"].Children["map1.scr"];
+
+ foreach (Node child in node.Children) {
+ // ...
+ }
+
+ for (int i = 0; i < node.Children.Count; i++) {
+ Node child = node.Children[i];
+ // ...
+ }
+ #endregion
+ }
+
+ public static void AddRemove()
+ {
+ #region AddRemove
+ using Node root = NodeFactory.CreateContainer("root");
+
+ Node child = new Node("file1", new Po());
+ root.Add(child);
+
+ Node childSameName = new Node("file1", new BinaryFormat());
+ root.Add(childSameName); // now root.Children[0] has BinaryFormat
+
+ Node child2 = new Node("file2");
+ root.Add(child2);
+
+ // The nodes are NOT disposed
+ root.Remove("file1");
+ bool found = root.Remove(child2); // true
+ bool notFound = root.Remove("IDontExists"); // false
+
+ // or alternatively
+ root.RemoveChildren(dispose: true);
+ #endregion
+ }
+
+ public static void Transform()
+ {
+ #region Transform
+ using Node text = NodeFactory.FromFile("input.bin");
+
+ text.TransformWith(new XorEncryptor("password"));
+ text.TransformWith();
+ #endregion
+
+ #region TransformChain
+ using Node graphics = NodeFactory.FromFile("graphics.bin.lz")
+ .TransformWith(new XorEncryptor("password"))
+ .TransformWith()
+ .TransformWith();
+ #endregion
+ }
+
+ private sealed class Binary2NitroRom : IConverter
+ {
+ public NodeContainerFormat Convert(IBinary source) =>
+ throw new NotImplementedException();
+ }
+
+ private sealed class Binary2Texts : IConverter
+ {
+ public Po Convert(IBinary source) => throw new NotImplementedException();
+ }
+
+ private sealed class LzDecompressor : IConverter
+ {
+ public IBinary Convert(IBinary source) => throw new NotImplementedException();
+ }
+
+ private sealed class XorEncryptor : IConverter
+ {
+ public XorEncryptor(string password)
+ {
+ }
+
+ public IBinary Convert(IBinary source) => throw new NotImplementedException();
+ }
+
+ private sealed class Narc2Container : IConverter
+ {
+ public NodeContainerFormat Convert(IBinary source) => throw new NotImplementedException();
+ }
+}
diff --git a/src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs b/src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs
new file mode 100644
index 00000000..4022a465
--- /dev/null
+++ b/src/Yarhl.Examples/FileSystem/NodeFactoryExamples.cs
@@ -0,0 +1,127 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.FileSystem;
+
+using Yarhl.FileFormat;
+using Yarhl.FileSystem;
+using Yarhl.IO;
+
+public static class NodeFactoryExamples
+{
+ public static void Constructor()
+ {
+ #region Constructor
+ IFormat nodeFormat = CreateNodeFormat();
+ using var node = new Node("myNode", nodeFormat);
+
+ using var nodeWithoutFormat = new Node("myNode2");
+ #endregion
+ }
+
+ public static void FromBinary()
+ {
+ #region BinaryData
+ using Node binMemoryNode = NodeFactory.FromMemory("memory");
+
+ byte[] data = new byte[] { 0xBA, 0xAD, 0xCA, 0xFE, 0xD0, 0x0D };
+ using Node binArrayNode = NodeFactory.FromArray("bad coffe", data);
+
+ using Node binSubArrayNode = NodeFactory.FromArray("coffee", data, 2, 2);
+
+ var fileStream = new FileStream("file.bin", FileMode.Open);
+ using Node binStreamNode = NodeFactory.FromStream("stream", fileStream);
+
+ using Node binSubStreamNode = NodeFactory.FromSubstream("substream", fileStream, 0x100, 0x80);
+ #endregion
+ }
+
+ public static void FromFiles()
+ {
+ #region Files
+ using Node fileNode = NodeFactory.FromFile("inputs/file.bin", FileOpenMode.Read);
+
+ using Node textsNode = NodeFactory.FromFile("inputs/file.bin", "texts", FileOpenMode.ReadWrite);
+
+ FileInfo fileInfo = fileNode.Tags["FileInfo"];
+ #endregion
+ }
+
+ public static void CreateContainer()
+ {
+ #region Container
+ using Node container = NodeFactory.CreateContainer("graphics");
+ #endregion
+ }
+
+ public static void FromDirectory()
+ {
+ #region Directory1
+ // File system:
+ // inputs/
+ // |- menu.txt
+ // |- logo.png
+ // |- maps/
+ // |--- names.txt
+ // |--- world.png
+ // |--- scenarios/
+ // |----- map1.png
+ // |--- regular_font.ttf
+
+ using Node inputNode = NodeFactory.FromDirectory("inputs/");
+ Console.WriteLine(inputNode.Name); // input
+ Console.WriteLine(inputNode.Children.Count); // 2: menu.txt and logo.png
+
+ using Node topTexts = NodeFactory.FromDirectory("inputs/", "*.txt");
+ Console.WriteLine(inputNode.Children.Count); // 1: menu.txt
+ #endregion
+
+ #region Directory2
+ // Same as 'topTexts' with custom name
+ using Node topTextsWithName = NodeFactory.FromDirectory("inputs/", "*.txt", "texts");
+
+ // Node with full hierarchy and filter
+ using Node images = NodeFactory.FromDirectory("inputs/", "*.png", "images", true);
+ Node map1 = images.Children["maps"].Children["scenarios"].Children["map1.png"];
+ #endregion
+
+ #region Directory3
+ static bool IsScenarioImageOrFont(string path) =>
+ (path.StartsWith("inputs/maps/scenarios") && path.EndsWith(".png"))
+ || path.EndsWith(".ttf");
+
+ using Node scenarios = NodeFactory.FromDirectory("inputs/", IsScenarioImageOrFont, "data", true);
+ #endregion
+ }
+
+ public static void CreateHierarchy()
+ {
+ #region CreateHierarchy
+ using Node root = NodeFactory.CreateContainer("root");
+
+ string childPath = "data/gfx/scene1";
+ using Node child = new Node("child1", CreateNodeFormat());
+
+ NodeFactory.CreateContainersForChild(root, childPath, child);
+ Node sameChild = root.Children["data"].Children["gfx"].Children["scene1"].Children["child1"];
+ #endregion
+ }
+
+ private static IFormat CreateNodeFormat() => new BinaryFormat();
+}
diff --git a/src/Yarhl.Examples/Formats/AdvancedConverters.cs b/src/Yarhl.Examples/Formats/AdvancedConverters.cs
new file mode 100644
index 00000000..11c32385
--- /dev/null
+++ b/src/Yarhl.Examples/Formats/AdvancedConverters.cs
@@ -0,0 +1,168 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Formats;
+
+using Yarhl.FileFormat;
+using Yarhl.FileSystem;
+using Yarhl.IO;
+
+#region ManyToOneFont
+public class Font2Binary : IConverter
+{
+ private readonly RgbImage fontImage;
+
+ public Font2Binary(RgbImage fontImage)
+ {
+ this.fontImage = fontImage;
+ }
+
+ public BinaryFormat Convert(Font source)
+ {
+ var binaryFont = new BinaryFormat();
+
+ // TODO: Use source and fontImage to serialize into binary.
+ return binaryFont;
+ }
+}
+#endregion
+
+#region ManyToOneIndexedImage
+public class IndexedImage2RgbImage : IConverter
+{
+ private readonly Palette palette;
+
+ public IndexedImage2RgbImage(Palette palette)
+ {
+ this.palette = palette;
+ }
+
+ public RgbImage Convert(IndexedImage source)
+ {
+ var fullImage = new RgbImage();
+
+ // TODO: Convert each pixel into RGB using the provided palette.
+ return fullImage;
+ }
+}
+#endregion
+
+#region OneToManyIndexedImage
+public class RgbImage2IndexedImage : IConverter
+{
+ public NodeContainerFormat Convert(RgbImage source)
+ {
+ // TODO: Run a quantization algorithm that generates a palette and indexed pixels
+ IndexedImage indexedImage = null;
+ Palette palette = null;
+
+ var container = new NodeContainerFormat();
+
+ var indexedImageNode = new Node("image", indexedImage);
+ container.Root.Add(indexedImageNode);
+
+ var paletteNode = new Node("palette", palette);
+ container.Root.Add(paletteNode);
+
+ return container;
+ }
+}
+#endregion
+
+#region FontImporter
+public class FontGlyphsImporter : IConverter
+{
+ private readonly RgbImage glyphs;
+
+ public FontGlyphsImporter(RgbImage glyphs)
+ {
+ this.glyphs = glyphs;
+ }
+
+ public Font Convert(Font source)
+ {
+ // TODO: Update Font instance with the glyph images
+ return source;
+ }
+}
+#endregion
+
+#region ExecutableTextImporter
+public record TextBlockInfo(uint Position, string Text);
+
+public class ExecutableTextImporter : IConverter
+{
+ private readonly IEnumerable textInfos;
+
+ public ExecutableTextImporter(IEnumerable textInfos)
+ {
+ this.textInfos = textInfos ?? throw new ArgumentNullException(nameof(textInfos));
+ }
+
+ public IBinary Convert(IBinary source)
+ {
+ var writer = new DataWriter(source.Stream);
+
+ foreach (var info in textInfos) {
+ writer.Stream.Position = info.Position;
+
+ // you should check it doesn't overwrite more data than it can
+ writer.Write(info.Text);
+ }
+
+ return source;
+ }
+}
+#endregion
+
+public static class Program
+{
+ public static void OneToManyIndexedImage()
+ {
+ #region OneToManyIndexedImageProgram
+ using Node imageNode = NodeFactory.FromFile("image.png", FileOpenMode.Read)
+ .TransformWith()
+ .TransformWith();
+
+ var indexedImage = imageNode.Children["image"].GetFormatAs();
+ var palette = imageNode.Children["palette"].GetFormatAs();
+ #endregion
+ }
+}
+
+public class Font : IFormat
+{
+}
+
+public class RgbImage : IFormat
+{
+}
+
+public class IndexedImage : IFormat
+{
+}
+
+public class Palette : IFormat
+{
+}
+
+public class BinaryPng2RgbImage : IConverter
+{
+ public RgbImage Convert(IBinary source) => throw new NotImplementedException();
+}
diff --git a/src/Yarhl.Examples/Formats/Converters.cs b/src/Yarhl.Examples/Formats/Converters.cs
new file mode 100644
index 00000000..ed21f9f0
--- /dev/null
+++ b/src/Yarhl.Examples/Formats/Converters.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Formats;
+
+using Yarhl.Media.Text;
+
+public class Converters
+{
+ public void SerializePo()
+ {
+ #region SerializePo
+ // Create a test PO model format to convert.
+ var poFormat = new Po(new PoHeader("software", "SceneGate", "en-US"));
+ poFormat.Add(new PoEntry("Hello, world!"));
+
+ // Create a new converter instance
+ var po2binary = new Po2Binary();
+
+ // Convert!
+ using var binaryPoFormat = po2binary.Convert(poFormat);
+
+ // Binary format is a wrapper over a DataStream (enhanced System.IO.Stream)
+ // We can now save the Stream into a file
+ binaryPoFormat.Stream.WriteTo("strings.po");
+ #endregion
+ }
+}
diff --git a/src/Yarhl.Examples/Formats/Formats.cs b/src/Yarhl.Examples/Formats/Formats.cs
new file mode 100644
index 00000000..4f69c58c
--- /dev/null
+++ b/src/Yarhl.Examples/Formats/Formats.cs
@@ -0,0 +1,65 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Formats;
+
+using System.Collections.ObjectModel;
+using Yarhl.FileFormat;
+
+public static class Formats
+{
+ #region FormatImpl
+ public class GameTextFormat : IFormat
+ {
+ public Collection Texts { get; init; }
+
+ public int SceneId { get; set; }
+ }
+ #endregion
+
+ #region FormatWrapper
+ public class SoundFormat : ThirdPartyWave, IFormat
+ {
+ }
+ #endregion
+
+ public class ThirdPartyWave
+ {
+ }
+
+ #region CloneableFormat
+ public class Image : ICloneableFormat
+ {
+ public int Width { get; set; }
+
+ public int Height { get; set; }
+
+ public byte[] IndexedPixels { get; set; }
+
+ public object DeepClone()
+ {
+ return new Image {
+ Width = Width,
+ Height = Height,
+ IndexedPixels = IndexedPixels.ToArray(),
+ };
+ }
+ }
+ #endregion
+}
diff --git a/src/Yarhl.Examples/Introduction.cs b/src/Yarhl.Examples/Introduction.cs
new file mode 100644
index 00000000..22e1928c
--- /dev/null
+++ b/src/Yarhl.Examples/Introduction.cs
@@ -0,0 +1,97 @@
+๏ปฟ// Copyright (c) 2022 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples;
+
+using Yarhl.FileFormat;
+using Yarhl.FileSystem;
+using Yarhl.IO;
+using Yarhl.Media.Text;
+
+internal static class Introduction
+{
+ internal static void ExportText(string gameFilePath)
+ {
+ #region Demo_Containers
+ // 1. Read the game file system from a file (deserialize)
+ using Node game = NodeFactory.FromFile(gameFilePath, FileOpenMode.Read)
+ .TransformWith();
+
+ // 2. Navigate to the container that has our text file and unpack it.
+ Node msgNode = Navigator.SearchNode(game, "data/ll/common/ll_common.darc")
+ .TransformWith() // binary -> file system (container)
+ .Children[2] // text file is the third file
+ .TransformWith(); // the file is compressed with LZSS
+
+ // 3. Convert its proprietary binary format into industry-standard translation format PO.
+ // As it's a big text file, the converter splits the content into different files.
+ msgNode.TransformWith()
+ .TransformWith(LondonLifeRegion.Usa);
+
+ foreach (var children in msgNode.Children) {
+ // 4. Convert the PO into a binary format (serialize) and write to disk
+ children.TransformWith()
+ .Stream.WriteTo(children.Name + ".po");
+ }
+ #endregion
+
+ #region Demo_Po
+ // Let's create a new PO format
+ var metadata = new PoHeader("software1", "SceneGate", "es-ES");
+ Po translatableContent = new Po(metadata);
+
+ // Now let's add one entry.
+ var entry1 = new PoEntry("Hello world!");
+ entry1.Translated = "ยกHola mundo!";
+ translatableContent.Add(entry1);
+
+ // Finally let's save the format into a file on disk
+ var serializer = new Po2Binary();
+ using BinaryFormat binaryPo = serializer.Convert(translatableContent);
+ binaryPo.Stream.WriteTo("strings.po");
+ #endregion
+ }
+
+ // Fake converters to avoid external dependencies
+ private sealed class Binary2NitroRom : IConverter
+ {
+ }
+
+ private sealed class BinaryDarc2Container : IConverter
+ {
+ }
+
+ private sealed class DencDecompression : IConverter
+ {
+ }
+
+ private sealed class Binary2MessageCollection : IConverter
+ {
+ }
+
+ private sealed class MessageCollection2PoContainer : IConverter, IInitializer
+ {
+ public void Initialize(LondonLifeRegion parameters) => throw new NotImplementedException();
+ }
+
+ private enum LondonLifeRegion
+ {
+ Usa = 0,
+ }
+}
diff --git a/src/Yarhl.Examples/Program.cs b/src/Yarhl.Examples/Program.cs
new file mode 100644
index 00000000..6ac700c1
--- /dev/null
+++ b/src/Yarhl.Examples/Program.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2022 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ // Just used to test from time to time.
+ Tutorial.Program.FakeMain2(args);
+ }
+}
diff --git a/src/Yarhl.Examples/Tutorial/Binary2Txti.cs b/src/Yarhl.Examples/Tutorial/Binary2Txti.cs
new file mode 100644
index 00000000..30b96d57
--- /dev/null
+++ b/src/Yarhl.Examples/Tutorial/Binary2Txti.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Tutorial;
+
+#region Class
+using System.Text;
+using Yarhl.FileFormat;
+using Yarhl.IO;
+
+public class Binary2Txti : IConverter
+{
+#endregion
+
+ #region ValidateHeader
+ public TxtiFormat Convert(IBinary source)
+ {
+ var reader = new DataReader(source.Stream);
+
+ if (reader.ReadString(bytesCount: 4) != "TXTI") {
+ throw new FormatException("Invalid format");
+ }
+ #endregion
+
+ #region ReadEntries
+ var txtiFormat = new TxtiFormat();
+
+ int numEntries = reader.ReadInt32();
+ for (int i = 0; i < numEntries; i++) {
+ var entry = new TextEntry();
+ entry.Id = reader.ReadUInt16();
+ entry.Text = reader.ReadString(Encoding.Unicode);
+
+ txtiFormat.Entries.Add(entry);
+ }
+
+ return txtiFormat;
+ #endregion
+ }
+}
diff --git a/src/Yarhl.Examples/Tutorial/Program.cs b/src/Yarhl.Examples/Tutorial/Program.cs
new file mode 100644
index 00000000..3204bfcc
--- /dev/null
+++ b/src/Yarhl.Examples/Tutorial/Program.cs
@@ -0,0 +1,59 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Tutorial;
+
+using Yarhl.FileSystem;
+using Yarhl.IO;
+using Yarhl.Media.Text;
+
+public static class Program
+{
+ public static void FakeMain(string[] args)
+ {
+ #region OpenFile
+ DataStream fileStream = DataStreamFactory.FromFile(args[0], FileOpenMode.Read);
+ using var binaryFormat = new BinaryFormat(fileStream);
+ #endregion
+
+ #region Deserialize
+ var deserializer = new Binary2Txti();
+ var txti = deserializer.Convert(binaryFormat);
+
+ Console.WriteLine($"Number of entries: {txti.Entries.Count}");
+ Console.WriteLine($"First text: '{txti.Entries[0].Text}'");
+ #endregion
+ }
+
+ public static void FakeMain2(string[] args)
+ {
+ #region ExportNodes
+ // Create a node from a file (BinaryFormat)
+ using Node node = NodeFactory.FromFile(args[0], FileOpenMode.Read);
+
+ // Transform chaining conversions fluent-like.
+ node.TransformWith(new Binary2Txti())
+ .TransformWith(new Txti2Po())
+ .TransformWith(new Po2Binary());
+
+ // Save our new binary data into disk
+ node.Stream!.WriteTo(args[1]);
+ #endregion
+ }
+}
diff --git a/src/Yarhl.Examples/Tutorial/TextEntry.cs b/src/Yarhl.Examples/Tutorial/TextEntry.cs
new file mode 100644
index 00000000..7f08ee1e
--- /dev/null
+++ b/src/Yarhl.Examples/Tutorial/TextEntry.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Tutorial;
+
+#region Class
+public class TextEntry
+{
+ public ushort Id { get; set; }
+
+ public string Text { get; set; }
+}
+#endregion
diff --git a/src/Yarhl.Examples/Tutorial/Txti2Po.cs b/src/Yarhl.Examples/Tutorial/Txti2Po.cs
new file mode 100644
index 00000000..6dd2cc00
--- /dev/null
+++ b/src/Yarhl.Examples/Tutorial/Txti2Po.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Tutorial;
+
+#region Converter
+using Yarhl.FileFormat;
+using Yarhl.Media.Text;
+
+public class Txti2Po : IConverter
+{
+ public Po Convert(TxtiFormat source)
+ {
+ var metadata = new PoHeader("MyProject", "me@example.com", "en");
+ var po = new Po(metadata);
+
+ foreach (var entry in source.Entries) {
+ var poEntry = new PoEntry(entry.Text);
+ poEntry.Context = $"ID: 0x{entry.Id:X2}";
+
+ po.Add(poEntry);
+ }
+
+ return po;
+ }
+}
+#endregion
diff --git a/src/Yarhl.Examples/Tutorial/TxtiFormat.cs b/src/Yarhl.Examples/Tutorial/TxtiFormat.cs
new file mode 100644
index 00000000..50789ecd
--- /dev/null
+++ b/src/Yarhl.Examples/Tutorial/TxtiFormat.cs
@@ -0,0 +1,30 @@
+// Copyright (c) 2023 SceneGate
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+namespace Yarhl.Examples.Tutorial;
+
+#region Class
+using System.Collections.ObjectModel;
+using Yarhl.FileFormat;
+
+public class TxtiFormat : IFormat
+{
+ public Collection Entries { get; init; } = new();
+}
+#endregion
diff --git a/src/Yarhl.Examples/Yarhl.Examples.csproj b/src/Yarhl.Examples/Yarhl.Examples.csproj
new file mode 100644
index 00000000..c014aebf
--- /dev/null
+++ b/src/Yarhl.Examples/Yarhl.Examples.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+
+
diff --git a/src/Yarhl.sln b/src/Yarhl.sln
index 49a70031..47e47418 100644
--- a/src/Yarhl.sln
+++ b/src/Yarhl.sln
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yarhl.IntegrationTests", "Y
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yarhl.PerformanceTests", "Yarhl.PerformanceTests\Yarhl.PerformanceTests.csproj", "{ADF44DD6-B355-45FB-A50A-06CBCE2EFF6E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yarhl.Examples", "Yarhl.Examples\Yarhl.Examples.csproj", "{F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -86,5 +88,17 @@ Global
{ADF44DD6-B355-45FB-A50A-06CBCE2EFF6E}.Release|x64.Build.0 = Release|Any CPU
{ADF44DD6-B355-45FB-A50A-06CBCE2EFF6E}.Release|x86.ActiveCfg = Release|Any CPU
{ADF44DD6-B355-45FB-A50A-06CBCE2EFF6E}.Release|x86.Build.0 = Release|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Debug|x64.Build.0 = Debug|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Debug|x86.Build.0 = Debug|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Release|x64.ActiveCfg = Release|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Release|x64.Build.0 = Release|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Release|x86.ActiveCfg = Release|Any CPU
+ {F6CF5BDF-FD87-47DB-9BB3-0B9369236B85}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/Yarhl.sln.DotSettings b/src/Yarhl.sln.DotSettings
new file mode 100644
index 00000000..3226316e
--- /dev/null
+++ b/src/Yarhl.sln.DotSettings
@@ -0,0 +1,4 @@
+๏ปฟ
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ True
\ No newline at end of file