Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions OpenKh.Kh2/Slct.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using OpenKh.Common;
using OpenKh.Kh2.Extensions;
using OpenKh.Kh2.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xe.BinaryMapper;

namespace OpenKh.Kh2
{

public class ChoiceEntry
{
[Data] public short Id { get; set; }
[Data] public short MessageId { get; set; }
}
public class Slct
{

[Data] public ushort Id { get; set; }
[Data] public byte ChoiceNum { get; set; }
[Data] public byte ChoiceDefault { get; set; }

[Data(Count = 4)] public ChoiceEntry[] Choice { get; set; }
[Data] public short BaseSequence { get; set; }
[Data] public short TitleSequence { get; set; }
[Data] public int Information { get; set; }
[Data] public int EntryId { get; set; }
[Data] public int Task { get; set; }
[Data] public byte PauseMode { get; set; }
[Data] public byte Flag { get; set; }
[Data] public byte SoundPause { get; set; }
[Data(Count = 25)] public byte[] Padding { get; set; }

public static List<Slct> Read(Stream stream) => BaseTable<Slct>.Read(stream);
public static void Write(Stream stream, IEnumerable<Slct> entries) =>
BaseTable<Slct>.Write(stream, 2, entries);
}
}
22 changes: 22 additions & 0 deletions OpenKh.Patcher/PatcherProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,27 @@ private static void PatchList(Context context, List<AssetFile> sources, Stream s
Kh2.SystemData.Cmd.Write(stream.SetPosition(0), cmdList);
break;

case "slct":
var slctList = Kh2.Slct.Read(stream);
var moddedSlct = deserializer.Deserialize<List<Kh2.Slct>>(sourceText);

foreach (var entries in moddedSlct)
{
var existingEntry = slctList.FirstOrDefault(x => x.Id == entries.Id);

if (existingEntry != null)
{
slctList[slctList.IndexOf(existingEntry)] = entries;
}
else
{
slctList.Add(entries);
}
}

Kh2.Slct.Write(stream.SetPosition(0), slctList);
break;

case "localset":
var localList = Kh2.Localset.Read(stream);
var moddedLocal = deserializer.Deserialize<List<Kh2.Localset>>(sourceText);
Expand Down Expand Up @@ -1627,3 +1648,4 @@ private static void PatchSynth(Context context, List<AssetFile> sources, Stream
}
}
}

88 changes: 88 additions & 0 deletions OpenKh.Tests/Patcher/PatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3187,6 +3187,94 @@ public void ListPatchJigsawTest()

}

[Fact]
public void ListPatchSlctTest()
{
var patcher = new PatcherProcessor();
var serializer = new Serializer();
var patch = new Metadata()
{
Assets = new List<AssetFile>()
{
new AssetFile()
{
Name = "14mission.bar",
Method = "binarc",
Source = new List<AssetFile>()
{
new AssetFile()
{
Name = "slct",
Method = "listpatch",
Type = "List",
Source = new List<AssetFile>()
{
new AssetFile()
{
Name = "SlctList.yml",
Type = "slct"
}
}
}
}
}
}
};

File.Create(Path.Combine(AssetsInputDir, "14mission.bar")).Using(stream =>
{
var slctEntry = new List<Kh2.Slct>()
{
new Kh2.Slct
{
Id = 1,
ChoiceNum = 2,
ChoiceDefault = 3,
Choice = Enumerable.Range(0, 4).Select(_ => new Kh2.ChoiceEntry { Id = 0, MessageId = 0 }).ToArray(),
Padding = new byte[25]
}
};
using var slctStream = new MemoryStream();
Kh2.Slct.Write(slctStream, slctEntry);
Bar.Write(stream, new Bar() {
new Bar.Entry()
{
Name = "slct",
Type = Bar.EntryType.List,
Stream = slctStream
}
});
});

File.Create(Path.Combine(ModInputDir, "SlctList.yml")).Using(stream =>
{
var writer = new StreamWriter(stream);
writer.WriteLine("- Id: 1");
writer.WriteLine(" ChoiceNum: 2");
writer.WriteLine(" ChoiceDefault: 3");
writer.WriteLine(" Choice:");
writer.WriteLine(" - Id: 0");
writer.WriteLine(" MessageId: 0");
writer.WriteLine(" Padding:");
writer.WriteLine(" - 0");
writer.Flush();
});

patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir, Tests: true);

AssertFileExists(ModOutputDir, "14mission.bar");

File.OpenRead(Path.Combine(ModOutputDir, "14mission.bar")).Using(stream =>
{
var binarc = Bar.Read(stream);
var slctStream = Kh2.Slct.Read(binarc[0].Stream);
Assert.Equal(1, slctStream[0].Id);
Assert.Equal(2, slctStream[0].ChoiceNum);
Assert.Equal(3, slctStream[0].ChoiceDefault);
});
}


