Fixed bug in quest parsing code.

This commit is contained in:
Daan Vanden Bosch 2019-06-23 02:45:56 +02:00
parent 591640344a
commit ab9a6afd54
4 changed files with 87 additions and 77 deletions

View File

@ -81,9 +81,8 @@
"GuilShark": 24,
"GrassAssassin": 7,
"PoisonLily": 41,
"PouillySlime": 2,
"PofuillySlime": 12,
"NanoDragon": 12,
"PofuillySlime": 10,
"PanArms": 13
}
},
@ -92,14 +91,13 @@
"name": "2-4:Waterway Shadow",
"episode": 1,
"enemyCounts": {
"PoisonLily": 36,
"PoisonLily": 40,
"EvilShark": 165,
"PalShark": 87,
"GuilShark": 28,
"GrassAssassin": 16,
"NanoDragon": 17,
"PofuillySlime": 4,
"NarLily": 4,
"PanArms": 4,
"DeRolLe": 1
}
@ -243,15 +241,14 @@
"name": "Addicting Food",
"episode": 1,
"enemyCounts": {
"PoisonLily": 62,
"PoisonLily": 64,
"PalShark": 38,
"EvilShark": 195,
"GrassAssassin": 19,
"GuilShark": 15,
"NanoDragon": 19,
"PofuillySlime": 13,
"PanArms": 7,
"NarLily": 2
"PanArms": 7
}
},
{
@ -280,13 +277,12 @@
"enemyCounts": {
"EvilShark": 94,
"PalShark": 31,
"PoisonLily": 20,
"PoisonLily": 22,
"GrassAssassin": 12,
"NanoDragon": 14,
"PofuillySlime": 8,
"GuilShark": 10,
"PanArms": 3,
"NarLily": 2
"PanArms": 3
}
},
{
@ -616,12 +612,11 @@
"enemyCounts": {
"GrassAssassin": 14,
"EvilShark": 38,
"PoisonLily": 15,
"PoisonLily": 17,
"NanoDragon": 10,
"PalShark": 16,
"PanArms": 6,
"GuilShark": 12,
"NarLily": 2
"GuilShark": 12
}
},
{
@ -1024,11 +1019,10 @@
"EvilShark": 24,
"PalShark": 30,
"GuilShark": 34,
"PoisonLily": 42,
"PoisonLily": 45,
"GrassAssassin": 81,
"NanoDragon": 17,
"PanArms": 12,
"NarLily": 3,
"Dubswitch": 4,
"Dubchic": 39,
"Gilchic": 44,
@ -1169,14 +1163,12 @@
"Hildebear": 5,
"EvilShark": 125,
"PalShark": 44,
"NarLily": 6,
"PoisonLily": 23,
"GrassAssassin": 14,
"GuilShark": 25,
"PanArms": 6,
"PoisonLily": 17,
"NanoDragon": 12,
"PouillySlime": 2,
"PofuillySlime": 9,
"PofuillySlime": 11,
"Dragon": 1
}
},
@ -1401,14 +1393,13 @@
"name": "5-1:Test/VR Temple 1",
"episode": 2,
"enemyCounts": {
"PoisonLily2": 29,
"PoisonLily2": 31,
"Dimenian2": 17,
"LaDimenian2": 33,
"GrassAssassin2": 16,
"RagRappy2": 13,
"Hildebear2": 5,
"Monest2": 3,
"NarLily2": 2
"Monest2": 3
}
},
{
@ -1448,13 +1439,12 @@
"name": "5-4:Test/VR Temple 4",
"episode": 2,
"enemyCounts": {
"PoisonLily2": 56,
"PoisonLily2": 61,
"Dimenian2": 14,
"LaDimenian2": 47,
"GrassAssassin2": 24,
"RagRappy2": 29,
"Hildebear2": 5,
"NarLily2": 5,
"Monest2": 5,
"SoDimenian2": 23,
"DarkBelra2": 8
@ -1687,13 +1677,12 @@
"Dimenian2": 48,
"LaDimenian2": 41,
"SoDimenian2": 54,
"PoisonLily2": 95,
"PoisonLily2": 105,
"GrassAssassin2": 20,
"RagRappy2": 69,
"Hildebear2": 38,
"Monest2": 13,
"DarkBelra2": 36,
"NarLily2": 10,
"SinowBerill": 12,
"SinowSpigell": 16,
"Gibbles": 4,
@ -2426,13 +2415,12 @@
"SoDimenian2": 55,
"Dimenian2": 48,
"LaDimenian2": 41,
"PoisonLily2": 95,
"PoisonLily2": 105,
"GrassAssassin2": 20,
"RagRappy2": 69,
"Hildebear2": 38,
"Monest2": 13,
"DarkBelra2": 36,
"NarLily2": 10,
"SinowBerill": 12,
"SinowSpigell": 16,
"Gibbles": 4,

View File

@ -95,20 +95,42 @@ function parseObjectCode(cursor: ArrayBufferCursor, lenient: boolean): Instructi
break;
}
const [, mnemonic, mask] = list[opcode];
const opargs = parseInstructionArguments(cursor, mask);
let [, mnemonic, mask] = list[opcode];
if (!opargs) {
logger.error(`Parameters unknown for opcode 0x${opcode.toString(16).toUpperCase()}.`);
break;
if (mask == null) {
let fullOpcode = mainOpcode;
if (mainOpcode === 0xF8 || mainOpcode === 0xF9) {
fullOpcode = (fullOpcode << 8) | opcode;
}
logger.warn(`Parameters unknown for opcode 0x${fullOpcode.toString(16).toUpperCase()}, assuming 0.`);
instructions.push({
opcode,
mnemonic,
args: [],
size: opsize
});
} else {
try {
const opargs = parseInstructionArguments(cursor, mask);
instructions.push({
opcode,
mnemonic,
args: opargs.args,
size: opsize + opargs.size
});
} catch (e) {
instructions.push({
opcode,
mnemonic,
args: [],
size: opsize
});
}
}
instructions.push({
opcode,
mnemonic,
args: opargs.args,
size: opsize + opargs.size
});
}
} catch (e) {
if (lenient) {
@ -121,14 +143,13 @@ function parseObjectCode(cursor: ArrayBufferCursor, lenient: boolean): Instructi
return instructions;
}
function parseInstructionArguments(cursor: ArrayBufferCursor, mask: string | null) {
if (mask == null) {
return;
}
function parseInstructionArguments(
cursor: ArrayBufferCursor,
mask: string
): { args: any[], size: number } {
const oldPos = cursor.position;
const args = [];
let size = 0;
let argsSize: number;
outer:
for (let i = 0; i < mask.length; ++i) {
@ -143,71 +164,63 @@ function parseInstructionArguments(cursor: ArrayBufferCursor, mask: string | nul
// Unsigned integers
case 'B':
args.push(cursor.u8());
size += 1;
break;
case 'W':
args.push(cursor.u16());
size += 2;
break;
case 'L':
args.push(cursor.u32());
size += 4;
break;
// Signed integers
case 'I':
args.push(cursor.i32());
size += 4;
break;
// Floats
case 'f':
case 'F':
args.push(cursor.f32());
size += 4;
break;
// Registers?
case 'R':
case 'r':
size += 1;
cursor.seek(1);
break;
// Pointers to unsigned integers?
// Registers with unsigned integers?
case 'b':
args.push(cursor.u8());
size += 1;
break;
case 'w':
args.push(cursor.u16());
size += 2;
break;
case 'l':
args.push(cursor.u32());
size += 4;
break;
// Pointers to signed integers?
// Registers with signed integers?
case 'i':
args.push(cursor.i32());
size += 4;
break;
// Variably sized data (e.g. strings)?
// Variably sized data?
case 'j':
case 'J':
size += 1 + cursor.seek(size).u8() * 2;
argsSize = 2 * cursor.u8();
cursor.seek(argsSize);
break;
case 't':
case 'T':
size += 1 + cursor.seek(size).u8();
argsSize = cursor.u8();
cursor.seek(argsSize);
break;
// Strings
case 's':
case 'S':
while (cursor.u16()) {
size += 2;
}
size += 2;
while (cursor.u16()) { }
break;
default:
@ -215,8 +228,7 @@ function parseInstructionArguments(cursor: ArrayBufferCursor, mask: string | nul
}
}
cursor.seekStart(oldPos + size);
return { args, size };
return { args, size: cursor.position - oldPos };
}
const opcodeList: Array<[number, string, string | null]> = [

View File

@ -106,7 +106,7 @@ export function parseDat(cursor: ArrayBufferCursor): DatFile {
const rotationY = cursor.i32() / 0xFFFF * 2 * Math.PI;
const rotationZ = cursor.i32() / 0xFFFF * 2 * Math.PI;
const unknown3 = cursor.u8Array(4);
const flags = cursor.u32();
const flags = cursor.f32();
const unknown4 = cursor.u8Array(12);
const skin = cursor.u32();
const unknown5 = cursor.u8Array(4);
@ -202,7 +202,7 @@ export function writeDat({ objs, npcs, unknowns }: DatFile): ArrayBufferCursor {
cursor.writeI32(Math.round(npc.rotation.y / (2 * Math.PI) * 0xFFFF));
cursor.writeI32(Math.round(npc.rotation.z / (2 * Math.PI) * 0xFFFF));
cursor.writeU8Array(npc.unknown[2]);
cursor.writeU32(npc.flags);
cursor.writeF32(npc.flags);
cursor.writeU8Array(npc.unknown[3]);
cursor.writeU32(npc.skin);
cursor.writeU8Array(npc.unknown[4]);

View File

@ -1,6 +1,6 @@
import { ArrayBufferCursor } from '../../ArrayBufferCursor';
import * as prs from '../../compression/prs';
import { parseDat, writeDat, DatObject, DatNpc } from './dat';
import { parseDat, writeDat, DatObject, DatNpc, DatFile } from './dat';
import { parseBin, writeBin, Instruction } from './bin';
import { parseQst, writeQst } from './qst';
import {
@ -64,7 +64,7 @@ export function parseQuest(cursor: ArrayBufferCursor, lenient: boolean = false):
if (func0Ops) {
episode = getEpisode(func0Ops);
areaVariants = getAreaVariants(episode, func0Ops, lenient);
areaVariants = getAreaVariants(dat, episode, func0Ops, lenient);
} else {
logger.warn(`Function 0 offset ${bin.functionOffsets[0]} is invalid.`);
}
@ -132,11 +132,22 @@ function getEpisode(func0Ops: Instruction[]): number {
}
function getAreaVariants(
dat: DatFile,
episode: number,
func0Ops: Instruction[],
lenient: boolean
): AreaVariant[] {
// Add area variants that have npcs or objects even if there are no BB_Map_Designate instructions for them.
const areaVariants = new Map();
for (const npc of dat.npcs) {
areaVariants.set(npc.areaId, 0);
}
for (const obj of dat.objs) {
areaVariants.set(obj.areaId, 0);
}
const bbMaps = func0Ops.filter(op => op.mnemonic === 'BB_Map_Designate');
for (const bbMap of bbMaps) {
@ -224,7 +235,7 @@ function parseNpcData(episode: number, npcs: DatNpc[]): QuestNpc[] {
// TODO: detect Mothmant, St. Rappy, Hallo Rappy, Egg Rappy, Death Gunner, Bulk and Recon.
function getNpcType(episode: number, { typeId, flags, skin, areaId }: DatNpc): NpcType {
const regular = (flags & 0x800000) === 0;
const regular = Math.abs(flags - 1) > 0.00001;
switch (`${typeId}, ${skin % 3}, ${episode}`) {
case `${0x044}, 0, 1`: return NpcType.Booma;
@ -266,11 +277,6 @@ function getNpcType(episode: number, { typeId, flags, skin, areaId }: DatNpc): N
case `${0x041}, 1, 2`: return NpcType.LoveRappy;
case `${0x041}, 1, 4`: return NpcType.DelRappy;
case `${0x061}, 0, 1`: return areaId > 15 ? NpcType.DelLily : NpcType.PoisonLily;
case `${0x061}, 0, 2`: return areaId > 15 ? NpcType.DelLily : NpcType.PoisonLily2;
case `${0x061}, 1, 1`: return areaId > 15 ? NpcType.DelLily : NpcType.NarLily;
case `${0x061}, 1, 2`: return areaId > 15 ? NpcType.DelLily : NpcType.NarLily2;
case `${0x080}, 0, 1`: return NpcType.Dubchic;
case `${0x080}, 0, 2`: return NpcType.Dubchic2;
case `${0x080}, 1, 1`: return NpcType.Gilchic;
@ -306,6 +312,10 @@ function getNpcType(episode: number, { typeId, flags, skin, areaId }: DatNpc): N
case `${0x060}, 1`: return NpcType.GrassAssassin;
case `${0x060}, 2`: return NpcType.GrassAssassin2;
case `${0x061}, 1`: return areaId > 15 ? NpcType.DelLily : (
regular ? NpcType.PoisonLily : NpcType.NarLily);
case `${0x061}, 2`: return areaId > 15 ? NpcType.DelLily : (
regular ? NpcType.PoisonLily2 : NpcType.NarLily2);
case `${0x062}, 1`: return NpcType.NanoDragon;
case `${0x064}, 1`: return regular ? NpcType.PofuillySlime : NpcType.PouillySlime;
case `${0x065}, 1`: return NpcType.PanArms;