CEN cutscene parser (#1680)

`CutsceneUnk2` uses 8 prims but only 7 are allocated. It looks like at
some point the game writes to the address `0x00000000` but I did not
confirm it with a PS1 debugger. This is a potential bug. `CutsceneUnk2`
signature was also wrong.

I had to get rid of `GfxBank` in `src/st/cen/header.c` and I probably
need to do the same for the other header files. The code reads chunks of
WORDS at the time and a structure would be too large to include
`GFX_TERMINATE` in it.

CEN is there but not linked because it conflicts with some WRP symbols.
I decided to take a much simpler approach compared to what I did with
WRP (which needs to be refactored later on).

There's a cutscene parser. The asset manager is exporting it to
`src/st/cen/cutscene_data.h` if you want to have a look. I know I am
still using `config/assets.us.weapon.yaml` and it probably needs to be
renamed as `config/assets.us.yaml` later on. I am still deciding.
This commit is contained in:
Luciano Ciccariello 2024-09-27 23:13:29 +01:00 committed by GitHub
parent 854683e40c
commit 807375d669
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 340 additions and 110 deletions

View File

@ -256,6 +256,40 @@ set(SOURCE_FILES_STAGE_SEL
src/st/sel/3642C.c
)
set(SOURCE_FILES_STAGE_CEN
src/pc/stages/stage_cen.c
src/st/cen/header.c
src/st/cen/e_laydef.c
src/st/cen/e_init.c
src/st/cen/rooms.c
src/st/cen/e_layout.c
src/st/cen/cutscene_data.c
src/st/cen/tile_data.c
src/st/cen/sprites.c
src/st/cen/st_debug.c
src/st/cen/e_breakable.c
src/st/cen/DB18.c
src/st/cen/holyglassescutscene.c
src/st/cen/F890.c
src/st/cen/st_update.c
src/st/cen/collision.c
src/st/cen/create_entity.c
src/st/cen/e_red_door.c
src/st/cen/st_common.c
src/st/cen/e_collect.c
src/st/cen/blit_char.c
src/st/cen/e_misc.c
src/st/cen/e_stage_name.c
src/st/cen/e_particles.c
src/st/cen/e_room_fg.c
src/st/cen/popup.c
src/st/cen/prim_helpers.c
src/st/cen/header.c
src/st/cen/create_entity.c
src/st/cen/holyglassescutscene.c
src/st/cen/e_collect.c
)
set(SOURCE_FILES_STAGE_WRP
src/pc/stages/stage_wrp.c
src/st/wrp/d_1b8.c

View File

@ -1,4 +1,13 @@
files:
- target: disks/us/ST/CEN/CEN.BIN
asset_path: assets/st/cen
src_path: src/st/cen
segments:
- start: 0
vram: 0x80180000
assets:
- [0x13F0, cutscene, cutscene_data]
- [0x1658, skip]
- target: disks/us/BIN/WEAPON0.BIN
asset_path: assets/weapon
src_path: src/weapon

View File

@ -54,7 +54,16 @@ segments:
- [0x1248, .data, e_room_fg]
- [0x12D4, .data, rooms]
- [0x1300, .data, e_layout] # layout entries data
- [0x13F0, data]
- [0x13F0, .data, cutscene_data]
- [0x1658, cmp, D_80181658]
- [0x199C, cmp, D_8018199C]
- [0x3A40, cmp, D_80183A40]
- [0x4B70, cmp, D_80184B70]
- [0x5830, cmp, D_80185830]
- [0x658C, raw, D_8018658C]
- [0x678C, raw, D_8018678C]
- [0x698C, raw, D_8018698C]
- [0x69AC, raw, D_801869AC]
- [0x69EC, .data, tile_data] # tile data
- [0x81EC, .data, tile_data] # tile definitions
- [0xC60C, .data, sprites]

35
include/cutscene.h Normal file
View File

@ -0,0 +1,35 @@
#include <game.h>
typedef enum {
CSOP_END_CUTSCENE,
CSOP_LINE_BREAK,
CSOP_SET_SPEED,
CSOP_SET_WAIT,
CSOP_SET_PORTRAIT = 5,
CSOP_NEXT_DIALOG,
CSOP_SET_POS,
CSOP_CLOSE_DIALOG,
CSOP_PLAY_SOUND,
CSOP_WAIT_FOR_SOUND,
CSOP_UNK_11,
CSOP_WAIT_FOR_FLAG = 16,
CSOP_SET_FLAG,
CSOP_LOAD_PORTRAIT = 19,
CSOP_SCRIPT_UNKNOWN_20,
} CutsceneOpcode;
#define END_CUTSCENE() CSOP_END_CUTSCENE
#define LINE_BREAK() CSOP_LINE_BREAK
#define SET_SPEED(x) CSOP_SET_SPEED, x
#define SET_WAIT(x) CSOP_SET_WAIT, x
#define SET_PORTRAIT(clut, side) CSOP_SET_PORTRAIT, clut, !!side
#define NEXT_DIALOG() CSOP_NEXT_DIALOG
#define SET_POS(x, y) CSOP_SET_POS, x, y
#define CLOSE_DIALOG() CSOP_CLOSE_DIALOG
#define PLAY_SOUND(x, y) CSOP_PLAY_SOUND, x, y
#define WAIT_FOR_SOUND() CSOP_WAIT_FOR_SOUND
#define SCRIPT_UNKNOWN_11() CSOP_UNK_11
#define WAIT_FOR_FLAG(x) CSOP_WAIT_FOR_FLAG, x
#define SET_FLAG(x) CSOP_SET_FLAG, x
#define LOAD_PORTRAIT(a, b, c, d, e) CSOP_LOAD_PORTRAIT, a, b, c, d, e
#define SCRIPT_UNKNOWN_20(x, y) CSOP_SCRIPT_UNKNOWN_20, x, y

View File

@ -7,7 +7,7 @@ extern Dialogue g_Dialogue;
#include "../../st/cutscene_unk1.h"
#include "../../st/cutscene_unk2.h"
#include "../../st/set_cutscene_script.h"
#include "../../st/cutscene_unk3.h"
@ -77,7 +77,7 @@ void EntityMariaCutscene(Entity* self) {
DestroyEntity(self);
return;
}
if (CutsceneUnk2(D_us_80181424) & 0xFF) {
if (SetCutsceneScript(D_us_80181424) & 0xFF) {
self->flags |= FLAG_HAS_PRIMS | FLAG_UNK_2000;
g_CutsceneFlags = 0;
D_us_8019AF2C = 0;

View File

@ -1293,8 +1293,8 @@ block_160:
void func_8010BF64(Unkstruct_8010BF64* arg0) {
if (g_PlayableCharacter == PLAYER_ALUCARD) {
u32 unk0C_var = (g_Player.unk0C / 2) & 2;
arg0->unk14 = D_800ACED0[4].x - unk0C_var;
arg0->unk1C = D_800ACED0[4].y + unk0C_var;
arg0->unk14 = D_800ACEE0[0].x - unk0C_var;
arg0->unk1C = D_800ACEE0[0].y + unk0C_var;
arg0->unk18 = D_800ACED0[1].y - 1;
arg0->unk20 = D_800ACEC0[1].y + 1;
} else {

View File

@ -182,6 +182,7 @@ void LoadStageTileset(u8* pTilesetData, size_t len, s32 y) {
}
void InitStageDummy(Overlay* o);
void InitStageCEN(Overlay* o);
void InitStageWrp(Overlay* o);
void InitStageSel(Overlay* o);
void InitPlayerArc(const struct FileUseContent* file);

View File

@ -50,20 +50,33 @@ u8 D_800C4A90[MAX_SIZE_FOR_COMPRESSED_GFX];
// list of exposed API
void FreePrimitives(s32 index);
s32 AllocPrimitives(u8 primType, s32 count);
void func_80102CD8(s32 start);
void SetSpeedX(s32 speed);
Entity* GetFreeEntity(s16 start, s16 end);
void GetEquipProperties(s32 handId, Equipment* res, s32 equipId);
s32 func_800EA5E4(u32);
void LoadGfxAsync(s32 gfxId);
void PlaySfx(s16 sfxId);
void func_800EA538(s32 arg0);
void func_800EA5AC(u16 arg0, u8 arg1, u8 arg2, u8 arg3);
void func_801027C4(u32 arg0);
void func_800EB758(s16 px, s16 py, Entity* e, u8 flags, POLY_GT4* p, u8 flipX);
bool func_80131F68(void);
DR_ENV* func_800EDB08(Primitive* prim);
u16* func_80106A28(u32 arg0, u16 kind);
void func_80118894(Entity* self);
Entity* func_80118970(void);
s16 func_80118B18(Entity* ent1, Entity* ent2, s16 facingLeft);
u32 UpdateUnarmedAnim(s8* frameProps, u16** frames);
void PlayAnimation(s8* frameProps, AnimationFrame** frames);
void func_80118C28(s32 arg0);
void func_8010E168(s32 arg0, s16 arg1);
void func_8010DFF0(s32 arg0, s32 arg1);
void LoadEquipIcon(s32 equipIcon, s32 palette, s32 index);
void AddToInventory(u16 itemId, s32 itemCategory);
u32 PlaySfxVolPan(s16 sfxId, s32 sfxVol, u16 sfxPan);
u32 CheckEquipmentItemCount(u32 itemId, u32 equipType);
void func_8010BF64(Unkstruct_8010BF64* arg0);
void func_800F2288(s32 arg0);
bool CalcPlayerDamage(DamageParam* damage);
void DebugInputWait(const char* msg);
@ -98,8 +111,6 @@ static bool InitBlueprintData(struct FileAsString* file);
s32 func_800EDB58(u8 primType, s32 count);
void func_801027C4(u32 arg0);
bool InitGame(void) {
if (!InitPlatform()) {
return false;
@ -110,11 +121,11 @@ bool InitGame(void) {
api.FreePrimitives = FreePrimitives;
api.AllocPrimitives = AllocPrimitives;
api.CheckCollision = CheckCollision;
api.func_80102CD8 = NULL;
api.func_80102CD8 = func_80102CD8;
api.UpdateAnim = UpdateAnim;
api.SetSpeedX = NULL;
api.GetFreeEntity = NULL;
api.GetEquipProperties = NULL;
api.SetSpeedX = SetSpeedX;
api.GetFreeEntity = GetFreeEntity;
api.GetEquipProperties = GetEquipProperties;
api.func_800EA5E4 = func_800EA5E4;
api.LoadGfxAsync = LoadGfxAsync;
api.PlaySfx = PlaySfx;
@ -122,18 +133,18 @@ bool InitGame(void) {
api.func_800EA538 = func_800EA538;
api.g_pfn_800EA5AC = func_800EA5AC;
api.func_801027C4 = func_801027C4;
api.func_800EB758 = NULL;
api.func_800EB758 = func_800EB758;
api.CreateEntFactoryFromEntity = CreateEntFactoryFromEntity;
api.func_80131F68 = func_80131F68;
api.func_800EDB08 = NULL;
api.func_800EDB08 = func_800EDB08;
api.func_80106A28 = func_80106A28;
api.func_80118894 = NULL;
api.func_80118894 = func_80118894;
api.enemyDefs = g_EnemyDefs;
api.func_80118970 = NULL;
api.func_80118B18 = NULL;
api.UpdateUnarmedAnim = NULL;
api.func_80118970 = func_80118970;
api.func_80118B18 = func_80118B18;
api.UpdateUnarmedAnim = UpdateUnarmedAnim;
api.PlayAnimation = PlayAnimation;
api.func_80118C28 = NULL;
api.func_80118C28 = func_80118C28;
api.func_8010E168 = func_8010E168;
api.func_8010DFF0 = func_8010DFF0;
api.DealDamage = NULL;
@ -152,7 +163,7 @@ bool InitGame(void) {
api.SetVolumeCommand22_23 = NULL;
api.func_800F53A4 = NULL;
api.CheckEquipmentItemCount = CheckEquipmentItemCount;
api.func_8010BF64 = NULL;
api.func_8010BF64 = func_8010BF64;
api.func_800F1FC4 = NULL;
api.func_800F2288 = func_800F2288;
api.func_8011A3AC = func_8011A3AC;

32
src/pc/stages/stage_cen.c Normal file
View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#include <game.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "stage_loader.h"
extern Overlay CEN_Overlay;
u16 D_8018658C[0x80];
u16 D_8018678C[0x80];
u16 D_8018698C[0x10];
u16 D_801869AC[0x20];
u8 D_80183A40[4400];
u8 D_80184B70[3264];
u8 D_80181658[836];
u8 D_8018199C[8356];
u8 D_80185830[3420];
void InitStageCEN(Overlay* o) {
LoadReset();
LOAD_ASSET("assets/st/cen/D_8018658C.bin", D_8018658C);
LOAD_ASSET("assets/st/cen/D_8018678C.bin", D_8018678C);
LOAD_ASSET("assets/st/cen/D_8018698C.bin", D_8018698C);
LOAD_ASSET("assets/st/cen/D_801869AC.bin", D_801869AC);
LOAD_ASSET("assets/st/cen/D_80183A40.bin", D_80183A40);
LOAD_ASSET("assets/st/cen/D_80184B70.bin", D_80184B70);
LOAD_ASSET("assets/st/cen/D_80181658.bin", D_80181658);
LOAD_ASSET("assets/st/cen/D_8018199C.bin", D_8018199C);
LOAD_ASSET("assets/st/cen/D_80185830.bin", D_80185830);
memcpy(o, &CEN_Overlay, sizeof(Overlay));
}

View File

@ -5,6 +5,8 @@
#include "../pc.h"
#include <stage.h>
#define LOAD_ASSET(path, dst) FileReadToBuf(path, dst, 0, sizeof(dst))
// load a rooms.layers.json file and all its dependencies to the returned
// pre-allocated RoomDef array. Returns NULL in case of a failure.
RoomDef* LoadRoomsLayers(const char* filePath);

1
src/st/.gitignore vendored
View File

@ -6,3 +6,4 @@ layers.h
sprite_banks.h
tilemap_*.h
tiledef_*.h
cutscene_*.h

View File

@ -56,6 +56,7 @@ extern u16 g_InitializeData0[];
extern u16 D_80180428[];
extern u16 g_EInitGeneric[];
extern u16 g_eInitGeneric2[];
extern u16 g_InitializeEntityData0[];
extern u16 D_8018047C[]; // EntityElevator
extern ObjInit g_eBackgroundBlockInit[];
@ -72,10 +73,6 @@ extern u16 g_ESoulStealOrbAngles[];
extern s16 g_ESoulStealOrbSprt[];
extern u8 g_ESoulStealOrbAnim[];
extern ObjInit D_8018125C[];
extern u16 g_InitializeEntityData0[];
// For EntityHolyGlassesCutscene
extern const char D_801813F0[];
extern u8 g_CutsceneScript[];
#endif

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#include <cutscene.h>
u8 g_CutsceneScript[] = {
#include "cutscene_data.h"
};

View File

@ -2,9 +2,9 @@
#include "cen.h"
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern MyRoomDef rooms_layers[];
static signed short* spriteBanks[];
static void* Cluts[];
static MyRoomDef rooms_layers[];
static GfxBank* gfxBanks[];
void UpdateStageEntities();
@ -43,51 +43,36 @@ static void* Cluts[] = {
static u32 D_8019C704[24];
static GfxBank D_80180134 = {
.kind = GFX_BANK_NONE,
.entries =
{
static u_long* D_80180134[] = {
GFX_BANK_NONE,
GFX_ENTRY(0, 0, 0, 0, NULL),
},
GFX_TERMINATE(),
};
static u_long D_80180134_TERM = GFX_TERMINATE();
extern u_long* D_80183A40;
extern u_long* D_80184B70;
static GfxBank D_80180148 = {
.kind = GFX_BANK_COMPRESSED,
.entries =
{
GFX_ENTRY(0x100, 0x80, 0x80, 0x80, &D_80183A40),
GFX_ENTRY(0x100, 0xA0, 0x80, 0x80, &D_80184B70),
},
extern u8 D_80183A40[];
extern u8 D_80184B70[];
static u_long* D_80180148[] = {
GFX_BANK_COMPRESSED,
GFX_ENTRY(0x100, 0x80, 0x80, 0x80, D_80183A40),
GFX_ENTRY(0x100, 0xA0, 0x80, 0x80, D_80184B70),
GFX_TERMINATE(),
};
static u_long D_80180148_TERM = GFX_TERMINATE();
extern u_long* D_80181658;
extern u_long* D_8018199C;
static GfxBank D_80180168 = {
.kind = GFX_BANK_COMPRESSED,
.entries =
{
GFX_ENTRY(0x100, 0x0040, 0x0080, 0x0080, &D_80181658),
GFX_ENTRY(0x100, 0x60, 0x80, 0x80, &D_8018199C),
},
extern u8 D_80181658[];
extern u8 D_8018199C[];
static u_long* D_80180168[] = {
GFX_BANK_COMPRESSED,
GFX_ENTRY(0x100, 0x40, 0x80, 0x80, D_80181658),
GFX_ENTRY(0x100, 0x60, 0x80, 0x80, D_8018199C),
GFX_TERMINATE(),
};
static u_long D_80180168_TERM = GFX_TERMINATE();
extern u_long* D_80185830;
static GfxBank D_80180188 = {
.kind = GFX_BANK_COMPRESSED,
.entries =
{
GFX_ENTRY(0x100, 0x80, 0x80, 0x80, &D_80185830),
},
extern u8 D_80185830[];
static u_long* D_80180188[] = {
GFX_BANK_COMPRESSED,
GFX_ENTRY(0x100, 0x80, 0x80, 0x80, D_80185830),
GFX_TERMINATE(),
};
static u_long D_80180188_TERM = GFX_TERMINATE();
static GfxBank* gfxBanks[] = {
&D_80180134, &D_80180148, &D_80180188, &D_80180134, &D_80180134,

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#include "cen.h"
#include "game.h"
#include <cutscene.h>
static u8 __unused[0xC00];
static s32 D_8019D374;
@ -50,7 +50,7 @@ static const char _pad[4] = "";
#include "../cutscene_unk1.h"
#include "../cutscene_unk2.h"
#include "../set_cutscene_script.h"
#include "../cutscene_unk3.h"
@ -124,7 +124,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
s16 vCoord;
u16 nextChar;
s32 nextChar2;
s32 bit_shifty;
u32 bit_shifty;
if (self->step != 0) {
if ((D_8019D428 != 0) && (D_8019D374 == 0) &&
@ -154,7 +154,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
DestroyEntity(self);
return;
}
if (CutsceneUnk2(D_801813F0)) {
if (SetCutsceneScript(g_CutsceneScript)) {
self->flags |= FLAG_HAS_PRIMS | FLAG_UNK_2000;
D_8003C704 = 1;
g_CutsceneFlags = 0;
@ -173,10 +173,10 @@ void EntityHolyGlassesCutscene(Entity* self) {
}
nextChar = *g_Dialogue.nextCharDialogue++;
switch (nextChar) {
case 0:
case CSOP_END_CUTSCENE:
self->step = 7;
return;
case 1:
case CSOP_LINE_BREAK:
if (D_8019D374 != 0) {
continue;
}
@ -203,10 +203,10 @@ void EntityHolyGlassesCutscene(Entity* self) {
self->step_s = 0;
self->step++;
return;
case 2:
case CSOP_SET_SPEED:
g_Dialogue.unk17 = *g_Dialogue.nextCharDialogue++;
continue;
case 3:
case CSOP_SET_WAIT:
g_Dialogue.nextCharTimer = *g_Dialogue.nextCharDialogue++;
if (D_8019D374 != 0) {
continue;
@ -222,7 +222,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
prim = prim->next;
}
return;
case 5:
case CSOP_SET_PORTRAIT:
if (D_8019D374 != 0) {
g_Dialogue.nextCharDialogue += 2;
continue;
@ -257,7 +257,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
g_Dialogue.portraitAnimTimer = 6;
self->step = 3;
return;
case 6:
case CSOP_NEXT_DIALOG:
if (D_8019D374 != 0) {
continue;
}
@ -270,7 +270,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
g_Dialogue.portraitAnimTimer = 6;
self->step = 4;
return;
case 7:
case CSOP_SET_POS:
if (D_8019D374 != 0) {
g_Dialogue.nextCharDialogue++;
g_Dialogue.nextCharDialogue++;
@ -289,15 +289,14 @@ void EntityHolyGlassesCutscene(Entity* self) {
self->step = 5;
self->step_s = 0;
return;
case 8:
case CSOP_CLOSE_DIALOG:
if (D_8019D374 != 0) {
continue;
}
g_Dialogue.portraitAnimTimer = 0x18;
self->step = 6;
return;
case 9:
case CSOP_PLAY_SOUND:
if (D_8019D374 != 0) {
g_Dialogue.nextCharDialogue++;
g_Dialogue.nextCharDialogue++;
@ -308,7 +307,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
nextChar |= *g_Dialogue.nextCharDialogue++;
g_api.PlaySfx(nextChar);
continue;
case 10:
case CSOP_WAIT_FOR_SOUND:
if (D_8019D374 != 0) {
continue;
}
@ -317,7 +316,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
}
*g_Dialogue.nextCharDialogue--;
return;
case 11:
case CSOP_UNK_11:
if (D_8019D374 != 0) {
continue;
}
@ -358,7 +357,6 @@ void EntityHolyGlassesCutscene(Entity* self) {
bit_shifty |= (s32)*g_Dialogue.nextCharDialogue;
g_Dialogue.nextCharDialogue = (u8*)bit_shifty + 0x100000;
continue;
case 15:
bit_shifty = (s32)*g_Dialogue.nextCharDialogue++;
bit_shifty <<= 4;
@ -369,8 +367,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
bit_shifty |= (s32)*g_Dialogue.nextCharDialogue;
g_Dialogue.nextCharDialogue = (u8*)bit_shifty + 0x100000;
continue;
case 16:
case CSOP_WAIT_FOR_FLAG:
if (!((g_CutsceneFlags >> *g_Dialogue.nextCharDialogue) & 1)) {
g_Dialogue.nextCharDialogue--;
return;
@ -384,7 +381,7 @@ void EntityHolyGlassesCutscene(Entity* self) {
case 18:
g_Dialogue.unk3C = 0;
continue;
case 19:
case CSOP_LOAD_PORTRAIT:
if (D_8019D374 != 0) {
g_Dialogue.nextCharDialogue += 5;
} else {
@ -397,11 +394,11 @@ void EntityHolyGlassesCutscene(Entity* self) {
bit_shifty |= (s32)*g_Dialogue.nextCharDialogue++;
bit_shifty += 0x100000;
nextChar2 = g_Dialogue.nextCharDialogue++[0];
LoadTPage((u32*)bit_shifty, 1, 0, D_801805E8[nextChar2],
LoadTPage((u_long*)bit_shifty, 1, 0, D_801805E8[nextChar2],
0x100, 0x30, 0x48);
}
continue;
case 20:
case CSOP_SCRIPT_UNKNOWN_20:
nextChar = *g_Dialogue.nextCharDialogue++;
nextChar <<= 4;
nextChar |= *g_Dialogue.nextCharDialogue++;

View File

@ -3,7 +3,7 @@
#include "../cutscene_unk1.h"
#include "../cutscene_unk2.h"
#include "../set_cutscene_script.h"
#include "../cutscene_unk3.h"
@ -132,9 +132,9 @@ void EntitySuccubusCutscene(Entity* self) {
}
}
if (self->params) {
bit_shifty = CutsceneUnk2(D_80181B65);
bit_shifty = SetCutsceneScript(D_80181B65);
} else {
bit_shifty = CutsceneUnk2(D_801816C8);
bit_shifty = SetCutsceneScript(D_801816C8);
}
if (bit_shifty) {
self->flags |= FLAG_HAS_PRIMS | FLAG_UNK_2000;

View File

@ -12,7 +12,7 @@ void CutsceneUnk1(void) {
g_Dialogue.nextLineY = g_Dialogue.startY + 0x14;
}
#include "../cutscene_unk2.h"
#include "../set_cutscene_script.h"
void CutsceneUnk3(s16 yOffset) {
RECT rect;
@ -98,7 +98,7 @@ void EntityDeathCutscene(Entity* self) {
return;
}
g_Entities[192].params = 0x100;
if (CutsceneUnk2(D_80184CE0)) {
if (SetCutsceneScript(D_80184CE0)) {
self->flags |= FLAG_HAS_PRIMS | FLAG_UNK_2000;
g_CutsceneFlags = 0;
D_801D7DD4 = 0;

View File

@ -7,7 +7,7 @@
#include "../cutscene_unk1.h"
#include "../cutscene_unk2.h"
#include "../set_cutscene_script.h"
#include "../cutscene_unk3.h"
@ -63,7 +63,7 @@ void EntityMariaCutscene(Entity* self) {
DestroyEntity(self);
return;
}
if (CutsceneUnk2(D_80183B0C)) {
if (SetCutsceneScript(D_80183B0C)) {
self->flags |= FLAG_HAS_PRIMS | FLAG_UNK_2000;
g_CutsceneFlags = 0;
D_801CB73C = 0;

View File

@ -3,7 +3,7 @@
#include "../cutscene_unk1.h"
u8 CutsceneUnk2(const char* textDialogue) {
u8 SetCutsceneScript(const char* textDialogue) {
Primitive* prim;
s16 firstPrimIndex;
@ -160,7 +160,7 @@ void func_801B69F8(Entity* entity) {
switch (entity->step) {
case 0:
if (CutsceneUnk2(D_8018B304)) {
if (SetCutsceneScript(D_8018B304)) {
D_801BC350 = D_801D6B00 = D_801BC3E8 = 0;
D_8003C704 = 1;
entity->flags |= FLAG_HAS_PRIMS | FLAG_UNK_2000;

View File

@ -1,15 +1,21 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
u8 CutsceneUnk2(s32 textDialogue) {
u8 SetCutsceneScript(const char* script) {
Primitive* prim;
s16 firstPrimIndex;
firstPrimIndex = g_api.AllocPrimitives(PRIM_SPRT, 7);
firstPrimIndex = g_api.AllocPrimitives(PRIM_SPRT,
#if defined(VERSION_PC)
8
#else
7
#endif
);
g_Dialogue.primIndex[2] = firstPrimIndex;
if (firstPrimIndex == -1) {
g_Dialogue.primIndex[2] = 0;
return 0;
}
g_Dialogue.nextCharDialogue = textDialogue;
g_Dialogue.nextCharDialogue = script;
g_Dialogue.unk3C = 0;
g_Dialogue.primIndex[1] = -1;
g_Dialogue.primIndex[0] = -1;
@ -36,11 +42,11 @@ u8 CutsceneUnk2(s32 textDialogue) {
prim->drawMode = DRAW_HIDE;
prim = g_Dialogue.prim[5] = prim->next;
prim->type = 4;
prim->type = PRIM_GT4;
prim->drawMode = DRAW_HIDE;
prim = prim->next;
prim->type = 3;
prim->type = PRIM_G4;
prim->r0 = prim->r1 = prim->r2 = prim->r3 = 0xFF;
prim->g0 = prim->g1 = prim->g2 = prim->g3 = 0;
prim->b0 = prim->b1 = prim->b2 = prim->b3 = 0;
@ -50,7 +56,7 @@ u8 CutsceneUnk2(s32 textDialogue) {
prim->drawMode = DRAW_HIDE;
prim = prim->next;
prim->type = 1;
prim->type = PRIM_TILE;
prim->x0 = 3;
prim->y0 = 0x2F;
prim->v0 = 0x4A;

View File

@ -9,7 +9,7 @@
#include "../cutscene_unk1.h"
// not an exact duplicate
u8 CutsceneUnk2(const char* textDialogue) {
u8 SetCutsceneScript(const char* textDialogue) {
Primitive* prim;
s16 firstPrimIndex;
@ -117,7 +117,7 @@ void EntityDraculaCutscene(Entity* self) {
switch (self->step) {
case 0:
if (CutsceneUnk2(D_801829D8)) {
if (SetCutsceneScript(D_801829D8)) {
self->flags |= FLAG_HAS_PRIMS;
g_CutsceneFlags = 0;
D_801C2580 = 0;

View File

@ -1,3 +1,3 @@
module github.com/xeeynamo/sotn-decomp/tools/gfxsotn
go 1.19
go 1.22

View File

@ -35,6 +35,7 @@ type assetEntry struct {
start int
end int
assetDir string
srcDir string
name string
args []string
ramBase psx.Addr
@ -72,6 +73,23 @@ var extractHandlers = map[string]func(assetEntry) error{
}
return os.WriteFile(outPath, content, 0644)
},
"cutscene": func(e assetEntry) error {
if e.start == e.end {
return fmt.Errorf("cutscene cannot be 0 bytes")
}
r := bytes.NewReader(e.data)
script, err := parseCutsceneAsC(r, e.ramBase, e.ramBase.Sum(e.start))
if err != nil {
return err
}
outPath := path.Join(e.srcDir, fmt.Sprintf("%s.h", e.name))
dir := filepath.Dir(outPath)
if err := os.MkdirAll(dir, 0755); err != nil {
fmt.Printf("failed to create directory %s: %v\n", dir, err)
return err
}
return os.WriteFile(outPath, []byte(script), 0644)
},
}
var buildHandlers = map[string]func(assetBuildEntry) error{
@ -105,11 +123,11 @@ func parseArgs(entry []string) (offset int64, kind string, args []string, err er
func readConfig(path string) (*assetConfig, error) {
yamlFile, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("Error reading YAML file: %v", err)
return nil, fmt.Errorf("error reading YAML file: %v", err)
}
var data assetConfig
if err = yaml.Unmarshal(yamlFile, &data); err != nil {
return nil, fmt.Errorf("Error unmarshalling YAML file: %v", err)
return nil, fmt.Errorf("error unmarshalling YAML file: %v", err)
}
return &data, nil
}
@ -118,6 +136,7 @@ func enqueueExtractAssetEntry(
eg *errgroup.Group,
handler func(assetEntry) error,
assetDir string,
srcDir string,
name string,
data []byte,
start int,
@ -130,6 +149,7 @@ func enqueueExtractAssetEntry(
start: start,
end: end,
assetDir: assetDir,
srcDir: srcDir,
ramBase: ramBase,
name: name,
args: args,
@ -174,7 +194,7 @@ func extractAssetFile(file assetFileEntry) error {
}
start := int(off) - segment.Start
end := start + size
enqueueExtractAssetEntry(&eg, handler, file.AssetDir, name, data[segment.Start:], start, end, args, segment.Vram)
enqueueExtractAssetEntry(&eg, handler, file.AssetDir, file.SourceDir, name, data[segment.Start:], start, end, args, segment.Vram)
}
off = off2
kind = kind2

View File

@ -0,0 +1,85 @@
package main
import (
"fmt"
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
"io"
"strings"
)
func readCutscene(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]string, error) {
if err := addr.MoveFile(r, baseAddr); err != nil {
return []string{}, fmt.Errorf("unable to read cutscene: %w", err)
}
read1 := func(r io.ReadSeeker) byte {
b := make([]byte, 1)
_, _ = r.Read(b)
return b[0]
}
script := make([]string, 0)
loop := true
for loop {
op := read1(r)
switch op {
case 0:
script = append(script, "END_CUTSCENE()")
script = append(script, "0xFF")
script = append(script, "0xFF")
loop = false
case 1:
script = append(script, "LINE_BREAK()")
case 2:
script = append(script, fmt.Sprintf("SET_SPEED(%d)", read1(r)))
case 3:
script = append(script, fmt.Sprintf("SET_WAIT(%d)", read1(r)))
case 4:
script = append(script, "HIDE_DIALOG()")
case 5:
script = append(script, fmt.Sprintf("SET_PORTRAIT(%d, %d)", read1(r), read1(r)))
case 6:
script = append(script, "NEXT_DIALOG()")
case 7:
script = append(script, fmt.Sprintf("SET_POS(%d, %d)", read1(r), read1(r)))
case 8:
script = append(script, "CLOSE_DIALOG()")
case 9:
script = append(script, fmt.Sprintf("PLAY_SOUND(0x%02X, 0x%02X)", read1(r), read1(r)))
case 10:
script = append(script, "WAIT_FOR_SOUND()")
case 11:
script = append(script, "SCRIPT_UNKNOWN_11()")
case 13:
script = append(script, "SCRIPT_UNKNOWN_13()")
case 16:
script = append(script, fmt.Sprintf("WAIT_FOR_FLAG(%d)", read1(r)))
case 17:
script = append(script, fmt.Sprintf("SET_FLAG(%d)", read1(r)))
case 18:
script = append(script, "SCRIPT_UNKOWN_18()")
case 19:
script = append(script, fmt.Sprintf(
"LOAD_PORTRAIT(0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X)",
read1(r), read1(r), read1(r), read1(r), read1(r)))
case 20:
script = append(script, fmt.Sprintf("SCRIPT_UNKNOWN_20(0x%02X, 0x%02X)", read1(r), read1(r)))
case 0x27:
script = append(script, "'\\''")
default:
if op >= 0x20 && op <= 0x7e {
script = append(script, fmt.Sprintf("'%s'", string([]byte{op})))
} else {
return script, fmt.Errorf("unknown op 0x%02X", op)
}
}
}
return script, nil
}
func parseCutsceneAsC(r io.ReadSeeker, baseAddr, addr psx.Addr) (string, error) {
script, err := readCutscene(r, baseAddr, addr)
if err != nil {
return "", err
}
return strings.ReplaceAll(strings.Join(script, ",\n"), "',\n'", "','"), nil
}

View File

@ -1,6 +1,6 @@
module github.com/xeeynamo/sotn-decomp/tools/sotn-assets
go 1.21
go 1.22
require (
golang.org/x/sync v0.7.0 // indirect

View File

@ -1,3 +1,3 @@
module github.com/xeeynamo/sotn-decomp/tools/sotn-disk
go 1.19
go 1.22