[Fact]
public void ListPatchPlacesTest()
{
Expand Down
27 changes: 27 additions & 0 deletions docs/tool/GUI.ModsManager/creatingMods.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ This document will focus on teaching you how to create mods using the OpenKH Mod
* [soundinfo](#soundinfo-source-example)
* [place](#place-source-example)
* [jigsaw](#jigsaw-source-example)
* [slct](#slct-source-example)
* [bbsarc](#bbsarc-bbs)
* [Example of a Fully Complete `mod.yml` File](#an-example-of-a-fully-complete-modyml-can-be-seen-below-and-the-full-source-of-the-mod-can-be-seen-here)
* [Generating a Simple `mod.yml` for New Mod Authors](#generating-a-simple-modyml-for-new-mod-authors)
Expand Down Expand Up @@ -332,6 +333,7 @@ Asset Example
* `soundinfo`
* `place`
* `jigsaw`
* `slct`

Asset Example
```
Expand Down Expand Up @@ -863,6 +865,31 @@ Sora:
Unk07: 0
Unk08: 0
```
### `slct` Source Example
```
- Id: 1
ChoiceNum: 4 #Amount of options
ChoiceDefault: 3 #Option to default to
Choice:
- Id: 0 #Choice "Function"
MessageId: 0 #MessageID to assign to the Choice
- Id: 1
MessageId: 1
- Id: 2
MessageId: 2
- Id: 3
MessageId: 3
BaseSequence: 12 #Signed short, can be negative
TitleSequence: 13 #Signed short, can be negative
Information: 14 #Value tends to always be 0?
EntryId: 15 #
Comment on lines +884 to +885
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Resolve contradictory/incomplete inline comments on Information and EntryId.

  • Line 884: The example value is 14, but the comment reads #Value tends to always be 0?. This directly contradicts the example and will confuse mod authors. Either use 0 as the example value (matching the stated expectation) or update the comment to reflect why 14 is shown.
  • Line 885: EntryId: 15 # has a trailing # with no comment body — the annotation was started but never completed.
📝 Proposed fix
-  Information: 14 `#Value` tends to always be 0?
-  EntryId: 15 #
+  Information: 0 `#Value` tends to always be 0
+  EntryId: 15 #<describe what EntryId controls>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Information: 14 #Value tends to always be 0?
EntryId: 15 #
Information: 0 `#Value` tends to always be 0
EntryId: 15 #<describe what EntryId controls>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tool/GUI.ModsManager/creatingMods.md` around lines 884 - 885, The inline
comments for the fields Information and EntryId are inconsistent/unfinished;
update the examples so they match their comments: either change Information's
example value from 14 to 0 if the note “Value tends to always be 0” is correct,
or update the comment to explain why 14 is shown (e.g., what 14 represents) so
the example and note agree; also remove the stray trailing “#” after EntryId or
replace it with a concise comment explaining EntryId (e.g., “# unique entry
identifier”) so the annotation is not left empty—edit the lines containing the
Information and EntryId fields in the docs to implement these changes.

Task: 16
PauseMode: 0 #6 possible values. 0 = Null, 1 = Battle, 2 = Form, 3 = Mission, 4 = Event, 5 = Musical
Flag: 1 #Two possible flags. 0 Allows you to unpause, 1 disables unpausing to get out of the menu.
SoundPause: 19
Padding: #There are 25 padding bytes in total
- 0
```
Comment on lines 868 to 892
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language identifier to the slct YAML fence.
markdownlint MD040 flags the fenced block without a language.

💡 Suggested fix
-```
+```yaml
 - Id: 1
   ChoiceNum: 4 `#Amount` of options
   ChoiceDefault: 3 `#Option` to default to
   Choice:
   - Id: 0 `#Choice` "Function"
     MessageId: 0 `#MessageID` to assign to the Choice
   - Id: 1
     MessageId: 1
   - Id: 2
     MessageId: 2
   - Id: 3
     MessageId: 3
   BaseSequence: 12 `#Signed` short, can be negative
   TitleSequence: 13 `#Signed` short, can be negative
   Information: 14
   EntryId: 15
   Task: 16
   PauseMode: 17
   Flag: 18
   SoundPause: 19
   Padding: `#There` are 25 padding bytes in total
   - 0
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 869-869: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tool/GUI.ModsManager/creatingMods.md` around lines 868 - 892, The fenced
code block under the "slct Source Example" should declare the language to
satisfy markdownlint MD040; update the triple-backtick fence that currently
contains the `slct` YAML example to use a language identifier (e.g., ```yaml) so
the block is highlighted and linted correctly, leaving the inner content (fields
like Id, ChoiceNum, ChoiceDefault, BaseSequence, TitleSequence, Padding, etc.)
unchanged.

Comment on lines +890 to +892
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify whether partial Padding is supported or if the example is intentionally truncated.

The comment states "There are 25 padding bytes in total" but the example supplies only one (- 0). It's unclear whether:

  • the reader/patcher accepts a partial list (padding the rest to zero automatically), or
  • the example is simply incomplete and mod authors must specify all 25 bytes.

If partial specification is valid, a brief note to that effect would prevent authors from accidentally writing malformed entries.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tool/GUI.ModsManager/creatingMods.md` around lines 890 - 892, Clarify
the semantics of the "Padding" entry: update the documentation around the
"Padding" example (the line showing "- 0") to state explicitly whether partial
specification is allowed and how it's handled — if partial lists are accepted,
say "you may provide N bytes and the remaining bytes will be zero-padded to a
total of 25"; otherwise require authors to list all 25 bytes and replace the
truncated example with a complete 25-byte list. Ensure the text references the
"Padding" field and the sample "- 0" so readers know which behavior applies.


* `synthpatch` (KH2) - Modifies Mixdata.bar, a file used for various properties related to synthesis in KH2.

Expand Down
Loading