add stream content recursively for scripts

This should make <auto> able to work even when there are scripts that point to scripts, as the content will appear after the last attached script.
This commit is contained in:
haven1433
2022-12-20 22:34:26 -06:00
parent 3b6a18072f
commit 3e16f8ee20
6 changed files with 48 additions and 16 deletions

View File

@@ -753,7 +753,7 @@ namespace HavenSoft.HexManiac.Core.Models
}
}
public Func<IScriptStartRun, int> GetScriptLength;
int IDataModel.GetScriptLength(IScriptStartRun run) => GetScriptLength?.Invoke(run) ?? default;
public Func<IScriptStartRun, IDictionary<int, int>, int> GetScriptLength;
int IDataModel.GetScriptLength(IScriptStartRun run, IDictionary<int, int> destinationLengths) => GetScriptLength?.Invoke(run, destinationLengths) ?? default;
}
}

View File

@@ -19,7 +19,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code {
public ScriptParser(IReadOnlyList<IScriptLine> engine, byte endToken) => (this.engine, this.endToken) = (engine, endToken);
public int GetScriptSegmentLength(IDataModel model, int address) => engine.GetScriptSegmentLength(model, address);
public int GetScriptSegmentLength(IDataModel model, int address) => engine.GetScriptSegmentLength(model, address, new Dictionary<int, int>());
public string Parse(IDataModel data, int start, int length) {
var builder = new StringBuilder();
@@ -493,13 +493,13 @@ namespace HavenSoft.HexManiac.Core.Models.Code {
return string.Join(Environment.NewLine, candidates.Select(line => line.Usage));
}
public static int GetArgLength(IDataModel model, IScriptArg arg, int start) {
public static int GetArgLength(IDataModel model, IScriptArg arg, int start, IDictionary<int, int> destinationLengths) {
if (arg.Type == ArgType.Pointer && arg.PointerType != ExpectedPointerType.Unknown) {
var destination = model.ReadPointer(start);
if (destination >= 0 && destination < model.Count) {
var run = model.GetNextRun(destination);
if (run is IScriptStartRun scriptStart && scriptStart.Start == destination && scriptStart.Start > start) {
return model.GetScriptLength(scriptStart);
return model.GetScriptLength(scriptStart, destinationLengths);
} else if (run.Start == destination) {
// we only want to add this run's length as part of the script if:
// (1) the run has no name
@@ -665,7 +665,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code {
public int CompiledByteLength(IDataModel model, int start, IDictionary<int, int> destinationLengths) {
var length = LineCode.Count;
foreach (var arg in Args) {
var argLength = ScriptParser.GetArgLength(model, arg, start + length);
var argLength = ScriptParser.GetArgLength(model, arg, start + length, destinationLengths);
if (argLength > 0) destinationLengths[model.ReadPointer(start + length)] = argLength;
length += arg.Length(default, -1);
}
@@ -807,7 +807,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code {
public int CompiledByteLength(IDataModel model, int start, IDictionary<int, int> destinationLengths) {
var length = LineCode.Count;
foreach (var arg in Args) {
var argLength = ScriptParser.GetArgLength(model, arg, start + length);
var argLength = ScriptParser.GetArgLength(model, arg, start + length, destinationLengths);
if (argLength > 0) destinationLengths[model.ReadPointer(start + length)] = argLength;
length += arg.Length(model, start + length);
}
@@ -1301,16 +1301,15 @@ namespace HavenSoft.HexManiac.Core.Models.Code {
return (ScriptLine)self.FirstOrDefault(option => option is ScriptLine && option.Matches(data, start));
}
public static int GetScriptSegmentLength(this IReadOnlyList<IScriptLine> self, IDataModel model, int address) {
public static int GetScriptSegmentLength(this IReadOnlyList<IScriptLine> self, IDataModel model, int address, IDictionary<int, int> destinationLengths) {
int length = 0;
var destinations = new Dictionary<int, int>();
while (true) {
var line = self.GetMatchingLine(model, address + length);
if (line == null) break;
length += line.CompiledByteLength(model, address + length, destinations);
length += line.CompiledByteLength(model, address + length, destinationLengths);
if (line.IsEndingCommand) break;
}
while (destinations.TryGetValue(address + length, out int argLength)) {
while (destinationLengths.TryGetValue(address + length, out int argLength)) {
length += argLength;
}
return length;

View File

@@ -33,6 +33,7 @@ msgbox.item 0F 00 msg<""> 1A 00 80 item:data.items.stats 1A 01 80 count: 1A
# followed by a standard 'put away' message.
msgbox.fanfare 31 song:songnames 67 ptr<""> 66 32
register.matchcall trainer:data.trainers.stats -> register.matchcall 16 04 80 trainer:data.trainers.stats 25 EA 01 1A 00 80 trainer:data.trainers.stats 09 08
if.compare variable: condition.script_compare value: pointer<`xse`> -> if.compare 21 variable: value: 06 condition.script_compare pointer<`xse`>
00 nop # does nothing

View File

@@ -97,7 +97,7 @@ namespace HavenSoft.HexManiac.Core.Models {
bool WriteValue(ModelDelta changeToken, int address, int value);
int ReadPointer(int address);
int ReadValue(int address);
int GetScriptLength(IScriptStartRun run);
int GetScriptLength(IScriptStartRun run, IDictionary<int, int> destinationLengths);
SortedSpan<int> GetUnmappedSourcesToAnchor(string anchor);
void SetUnmappedConstant(ModelDelta changeToken, string name, int value);
@@ -294,7 +294,7 @@ namespace HavenSoft.HexManiac.Core.Models {
public abstract void ClearPointer(ModelDelta currentChange, int source, int destination);
public virtual int GetScriptLength(IScriptStartRun run) => 1;
public virtual int GetScriptLength(IScriptStartRun run, IDictionary<int, int> destinationLengths) => 1;
public int ReadValue(int index) => BitConverter.ToInt32(RawData, index);

View File

@@ -1553,19 +1553,19 @@ namespace HavenSoft.HexManiac.Core.Models {
}
}
public override int GetScriptLength(IScriptStartRun run) {
public override int GetScriptLength(IScriptStartRun run, IDictionary<int, int> destinationLengths) {
IReadOnlyList<IScriptLine> lines = null;
if (run is XSERun) lines = singletons.ScriptLines;
if (run is BSERun) lines = singletons.BattleScriptLines;
if (run is ASERun) lines = singletons.AnimationScriptLines;
if (run is TSERun) lines = singletons.BattleAIScriptLines;
return Math.Max(1, lines.GetScriptSegmentLength(this, run.Start));
return Math.Max(1, lines.GetScriptSegmentLength(this, run.Start, destinationLengths));
}
public override T RelocateForExpansion<T>(ModelDelta changeToken, T run, int minimumLength) {
int currentLength = run.Length;
if (run is IScriptStartRun scriptStart) {
currentLength = GetScriptLength(scriptStart);
currentLength = GetScriptLength(scriptStart, new Dictionary<int, int>());
}
if (minimumLength <= currentLength) return run;
if (CanSafelyUse(run.Start + currentLength, run.Start + minimumLength)) return run;

View File

@@ -505,6 +505,38 @@ applymovement 0 <auto>
Assert.Contains("<auto>", script);
}
[Fact]
public void ScriptWithTextAfterBranch_Decompile_IncludedInLength() {
EventScript = @"
lock
msgbox.yesno <auto>
{
Is the answer yes?
}
if.compare 0x800D = 1 <yes>
if.compare 0x800D = 0 <no>
release
end
yes:
msgbox.default <auto>
{
You said yes!
}
release
end
no:
msgbox.default <auto>
{
You said no!
}
release
end";
var length = Tool.ScriptParser.FindLength(Model, 0);
Assert.Equal(99, length);
}
// TODO test that we get an error (not an exception) if we do auto on an unformatted pointer
}
}