Skip to content

Commit 261850a

Browse files
committed
Refactor Sst to extend BinaryTree + MP gain patch
1 parent 81f99fb commit 261850a

8 files changed

Lines changed: 440 additions & 148 deletions

File tree

src/main/java/org/ruru/ffta2editor/PatchesController.java

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import java.util.List;
88
import java.util.logging.Logger;
99

10+
import org.ruru.ffta2editor.model.battle.SBN;
11+
import org.ruru.ffta2editor.model.battle.SBN.Command;
12+
import org.ruru.ffta2editor.model.unitSst.SstHeaderNode;
1013
import org.ruru.ffta2editor.model.unitSst.UnitAnimation;
1114
import org.ruru.ffta2editor.model.unitSst.UnitAnimation.UnitAnimationFrame;
1215
import org.ruru.ffta2editor.model.unitSst.UnitSst;
13-
import org.ruru.ffta2editor.model.unitSst.UnitSst.SstHeaderNode;
1416
import org.ruru.ffta2editor.utility.LZSS;
1517

1618
import javafx.beans.property.BooleanProperty;
@@ -207,7 +209,7 @@ private void portAnimations(int i, ArrayList<Pair<Integer, byte[]>> compressedAn
207209
byte animationId = (byte)(animation.getKey() >>> 8);
208210
byte animationType = (byte)(animation.getKey() & 0xFF);
209211
SstHeaderNode newNode = new SstHeaderNode(unitSst.size, animationType, animationId, 0);
210-
newNode.compressedValue = compressedAnimation;
212+
newNode.value = compressedAnimation;
211213
unitSst.insert(newNode);
212214

213215
int oldValue = App.naUnitAnimTable.get(4+1 + i*entryLength + animationId);
@@ -497,6 +499,124 @@ public void applyMaxLevelPatch() {
497499

498500
}
499501

502+
@FXML
503+
public void applyMPGainPatch() {
504+
if (App.archive != null) {
505+
506+
TextInputDialog dialog = new TextInputDialog(Integer.toString(10));
507+
dialog.setTitle("MP gain");
508+
dialog.setHeaderText("Flat MP per turn");
509+
var result = dialog.showAndWait();
510+
if (!result.isPresent()) return;
511+
512+
int flatRegen;
513+
try {
514+
flatRegen = Integer.parseInt(result.get());
515+
if (flatRegen < 0) throw new Exception("Value must be 0 or higher");
516+
} catch (Exception e) {
517+
Alert loadAlert = new Alert(AlertType.ERROR);
518+
loadAlert.setTitle("MP gain patch");
519+
loadAlert.setHeaderText(e.toString());
520+
loadAlert.show();
521+
return;
522+
}
523+
524+
dialog.setHeaderText("%Max MP per turn");
525+
result = dialog.showAndWait();
526+
if (!result.isPresent()) return;
527+
528+
int percentage;
529+
try {
530+
percentage = Integer.parseInt(result.get());
531+
if (percentage < 0 || 100 < percentage) throw new Exception("Value must be between 0 and 100");
532+
} catch (Exception e) {
533+
Alert loadAlert = new Alert(AlertType.ERROR);
534+
loadAlert.setTitle("MP gain patch");
535+
loadAlert.setHeaderText(e.toString());
536+
loadAlert.show();
537+
return;
538+
}
539+
int divisor = percentage != 0 ? 100 / percentage : 99999;
540+
541+
542+
ByteBuffer btlprocessFile = App.archive.getFile("battle/btlprocess.sbn");
543+
SBN btlprocess = new SBN(btlprocessFile.rewind());
544+
545+
int insertion_point = 0x160c;
546+
int i = 0;
547+
for (; i < btlprocess.commands.size(); i++) {
548+
if (btlprocess.commands.get(i).address == insertion_point) break;
549+
}
550+
551+
ByteBuffer flatParams = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
552+
flatParams.putShort((short)0);
553+
flatParams.putInt(flatRegen);
554+
ByteBuffer divisorParams = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
555+
divisorParams.putShort((short)0);
556+
divisorParams.putInt(divisor);
557+
558+
ArrayList<Command> newCommands = new ArrayList<>();
559+
newCommands.add(new Command(0, (byte)0x0, (byte)0x8, flatParams.array()));
560+
newCommands.add(new Command(0, (byte)0x2, (byte)0x8, new byte[]{0x00, 0x00, (byte)0xfe, (byte)0xff, 0x01, 0x00}));
561+
newCommands.add(new Command(0, (byte)0x1c, (byte)0x4, new byte[]{0x01, 0x00}));
562+
newCommands.add(new Command(0, (byte)0x32, (byte)0x4, new byte[]{(byte)0xff, (byte)0xff}));
563+
newCommands.add(new Command(0, (byte)0x31, (byte)0x4, new byte[]{0x18, 0x06}));
564+
newCommands.add(new Command(0, (byte)0x1c, (byte)0x4, new byte[]{(byte)0xff, (byte)0xff}));
565+
newCommands.add(new Command(0, (byte)0x2b, (byte)0x4, new byte[]{0x00, 0x00}));
566+
newCommands.add(new Command(0, (byte)0x0, (byte)0x8, divisorParams.array()));
567+
newCommands.add(new Command(0, (byte)0x9, (byte)0x2, new byte[]{}));
568+
newCommands.add(new Command(0, (byte)0x6, (byte)0x2, new byte[]{}));
569+
570+
int additionalBytes = newCommands.stream().mapToInt(x -> x.size).sum();
571+
newCommands.add(new Command(0, (byte)0x2d, (byte)0x10, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
572+
573+
574+
if (btlprocess.commands.get(i).opcode == 0x30) {
575+
// Already applied
576+
System.out.println("Already applied");
577+
ByteBuffer params = ByteBuffer.wrap(btlprocess.commands.get(i).parameters).order(ByteOrder.LITTLE_ENDIAN);
578+
params.getShort();
579+
int jumpAddress = params.getInt();
580+
for (; i < btlprocess.commands.size(); i++) {
581+
if (btlprocess.commands.get(i).address == insertion_point+jumpAddress) break;
582+
}
583+
int j = 0;
584+
for (; i < btlprocess.commands.size(); i++, j++) {
585+
btlprocess.commands.set(i, newCommands.get(j));
586+
}
587+
if (i < btlprocess.commands.size() || j < newCommands.size()) {
588+
System.err.println(String.format("Sizes don't match: %d < %d or %d < %d", i, btlprocess.commands.size(), j, newCommands.size()));
589+
}
590+
} else {
591+
System.out.println("Not applied yet");
592+
for (Command c : newCommands) {
593+
btlprocess.commands.add(c);
594+
}
595+
596+
ByteBuffer params = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
597+
params.putShort((short)0);
598+
params.putInt(btlprocess.endAddress - insertion_point);
599+
//btlprocess.commands.set(i, new Command(0, (byte)0x30, (byte)0x8, params.array()));
600+
btlprocess.commands.get(i).opcode = 0x30;
601+
btlprocess.commands.get(i).size = 0x8;
602+
btlprocess.commands.get(i).parameters = params.array();
603+
}
604+
605+
606+
ByteBuffer newBtlprocess = btlprocess.toByteBuffer();
607+
608+
App.archive.setFile("battle/btlprocess.sbn", newBtlprocess);
609+
610+
611+
String alertText = String.format("MP gain set to %d + %d%% Max MP per turn", flatRegen, percentage);
612+
Alert loadAlert = new Alert(AlertType.INFORMATION);
613+
loadAlert.setTitle("MP gain patch");
614+
loadAlert.setHeaderText(alertText);
615+
loadAlert.show();
616+
}
617+
618+
}
619+
500620
public void loadPatches() {
501621
patchedExpandedTopSprites.set(App.arm9.getInt(0x000b5ab4) != 0xe5d00018);
502622
patchedSignedEquipmentStats.set(App.arm9.getInt(0x000cfcd8) != 0xe5d01017);

src/main/java/org/ruru/ffta2editor/SpritesController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void initialize() {
191191

192192
public void refresh() {
193193
UnitSst sst = App.unitSstList.get(unitProperty.getValue().unitIndex);
194-
List<UnitAnimation> animations = sst.asList().stream().filter(node -> node.key() != 0xFF && node.key() != 0xF0).map(node -> sst.getAnimation(node.key())).filter(x -> x != null).sorted(Comparator.comparingInt(x -> x.key)).toList();
194+
List<UnitAnimation> animations = sst.asList().stream().filter(node -> node.key != 0xFF && node.key != 0xF0).map(node -> sst.getAnimation(node.key)).filter(x -> x != null).sorted(Comparator.comparingInt(x -> x.key)).toList();
195195
animationList.setItems(FXCollections.observableArrayList(animations));
196196
animationsTab.setDisable(false);
197197

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.ruru.ffta2editor.model.battle;
2+
3+
import java.nio.ByteBuffer;
4+
import java.nio.ByteOrder;
5+
import java.util.ArrayList;
6+
import java.util.Comparator;
7+
8+
import org.ruru.ffta2editor.utility.BinaryTree;
9+
import org.ruru.ffta2editor.utility.LZSS;
10+
11+
import javafx.scene.control.Alert;
12+
import javafx.scene.control.Alert.AlertType;
13+
14+
public class SBN {
15+
16+
public static class Command {
17+
public int address;
18+
19+
public byte opcode;
20+
public byte size;
21+
public byte[] parameters;
22+
23+
public Command(ByteBuffer buffer) {
24+
this.address = buffer.position();
25+
this.opcode = buffer.get();
26+
this.size = buffer.get();
27+
this.parameters = new byte[this.size-2];
28+
buffer.get(parameters);
29+
}
30+
31+
public Command(int address, byte opcode, byte size, byte[] parameters) {
32+
this.address = address;
33+
this.opcode = opcode;
34+
this.size = size;
35+
this.parameters = parameters;
36+
}
37+
38+
public byte[] toBytes() {
39+
ByteBuffer newSbn = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
40+
newSbn.put(opcode);
41+
newSbn.put(size);
42+
newSbn.put(parameters);
43+
return newSbn.array();
44+
}
45+
}
46+
47+
public BinaryTree<Command> tree;
48+
public ArrayList<Command> commands = new ArrayList<>();
49+
private short numJumps;
50+
private short unknown1;
51+
private int startAddress;
52+
public int endAddress;
53+
private int unknown2;
54+
55+
public SBN(ByteBuffer buffer) {
56+
buffer.order(ByteOrder.LITTLE_ENDIAN);
57+
this.numJumps = buffer.getShort();
58+
this.unknown1 = buffer.getShort();
59+
this.startAddress = buffer.getInt();
60+
this.endAddress = buffer.getInt();
61+
this.unknown2 = buffer.getInt();
62+
63+
tree = new BinaryTree<>(buffer.slice().order(ByteOrder.LITTLE_ENDIAN));
64+
65+
buffer.position(startAddress);
66+
while (buffer.position() < endAddress) {
67+
commands.add(new Command(buffer));
68+
}
69+
70+
tree.traverseInOrder(node -> commands.stream().filter(t -> t.address == node.offset*0x10).findFirst().ifPresent(t -> node.value = t));
71+
}
72+
73+
public byte[] toBytes() {
74+
return toByteBuffer().array();
75+
}
76+
77+
public ByteBuffer toByteBuffer() {
78+
tree.traverseInOrder(node -> node.offset = node.value.address / 0x10);
79+
byte[] treeBytes = tree.toBytes(); // Just for size
80+
81+
ByteBuffer newSbn = ByteBuffer.allocate(0x10 + treeBytes.length + commands.stream().mapToInt(c -> c.size).sum()).order(ByteOrder.LITTLE_ENDIAN);
82+
numJumps = (short)tree.size;
83+
startAddress = 0x10 + treeBytes.length;
84+
endAddress = newSbn.capacity();
85+
86+
87+
newSbn.putShort(numJumps);
88+
newSbn.putShort(unknown1);
89+
newSbn.putInt(startAddress);
90+
newSbn.putInt(endAddress);
91+
newSbn.putInt(unknown2);
92+
93+
newSbn.position(startAddress);
94+
95+
for (Command c : commands) {
96+
c.address = newSbn.position();
97+
newSbn.put(c.toBytes());
98+
}
99+
if (newSbn.position() < newSbn.limit()) {
100+
System.err.println(String.format("Incorrect SBN size: %d < %d", newSbn.position(), newSbn.limit()));
101+
}
102+
103+
newSbn.position(0x10);
104+
tree.traverseInOrder(node -> node.offset = node.value.address / 0x10);
105+
treeBytes = tree.toBytes(); // Command addresses have been updated
106+
newSbn.put(treeBytes);
107+
108+
return newSbn.rewind();
109+
}
110+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.ruru.ffta2editor.model.unitSst;
2+
3+
import java.nio.ByteBuffer;
4+
5+
import org.ruru.ffta2editor.utility.BinaryTreeNode;
6+
7+
public class SstHeaderNode extends BinaryTreeNode<byte[]> {
8+
public byte dataType;
9+
public byte animationId;
10+
11+
public SstHeaderNode(ByteBuffer bytes, int index) {
12+
super(bytes, index);
13+
this.dataType = (byte)(key & 0xff);
14+
this.animationId = (byte)(key >>> 0x8);
15+
}
16+
17+
public SstHeaderNode(int index, byte dataType, byte animationId, int offset) {
18+
super(index, (short)key(dataType, animationId), offset);
19+
this.dataType = dataType;
20+
this.animationId = animationId;
21+
}
22+
23+
public static int key(byte dataType, byte animationId) {
24+
return (Byte.toUnsignedInt(animationId) << 0x8) | Byte.toUnsignedInt(dataType);
25+
}
26+
}

0 commit comments

Comments
 (0)