-
Notifications
You must be signed in to change notification settings - Fork 2
/
LiveSplit.Yakuza4.asl
238 lines (207 loc) · 9.9 KB
/
LiveSplit.Yakuza4.asl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
Documentation for sanity! Ghidra hates the Steam version, but the GOG version works well enough.
EnemyCount has always used the pointer for CActionFighterManager, but it was previously going outside
that instance's boundaries to land on EnemyManager instead, which is usually (not always) contiguous in memory.
This explains why this pointer sometimes can seem to sour and not work... probably.
But FighterManager has a pointer to EnemyManager, which we're now using instead. Blame Cheat Engine.
Chapter: CActionManager -> CActionRandomEncount ptr -> 0x204
It only increments when gameplay first resumes after a chapter begins (i.e. after title card and cutscenes),
which is fine for our use case of determining the final boss split.
Character: Static value, not sure yet what shepherds it.
Paradigm: Static value inc/decremented by CSE interfaces. I think it's just a count (possibly of CSEObj?).
I need to find something to replace this, because we've had one instance of values being different than expected
for one player in particular.
Start: Static value written through CActionLoadGame. It's set to values of 0, 1, or 2.
It's a mode (or steps?) somewhere in the screen fade process.
FileTimer: IGT, just a value in CSaveData which is a static instance.
States: We're tracking when the game sets scenario values, which are one-to-one with the file scenario_state.bin.
The count is the number of new states set in this frame, and the entries are the states in an array.
This is not the only way that the game sets these states, but this is the method for most cases.
*/
state("Yakuza4", "Steam")
{
byte EnemyCount: 0x197C440, 0x368, 0x33;
byte Chapter: 0x197C838, 0x640, 0x204;
byte Character: 0x19806D0; // 0 - 3: Kiryu, Akiyama, Saejima, Tanimura
short Paradigm: 0x1980D94; // Unique value for different gameplay modes, menus, etc.
byte Start: 0x198C624; // Black screen / screen fade flag
int FileTimer: 0x19A3AC8; // In-game timer
uint StateCount: 0x1A1FA20, 0x0;
byte384 StateEntries: 0x1A1FA20, 0x4;
}
state("Yakuza4", "M Store")
{
byte EnemyCount: 0x1C590F0, 0x368, 0x33;
byte Chapter: 0x1C594E8, 0x640, 0x204;
byte Character: 0x1C5D4A0;
short Paradigm: 0x1C5DB64;
byte Start: 0x1C693F4;
int FileTimer: 0x1C80888;
uint StateCount: 0x1CFC5C0, 0x0;
byte384 StateEntries: 0x1CFC5C0, 0x4;
}
state("Yakuza4", "GOG")
{
byte EnemyCount: 0x18F68C0, 0x368, 0x33;
byte Chapter: 0x18F6CB8, 0x640, 0x204;
byte Character: 0x18FAB50;
short Paradigm: 0x18FB214;
byte Start: 0x1906AA4;
int FileTimer: 0x191DF38;
uint StateCount: 0x1999ea0, 0x0;
byte384 StateEntries: 0x1999ea0, 0x4;
}
startup
{
settings.Add("CHAPTER", false, "Chapter End Splits");
settings.Add("sa", true, "Shun Akiyama", "CHAPTER");
settings.Add("4-0", true, "Chapter 1: The Infamous Loan Shark", "sa");
settings.Add("5-0", true, "Chapter 2: The One", "sa");
settings.Add("6-0", true, "Chapter 3: Trouble in the Tojo Clan", "sa");
settings.Add("6-89", true, "Chapter 4: The Promise", "sa");
settings.Add("ts", true, "Taiga Saejima", "CHAPTER");
settings.Add("8-0", true, "Chapter 1: To the Truth", "ts");
settings.Add("9-0", true, "Chapter 2: Tiger and Dragon", "ts");
settings.Add("10-0", true, "Chapter 3: The 25 Year Vacuum", "ts");
settings.Add("10-82", true, "Chapter 4: Oath of Brotherhood", "ts");
settings.Add("mt", true, "Masayoshi Tanimura", "CHAPTER");
settings.Add("12-0", true, "Chapter 1: The Kamurocho Parasite", "mt");
settings.Add("13-0", true, "Chapter 2: The Perpetrator", "mt");
settings.Add("14-0", true, "Chapter 3: Door to the Truth", "mt");
settings.Add("14-31", true, "Chapter 4: A Detective's Honor", "mt");
settings.Add("kk", true, "Kazuma Kiryu", "CHAPTER");
settings.Add("16-0", true, "Chapter 1: Reunion", "kk");
settings.Add("17-0", true, "Chapter 2: To Kamurocho", "kk");
settings.Add("18-0", true, "Chapter 3: Encounter", "kk");
settings.Add("18-27", true, "Chapter 4: Chain of Betrayal", "kk");
settings.Add("FIGHT", false, "Fight Splits");
settings.Add("3-11", false, "Akiyama Tutorial 1", "FIGHT");
settings.Add("3-20", false, "Akiyama Tutorial 2", "FIGHT");
settings.Add("3-25", false, "Ihara", "FIGHT");
settings.Add("4-21", false, "Ch.2 Rooftop", "FIGHT");
settings.Add("5-31", false, "Champion District", "FIGHT"); // Guess
settings.Add("6-59", false, "Midorikawa", "FIGHT");
settings.Add("6-88", false, "Minami 1", "FIGHT");
settings.Add("7-7", false, "Saejima Tutorial", "FIGHT");
settings.Add("7-22", false, "Prison Yard Chain", "FIGHT"); // Fight is 20
settings.Add("7-30", false, "Prison Yard Hoe", "FIGHT"); // Fight is 29
settings.Add("7-41", false, "Saito 1 (leaving)", "FIGHT");
settings.Add("7-43", false, "Saito 2 (leaving)", "FIGHT");
settings.Add("7-51", false, "Prison Rooftop", "FIGHT");
settings.Add("7-53", false, "Bottom of the Staircase Gang", "FIGHT");
settings.Add("7-58", false, "Saito 3", "FIGHT");
settings.Add("8-9", false, "Kiryu", "FIGHT");
settings.Add("9-42", false, "Park Fight", "FIGHT");
settings.Add("9-52", false, "Ibrahimovic", "FIGHT");
settings.Add("10-66", false, "Minami 2", "FIGHT");
settings.Add("10-71", false, "Majima", "FIGHT");
settings.Add("11-29", false, "Outside Midori", "FIGHT");
settings.Add("11-36", false, "Sword and Gun at the Docks", "FIGHT");
settings.Add("12-38", false, "Briefcase Fights", "FIGHT");
settings.Add("12-44", false, "Rooftop Briefcase Fight", "FIGHT");
settings.Add("12-50", false, "Toyosaki", "FIGHT");
settings.Add("14-21", false, "Sugiuchi", "FIGHT");
settings.Add("15-22", false, "Sayonara, Saito", "FIGHT");
settings.Add("17-31", false, "AkiMura", "FIGHT");
settings.Add("18-15", false, "Entering the Mall Proper", "FIGHT");
settings.Add("18-16", false, "Kamurocho Hills Elevator", "FIGHT");
settings.Add("18-17", false, "Kamurocho Hills Rooftop Key", "FIGHT");
settings.Add("18-20", false, "Kamurocho Hills Rooftop Fight", "FIGHT");
settings.Add("19-18", false, "Arai", "FIGHT");
settings.Add("19-21", false, "Balls Out", "FIGHT");
settings.Add("19-24", false, "Daigo", "FIGHT");
settings.Add("end", false, "Munakata", "FIGHT");
settings.SetToolTip("end", "Splits on the last hit of the final boss.");
// Setpiece 1-4: 6-57
settings.Add("OTHER", false, "Other Splits");
settings.Add("315-5", false, "Saigo Tutorial Chase", "OTHER");
// settings.Add("6-67", false, "Club Elise 2", "OTHER"); // Guess
settings.Add("9-13", false, "Kido Chase", "OTHER"); // 9-12 also fine
settings.Add("12-35", false, "Walk and Talk", "OTHER");
settings.Add("12-41", false, "Briefcase Chase", "OTHER");
settings.Add("12-48", false, "Briefcase Chase 2", "OTHER");
settings.Add("14-18", false, "Boat Chase", "OTHER");
if (timer.CurrentTimingMethod == TimingMethod.RealTime)
{
var timingMessage = MessageBox.Show (
"This game uses Time without Loads (Game Time) as the main timing method.\n"+
"LiveSplit is currently set to show Real Time (RTA).\n"+
"Would you like to set the timing method to Game Time?",
"LiveSplit | Yakuza 4",
MessageBoxButtons.YesNo, MessageBoxIcon.Question
);
if (timingMessage == DialogResult.Yes)
timer.CurrentTimingMethod = TimingMethod.GameTime;
}
}
init
{
vars.Splits = new HashSet<string>();
switch(modules.First().ModuleMemorySize)
{
case 60833792:
version = "Steam";
break;
case 60022784:
version = "GOG";
break;
default: // 63631360?
version = "M Store";
break;
}
}
// Pause the timer while the screen is black, but only if IGT has stopped.
isLoading
{
return current.Start == 2 && current.FileTimer == old.FileTimer;
}
onStart
{
timer.IsGameTimePaused = true;
}
start
{
// Start at choosing difficulty
return (current.FileTimer == 0 && (current.Paradigm == 212 || current.Paradigm == 213));
// Start at the first title card, i.e. after the disclaimer in English
// return (current.Paradigm == 185);
}
split
{
// For each scenario state being changed:
for(int i = 0; i < current.StateCount; i++)
{
int adj = i * 12; // Adjust for the size of the structs
// If the state is being set to true:
if (current.StateEntries[8 + adj] == 1)
{
string s = string.Format("{0}-{1}",
BitConverter.ToUInt32(current.StateEntries, adj), // Scenario section
BitConverter.ToUInt32(current.StateEntries, 4 + adj)); // Scenario state
if (!vars.Splits.Contains(s))
{
print(s);
vars.Splits.Add(s);
return settings[s];
}
}
}
if (current.Chapter == 17 && current.Character == 3 && vars.Splits.Contains("19-26") && old.EnemyCount > 0 && current.EnemyCount == 0 && !vars.Splits.Contains("end"))
{
vars.Splits.Add("end");
return settings["end"];
}
}
reset
{
return (old.Paradigm == 209 && current.Paradigm == 221);
}
onReset
{
vars.Splits.Clear();
}
exit
{
timer.IsGameTimePaused = true;
vars.Splits.Clear();
}