Add playable Maria on PSX (#2638)

![image](https://github.com/user-attachments/assets/c5f099c0-a889-42c6-9b15-ba6604734660)

The mod replaces Richter with Maria as a playable character.

I created the folder `mods/`, where I put `maria-psx` and planning to
move the debug module in as well. I am not sure if the path is what we
can all agree on, but let's decide before merging this in.

I re-compiled the PSP code by rebasing the address to `0x8013C000` with
the simple `player.ld` in the repo root. I plan on migrating `RIC.BIN`
to use it too and allow relocating the overlay. `stage.ld` and
`servant.ld` will follow. This follows the `weapon0.ld` pattern found in
the repo root. I am happy to move those new linker scripts anywhere you
suggest me to.

PSP Maria loads most of the graphics on the same Video RAM address that
is used on PS1 by the second screen buffer. In `ovl_load.c` you will
find some ifdef to keep the PSP match while relocating the Maria
graphics to the Video RAM section that were used for Richter exclusive
content (e.g. Kid Maria and the four beasts during the prologue). The
graphics need to be strategically located to minimize the code changes
on entities that use UV coordinates. The only exception so far is the `u
-= 0x80;` you will find on the Dragon Crash.

The palette also had to be relocated from `0x114` (PSP Maria) to `0x120`
(PSX RIC). The reason is the 0x114 palette row is replaced at runtime by
the NO1 and RNO1 overlay, resulting on a Maria all black and all her
sub-weapons with the incorrect colours. Luckily the `enum MarPalette`
made it very easy to re-locate everything.

The HUD was the main source of issue, as it is found in DRA and not in
the player overlay. I created a `PatchHudSubweaponUV` to patch DRA on
the fly. Alucard and Richter have separate UV coordinates to show their
respective subweapon graphics in the HUD. Since the mod replaces
Richter, I am just patching the Richter UVs with Maria.

Another issue is where the sub-weapon IDs need to be re-mapped. Maria
uses the subweapon DOLL when subweapon == 0. As PS1 DRA will not show
any icon when the subweapon is zero, I am pulling and pushing the
correct ID right before and right after the player loop. I needed to do
the same task for the other subweapons to reflect the PSP behaviour,
where the Doll replaced Bible, Dragon replaces Vithubi and so on. It is
all documented in the respective functions. Like the PSP, picking up a
subweapon that does not exist for maria (e.g. Cross), will not dismiss
the current subweapon and instead it will increase the heart count by 1.

Maria on PSP allows to hold two sub-weapons. The active subweapon can be
swapped with Triangle. While the swapping logic is present in the Maria
overlay, the HUD cannot be patched. There is a way to make the HUD
working nicely, but we need to agree to what extend this mod can go.

All Maria grunts are absent: we need to find a way to convert PSP sound
effects to PS1, with the appropriate VH/VB container. But I think it is
out of scope for this PR.

The graphics of the subweapon drops from candles will incorrectly show
the Richter subweapons. I can think of two options to fix this:
1. Replace the UV in DRA. For example `D_800D1A28` contains the UVs of
the Axe subweapon. If they get replaced by the correspondent Maria
subweapon, the correct graphics will always be displayed. The downside
is soft-resetting / gameover to play with Alucard will result in the
wrong graphics.
2. We could patch the graphics file by blitting the Maria subweapons
over the Richter ones. This has the advantage of being more
self-contained. But the Bird icon cannot replace the Knife as they're of
different size. EDIT: the palette will be incorrect, so this is not
possible.
This commit is contained in:
Luciano Ciccariello authored and GitHub committed 2025-06-20 19:51:21 +01:00
1 parent 6c17c285c6
commit 52962323a7
23 files changed
+596 -93

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@ on:
workflow_dispatch:
jobs:
format:
push-docker-image:
runs-on: ubuntu-latest
steps:
- name: Clone main repository
@@ -0,0 +1,74 @@
name: Build Maria (PSP) for PSX
on:
push:
branches:
- master
paths:
- 'include/**'
- 'src/maria/**'
- 'mods/mariapsp-forpsx/**'
pull_request:
paths:
- 'include/**'
- 'src/maria/**'
- 'mods/mariapsp-forpsx/**'
workflow_dispatch:
jobs:
build:
if: github.repository == 'Xeeynamo/sotn-decomp'
strategy:
matrix:
version: ["us"]
runs-on: ubuntu-latest
env:
VERSION: ${{ matrix.version }}
steps:
- name: Install requirements
run: sudo apt-get install gcc-mipsel-linux-gnu
- name: Clone repo
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
submodules: false
- uses: actions/setup-go@v5
with:
go-version: '>=1.24.4'
- name: Get dependencies
uses: actions/cache@v4
with:
path: 'disks/dependencies'
key: sotn-pspeu-deps
- name: Setting up dependencies
working-directory: disks
run: cat dependencies/* | tar -zxf -
- name: Extract dependencies
run: |
mkdir -p ~/go/bin
ln -s $(which go) ~/go/bin/
VERSION=pspeu make extract_disk
- name: build
working-directory: mods/mariapsp-forpsx
run: VERSION=${{ matrix.version }} make
push-docker-image:
runs-on: ubuntu-latest
steps:
- name: Clone main repository
uses: actions/checkout@v4
with:
submodules: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: ./mods/mariapsp-forpsx
file: ./mods/mariapsp-forpsx/Dockerfile
push: true
tags: ${{ vars.DOCKERHUB_USERNAME }}/sotn-mariapsp-forpsx:latest
+1
View File
@@ -7,6 +7,7 @@ __pycache__
.venv/
asm/
build/
release/
expected/
disks/
nonmatchings/
+4 -5
View File
@@ -86,8 +86,7 @@ MWCCGAP_APP := $(TOOLS_DIR)/mwccgap/mwccgap.py
DOSEMU_APP := $(or $(shell which dosemu),/usr/bin/dosemu)
SATURN_SPLITTER_DIR := $(TOOLS_DIR)/saturn-splitter
SATURN_SPLITTER_APP := $(SATURN_SPLITTER_DIR)/rust-dis/target/release/rust-dis
SOTNDISK_DIR := $(TOOLS_DIR)/sotn-disk/
SOTNDISK := $(GOPATH)/bin/sotn-disk
SOTNDISK := bin/sotn-disk
SOTNASSETS := bin/sotn-assets
# Directories
@@ -209,7 +208,7 @@ format-src: bin/clang-format format-sotn-lint
@# -print0 : print only the matching entries, delimit by NULL to
@# ensure files with characters xargs uses as delimiters are
@# properly handled
find $(SRC_DIR) $(INCLUDE_DIR) \
find $(SRC_DIR) $(INCLUDE_DIR) mods \
-type d \( -name 3rd -o -name CMakeFiles -o -name gen \) -prune \
-o \( -type f \( -name '*.c' -o -name '*.h' \) \) \
-print0 \
@@ -242,7 +241,7 @@ format-symbols-pspeu: format-symbols-pspeu-sort
format-symbols: format-symbols-us format-symbols-hd format-symbols-pspeu
format-license:
find src/ | grep -E '\.c$$|\.h$$' | grep -vE 'PsyCross|mednafen|psxsdk|3rd|saturn/lib' | $(PYTHON) ./tools/lint-license.py - AGPL-3.0-or-later
find src/ mods/ | grep -E '\.c$$|\.h$$' | grep -vE 'PsyCross|mednafen|psxsdk|3rd|saturn/lib' | $(PYTHON) ./tools/lint-license.py - AGPL-3.0-or-later
$(PYTHON) ./tools/lint-license.py include/game.h AGPL-3.0-or-later
$(PYTHON) ./tools/lint-license.py include/entity.h AGPL-3.0-or-later
$(PYTHON) ./tools/lint-license.py include/items.h AGPL-3.0-or-later
@@ -459,7 +458,7 @@ $(GO):
tar -C $(HOME) -xzf go1.22.4.linux-amd64.tar.gz
rm go1.22.4.linux-amd64.tar.gz
$(SOTNDISK): $(GO) $(SOTNDISK_SOURCES)
cd tools/sotn-disk; $(GO) install
$(GO) build -C tools/sotn-disk -o ../../$@ .
$(SOTNASSETS): $(GO) $(SOTNASSETS_SOURCES)
$(GO) build -C tools/sotn-assets -o ../../$@ .
+1
View File
@@ -72,6 +72,7 @@ CompMatrix = 0x8927678;
DpqColor = 0x0892788C;
NormalColorDpq = 0x089278B0;
NormalClip = 0x089278D4;
RotTrans = 0x892796C;
RotTransPers = 0x89279C4;
RotTransPers4 = 0x8927AF8;
RotAverage3 = 0x8927B88;
-1
View File
@@ -2108,7 +2108,6 @@ u8* GetLangAt(s32 idx, u8* en, u8* fr, u8* sp, u8* ge, u8* it);
// PSP exclusive functions in PS.ELF
void func_892667C(s32 paletteID, u16* palette);
void func_892796C(SVECTOR* in, VECTOR* out, s32* arg3);
s16 func_90E0E30(PrimitiveType, s32);
void func_91040A0(u_long** gfxBank);
+1
View File
@@ -27,6 +27,7 @@ typedef struct {
MATRIX* CompMatrix(MATRIX* m0, MATRIX* m1, MATRIX* m2);
MATRIX* RotMatrix(SVECTOR* r, MATRIX* m);
void SetGeomOffset(long ofx, long ofy);
void RotTrans(SVECTOR* v0, VECTOR* v1, long* flag);
long RotTransPers(SVECTOR*, long*, long*, long*);
void SetGeomScreen(long h);
void SetRotMatrix(MATRIX* m);
+1 -2
View File
@@ -656,7 +656,7 @@ enum Sfx {
SFX_UNK_7BE = 0x7BE,
SFX_UNK_761 = 0x761,
#if defined(VERSION_PSP) || defined(VERSION_PC)
// Exclusive to Maria PSP
/* 0x8E6 */ SFX_VO_MAR_8E6 = 0x8E6,
/* 0x8E7 */ SFX_VO_MAR_ATTACK_C,
/* 0x8E8 */ SFX_VO_MAR_8E8,
@@ -673,7 +673,6 @@ enum Sfx {
/* 0x8F3 */ SFX_VO_MAR_PAIN_C,
/* 0x8F4 */ SFX_VO_MAR_PAIN_D,
/* 0x8F5 */ SFX_VO_MAR_DEATH,
#endif
};
#endif
+22
View File
@@ -0,0 +1,22 @@
# Minimal image to create a PSX disk image with Maria replacing Richter.
#
# 1. build image
# docker build --tag sotn-mariapsp-forpsx:latest .
#
# 2. create the directory disks/ and place the following two files:
# disks/sotn.us.bin
# disks/sotn.pspeu.iso
#
# 3. generate disks/sotn_mariapsp.us.bin
# docker run --rm -v ./disks:/disks sotn-mariapsp-forpsx:latest
FROM xeeynamo/sotn-build:latest
USER ubuntu
WORKDIR /disks
RUN git clone https://github.com/xeeynamo/sotn-decomp.git --branch master --depth 1 /sotn/sotn-decomp && \
cp -r /sotn/.venv /sotn/sotn-decomp/ && \
rm -rf /sotn/sotn-decomp/disks && \
ln -s /disks /sotn/sotn-decomp/
WORKDIR /sotn/sotn-decomp/mods/mariapsp-forpsx
ENTRYPOINT ["make", "disk"]
+68
View File
@@ -0,0 +1,68 @@
ROOT_DIR = ../..
SRC_DIR = $(ROOT_DIR)/src/maria
VERSION ?= us
BUILD_DIR = build/$(VERSION)
TARGET = $(BUILD_DIR)/maria.elf
MAP = $(BUILD_DIR)/maria.map
OVERLAY = $(BUILD_DIR)/MARIA.BIN
CC = mipsel-linux-gnu-gcc
CFLAGS = -mno-abicalls -G0 -fno-pic -g -O2 -I$(ROOT_DIR)/include -I$(ROOT_DIR)/src/maria -msoft-float -march=3000
CFLAGS += -funsigned-char -fpeephole -ffunction-cse -fpcc-struct-return -fcommon -fverbose-asm -fno-stack-protector
LDFLAGS = -nostdlib -Wl,-Map=$(MAP) -T symexport.txt -T $(ROOT_DIR)/player.ld -T $(ROOT_DIR)/config/undefined_syms.$(VERSION).txt -T $(BUILD_DIR)/symbols.txt
HEADER_SRC = maria.c
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(BUILD_DIR)/maria.o $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
ASSETS = $(BUILD_DIR)/.assets_done
.PHONY: all build clean release disk
all: build
build: $(OVERLAY)
$(BUILD_DIR):
mkdir -p $@
$(ASSETS): $(BUILD_DIR)
cd $(ROOT_DIR) && make bin/sotn-assets
cd $(ROOT_DIR) && bin/sotn-assets extract config/assets.pspeu.yaml
cd $(ROOT_DIR) && bin/sotn-assets build config/assets.pspeu.yaml
touch $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) $(ASSETS)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/maria.o: maria.c | $(BUILD_DIR) $(ASSETS)
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJS) | $(BUILD_DIR) $(ASSETS)
sed 's;//.*;;' $(ROOT_DIR)/config/symbols.$(VERSION).txt > $(BUILD_DIR)/symbols.txt
$(CC) $(LDFLAGS) -o $@ $^
$(OVERLAY): $(TARGET)
mipsel-linux-gnu-objcopy -O binary $< $@
clean:
rm $(OVERLAY) $(MAP) $(BUILD_DIR)/symbols.txt $(ASSETS) $(TARGET) $(OBJS)
# prepare release by complying with the AGPL-3.0-or-later license
RELEASE_DIR = release
release:
cd $(ROOT_DIR) && VERSION=pspeu make extract_disk
make $(OVERLAY)
mkdir -p $(RELEASE_DIR)/src
cp $(OVERLAY) $(RELEASE_DIR)/
cp $(MAP) $(RELEASE_DIR)/
cp $(ROOT_DIR)/LICENSE $(RELEASE_DIR)/
cp $$(find . -maxdepth 1 -type f) release/src/
cp $$(find $(ROOT_DIR)/src/maria -maxdepth 1 -type f) release/src/
cp $$(find $(ROOT_DIR)/include -maxdepth 1 -type f) release/src/
cd release && sha256sum MARIA.BIN > MARIA.BIN.sha256
DISK_DIR = $(ROOT_DIR)/disks/mariapsp-forpsx
disk: release
rm -rf $(DISK_DIR) || true
cp -r $(RELEASE_DIR) $(DISK_DIR)
cp $(ROOT_DIR)/disks/sotn.$(VERSION).bin $(DISK_DIR)/sotn-patched.$(VERSION).bin
cd $(ROOT_DIR) && make bin/sotn-disk
$(ROOT_DIR)/bin/sotn-disk inject $(DISK_DIR)/sotn-patched.$(VERSION).bin BIN/RIC.BIN $(OVERLAY)
+213
View File
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#include <maria.h>
// copied from src/dra/5F60C.c
typedef struct {
s32 x;
s32 y;
s32 w;
s32 h;
s32 u;
s32 v;
s32 tpage;
s32 clut;
} SubwpnIconParams;
static SubwpnIconParams D_psp_09147418[] = {
{0x05, 0x09, 0x1F, 0x17, 0xE0, 0xB0, 0x01E, 0x17F}, // CAT
{0x0C, 0x07, 0x0F, 0x17, 0xD0, 0xC0, 0x01E, 0x17F}, // CARDINAL
{0x05, 0x09, 0x1F, 0x17, 0xE0, 0xC8, 0x01E, 0x17F}, // TURTLE
{0x0C, 0x09, 0x10, 0x17, 0x98, 0xD8, 0x01E, 0x17F}, // DOLL (dummy)
{0x0C, 0x09, 0x10, 0x17, 0x98, 0xD8, 0x01E, 0x17F}, // DOLL
{0x0C, 0x09, 0x10, 0x17, 0x98, 0xD8, 0x01E, 0x17F}, // DOLL (dummy)
{0x0C, 0x09, 0x10, 0x17, 0x98, 0xD8, 0x01E, 0x17F}, // DOLL (dummy)
{0x04, 0x00, 0x1F, 0x1F, 0xE0, 0xE0, 0x01E, 0x17F}, // DRAGON
{0x0C, 0x09, 0x10, 0x17, 0x98, 0xD8, 0x01E, 0x17F}, // DOLL (dummy)
};
static s32 _hud_search_pattern[] = {
0x009, 0x00F, 0x018, 0x010, 0x0A8, 0x0C0, 0x01E, 0x17F};
static bool DoesRichterHudMatch(s32* ptr) {
return ptr[0] == _hud_search_pattern[0] &&
ptr[1] == _hud_search_pattern[1] &&
ptr[2] == _hud_search_pattern[2] &&
ptr[3] == _hud_search_pattern[3] &&
ptr[4] == _hud_search_pattern[4] &&
ptr[5] == _hud_search_pattern[5] &&
ptr[6] == _hud_search_pattern[6] && ptr[7] == _hud_search_pattern[7];
}
static SubwpnIconParams* SearchRichterSubwpnIconsDef(s32* ptr) {
if (DoesRichterHudMatch(ptr)) {
return (SubwpnIconParams*)ptr;
}
for (ptr = (s32*)0x800A0000; (u32)ptr < 0x8013C000U; ptr++) {
if (DoesRichterHudMatch(ptr)) {
return (SubwpnIconParams*)ptr;
}
}
return NULL;
}
static void PatchHudSubweaponUV() {
// offset of g_ricSubwpnIcons, from build/us/dra.map
s32* DefaultOffset = (s32*)0x800A3014;
SubwpnIconParams* params = SearchRichterSubwpnIconsDef(DefaultOffset);
if (!params) {
// failed to patch, silently fail and continue instead of crashing
return;
}
// Patch Richter subweapon UVs and replace them with those from Maria
memcpy(params, D_psp_09147418, sizeof(D_psp_09147418));
}
// On MARIA overlay, PL_W_NONE is used for the Doll Crash. We need to convert it
// back and forth to make it work in the overlay and in the HUD managed by DRA.
// * Bible -> Doll
// * Knife -> Cat
// * Vithubi -> Dragon
// * Axe -> Bird
// * Water -> Turtle
static int equipped_subweapon;
static inline void ConvertSubweaponRicToMaria() {
switch (g_Status.subWeapon) {
case SUBWPN_NONE:
g_Status.subWeapon = PL_W_NONE;
break;
case SUBWPN_DAGGER:
g_Status.subWeapon = PL_W_CAT;
break;
case SUBWPN_AXE:
g_Status.subWeapon = PL_W_CARDINAL;
break;
case SUBWPN_HOLYWATER:
g_Status.subWeapon = PL_W_TURTLE;
break;
case SUBWPN_BIBLE:
g_Status.subWeapon = PL_W_NONE;
break;
case SUBWPN_VIBHUTI:
g_Status.subWeapon = PL_W_DRAGON;
break;
default:
// Re-use previous subweapon, and increase heart count by 1
g_Status.subWeapon = equipped_subweapon;
if (g_Status.hearts < g_Status.heartsMax) {
g_Status.hearts++;
}
break;
}
equipped_subweapon = g_Status.subWeapon;
}
static inline void ConvertSubweaponMariaToRic() {
switch (g_Status.subWeapon) {
case PL_W_NONE:
g_Status.subWeapon = SUBWPN_BIBLE;
break;
case PL_W_CAT:
g_Status.subWeapon = SUBWPN_DAGGER;
break;
case PL_W_CARDINAL:
g_Status.subWeapon = SUBWPN_AXE;
break;
case PL_W_TURTLE:
g_Status.subWeapon = SUBWPN_HOLYWATER;
break;
case PL_W_DRAGON:
g_Status.subWeapon = SUBWPN_VIBHUTI;
break;
}
}
int func_8919BA8() { return 0; }
int func_90E4C58() { return 0; }
void func_90E4C18() {}
void func_90E4C68() {}
void func_9101FC8() {}
void func_91040A0(u_long** _gfxBank) {
GfxBank* gfxBank;
GfxLoad* gfxLoad;
s32 i;
gfxBank = (GfxBank*)_gfxBank; // avoid problems with the game.h signature
if (gfxBank->kind == GFX_BANK_NONE) {
return -1;
}
if (gfxBank->kind == -1) {
return -1;
}
for (i = 0; i < LEN(g_GfxLoad); i++) {
gfxLoad = &g_GfxLoad[i];
if (gfxLoad->kind == GFX_BANK_NONE) {
gfxLoad->kind = gfxBank->kind;
gfxLoad->unk6 = 0;
gfxLoad->unk8 = 0;
gfxLoad->next = gfxBank->entries;
return i;
}
gfxLoad++;
}
return -1;
}
void func_892667C(s32 paletteID, u16* paletteData) {
int x = (paletteID & 15) * 16;
int y = (paletteID >> 4) & 15;
RECT rect = {x, 240 + y, 16, 1};
memcpy(
&g_Clut[(paletteID & 0x300) >> 8][(paletteID & 0xFF) * COLORS_PER_PAL],
paletteData, PALETTE_LEN);
LoadImage(&rect, (u_long*)paletteData);
}
void func_9142FC8(int sfx) {}
void func_89285A0(s32 angle, MATRIX* out) {
short c = (short)rcos(angle);
short s = (short)rsin(angle);
out->m[0][0] = c;
out->m[0][1] = (s16)-s;
out->m[0][2] = 0;
out->m[1][0] = s;
out->m[1][1] = c;
out->m[1][2] = 0;
out->m[2][0] = 0;
out->m[2][1] = 0;
out->m[2][2] = 4096;
out->t[2] = 0;
out->t[1] = 0;
out->t[0] = 0;
}
s16 func_90E0E30(PrimitiveType kind, s32 count) {
return g_api.AllocPrimitives(kind, count);
}
void MARIA_Load();
void MarInit(s16 initParam);
static void MarPsxInit(u16 initParam) {
static int isInitialized = 0;
if (!isInitialized) {
MARIA_Load();
PatchHudSubweaponUV();
g_api.CalcDealDamageMaria = g_api.func_800FD664;
g_api.CalcPlayerDamageMaria = g_api.CalcPlayerDamage;
isInitialized = true;
}
// Call the actual init function
MarInit(initParam);
}
void MarMain(void);
static void MarPsxMain() {
ConvertSubweaponRicToMaria();
MarMain();
ConvertSubweaponMariaToRic();
}
#include "gen/maria.h"
void MarUpdatePlayerEntities(void);
void MarGetPlayerSensor(Collider* col);
// needs to stay at the bottom for the struct to fall to 0x8013C000
PlayerOvl MARIA_override = {
MarPsxMain, MarPsxInit, MarUpdatePlayerEntities, MarGetPlayerSensor,
maria_sprites, maria_sprites, maria_sprites, maria_sprites,
};
+1
View File
@@ -0,0 +1 @@
EXTERN(MARIA_override);
+13
View File
@@ -0,0 +1,13 @@
SECTIONS {
. = 0x8013C000;
.data : {
*(.data*)
}
.rodata : {
*(.rodata*)
}
.text : {
*(.text*)
}
end = .;
}
+1 -1
View File
@@ -723,7 +723,7 @@ void DrawRichterHud(void) {
}
RicSubwpnIconParams g_ricSubwpnIcons[] = {
{0x9, 0x00F, 0x018, 0x010, 0x0A8, 0x0C0, 0x01E, 0x17F},
{0x009, 0x00F, 0x018, 0x010, 0x0A8, 0x0C0, 0x01E, 0x17F},
{0x009, 0x007, 0x018, 0x018, 0x080, 0x0C0, 0x01E, 0x17F},
{0x00C, 0x00D, 0x010, 0x010, 0x028, 0x070, 0x01B, 0x102},
{0x008, 0x00C, 0x018, 0x018, 0x038, 0x068, 0x01B, 0x102},
+32
View File
@@ -62,7 +62,9 @@ static u8 wpn_dragon_gfx[] = {
#include "gen/wpn_dragon_gfx.h"
};
#if defined(VERSION_PSP) || defined(VERSION_PC)
#include "gen/maria.h"
#endif
static u8 items_gfx[] = {
#include "gen/items_gfx.h"
@@ -101,37 +103,65 @@ static u16 crash_cardinal_pal[] = {
// clang-format
static u_long* wpn_owl_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x180, 128, 128, wpn_owl_gfx),
#else
GFX_ENTRY(0x180, 0x240, 128, 128, wpn_owl_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* wpn_turtle_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x100, 128, 128, wpn_turtle_gfx),
#else
GFX_ENTRY(0x100, 0x2E0, 128, 128, wpn_turtle_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* wpn_cat_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x140, 128, 128, wpn_cat_gfx),
#else
GFX_ENTRY(0x180, 0x2C0, 128, 128, wpn_cat_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* wpn_cardinal_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x120, 128, 128, wpn_cardinal_gfx),
#else
GFX_ENTRY(0x100, 0x260, 128, 128, wpn_cardinal_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* wpn_dragon_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x160, 128, 128, wpn_dragon_gfx),
#else
GFX_ENTRY(0x100, 0x240, 128, 128, wpn_dragon_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* wpn_doll_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x1A0, 128, 128, wpn_doll_gfx),
#else
GFX_ENTRY(0x180, 0x260, 128, 128, wpn_doll_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* crash_img[] = {
(u_long*)GFX_BANK_COMPRESSED,
#ifdef VERSION_PSP
GFX_ENTRY(0x000, 0x1C0, 128, 128, crash_turtle_gfx),
#else
GFX_ENTRY(0x100, 0x300, 128, 128, crash_turtle_gfx),
#endif
GFX_TERMINATE(),
};
static u_long* hud_img[] = {
@@ -199,8 +229,10 @@ void MarLoadGraphics(void) {
extern PlayerOvl MARIA_player;
void MARIA_Load(void) {
#if defined(VERSION_PSP) || defined(VERSION_PC)
memcpy(&g_PlOvl, &MARIA_player, sizeof(PlayerOvl));
memcpy(&g_PlOvlSpritesheet, maria_sprites, sizeof(maria_sprites));
#endif
func_91040A0(wpn_owl_img);
func_91040A0(wpn_turtle_img);
func_91040A0(wpn_cat_img);
+16 -34
View File
@@ -44,11 +44,6 @@ static Entity* MarGetFreeEntityReverse(s16 start, s16 end) {
}
return NULL;
}
#if defined(VERSION_PSP)
extern s32 D_80174F80[11];
#else
static s32 D_80174F80[11];
#endif
void func_maria_8015F9F0(Entity* entity) {
s32 i;
s32 enemyId;
@@ -69,21 +64,6 @@ void func_maria_8015F9F0(Entity* entity) {
}
}
#if defined(VERSION_PSP)
extern u8 D_80174FAC;
extern u8 D_80174FB0;
extern u8 D_80174FB4;
extern u8 D_80174FB8;
#else
static u8 D_80174FAC;
STATIC_PAD_BSS(3);
static u8 D_80174FB0;
STATIC_PAD_BSS(3);
static u8 D_80174FB4;
STATIC_PAD_BSS(3);
static u8 D_80174FB8;
STATIC_PAD_BSS(3);
#endif
void func_maria_8015FA5C(s32 arg0) {
D_80174FAC = D_80154674[arg0][0];
D_80174FB0 = D_80154674[arg0][1];
@@ -184,8 +164,7 @@ static s32 func_8015FDB0(Primitive* prim, s16 posX, s16 posY) {
return ret;
}
void MarEntityDummy(Entity*) {}
void MarEntityDummy(Entity* self) {}
void MarEntityFactory(Entity* self);
void MarEntitySmokePuff(Entity* self);
void EntityMariaDragonAttack(Entity* self);
@@ -1328,7 +1307,11 @@ void EntityMariaOwl(Entity* self) {
case 0:
self->flags = FLAG_UNK_100000 | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_POS_CAMERA_LOCKED;
#ifdef VERSION_PSP
self->unk5A = 0x18;
#else
self->unk5A = 0x66;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_OWL;
self->animSet = ANIMSET_WPN_OWL;
@@ -1391,7 +1374,7 @@ void EntityMariaOwl(Entity* self) {
VECTOR sp5C = {0, 0, 0};
VECTOR sp4C;
SVECTOR sp44;
s32 sp40;
long sp40;
s32 x;
s32 y;
s32 var_s0;
@@ -1431,7 +1414,7 @@ void EntityMariaOwl(Entity* self) {
sp44.vx = self->ext.mariaOwl.velocityX;
sp44.vy = 0;
sp44.vz = 0;
func_892796C(&sp44, &sp4C, &sp40);
RotTrans(&sp44, &sp4C, &sp40);
self->velocityX = sp4C.vx << 8;
self->velocityY = sp4C.vy << 8;
self->posX.val += self->velocityX;
@@ -1467,11 +1450,6 @@ bool func_maria_80162E9C(Entity* entity) {
// EntityPlayerBlinkWhite (Richter's version).
// Same general logic flow as in DRA but lots of missing cases.
#if defined(VERSION_PSP)
extern s32 D_80154F7C[];
#else
static s32 D_80154F7C[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#endif
static s16 D_80154FBC[][10] = {
{0, 0, 0, 0x15F, 0x0100, 0x0100, 0x0100, 0x0060, 0x31, 0x200},
{0, 2, 6, 0x15F, 0x0100, 0x0100, 0x0100, 0x0040, 0x31, 0x200},
@@ -2449,11 +2427,6 @@ static void func_80166044() {
// Entity ID 66. Made by blueprint 77 (the very last one).
// Created in 3 spots in 2 functions (total of 6 calls).
// DRA version is very similar.
#if defined(VERSION_PSP)
extern Point16 D_80175000[32];
#else
static Point16 D_80175000[32];
#endif
void MarEntityTeleport(Entity* self) {
Primitive* prim;
s32 w;
@@ -2999,7 +2972,12 @@ void EntityMariaCrashSummon(Entity* self) {
return;
}
self->flags = FLAG_HAS_PRIMS | FLAG_KEEP_ALIVE_OFFCAMERA;
#ifdef VERSION_PSP
self->unk5A = 0x1C;
#else
self->unk5A = 0x70;
#endif
self->zPriority = 0x1C0;
LoadCrashSummonResouces(self->ext.mariaCrashSummon.crashId);
MarSetInvincibilityFrames(0, 999);
@@ -3010,7 +2988,11 @@ void EntityMariaCrashSummon(Entity* self) {
self->ext.mariaCrashSummon.timer -= 0x10;
if (self->ext.mariaCrashSummon.timer > 0) {
prim = &g_PrimBuf[self->primIndex];
#ifdef VERSION_PSP
prim->tpage = 7;
#else
prim->tpage = 0x1C;
#endif
prim->clut = 0x11E;
prim->priority = 0x1C0;
x = (self->ext.mariaCrashSummon.timer * 128) / 256;
-3
View File
@@ -679,10 +679,7 @@ void MarMain(void) {
break;
}
g_Player.unk08 = g_Player.status;
#if defined(VERSION_PC) || defined(VERSION_PSP)
// uninitialized on PSX, it was a coincidence it worked
newStatus = 0;
#endif
switch (PLAYER.step) {
case PL_S_STAND:
newStatus = NO_AFTERIMAGE;
-2
View File
@@ -392,11 +392,9 @@ static void func_80159C04(void) {
s16 var_s1;
entity = PLAYER.unkB8;
#if defined(VERSION_PSP)
if (!entity) {
return;
}
#endif
if (entity->facingLeft) {
var_s3 = -entity->hitboxOffX;
} else {
+98 -13
View File
@@ -1,6 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#include "maria.h"
// ** How to calculate self->unk5A
// GFX_ENTRY(Y, X, ...
// where Y and X are variables for the python script below
// def calc_tpage(y, x):
// return (y // 0x100) * 0x40 + ((y & 0x80) // 0x40) + \
// ((x & 0x20) // 0x20) + ((x // 0x20) * 2)
static s32 D_pspeu_092E5F18;
static s32 D_pspeu_092E5D18[0x80];
static Point16 D_pspeu_092E5B18[0x80];
@@ -20,7 +27,11 @@ void EntityMariaDollAttack(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x19;
#else
self->unk5A = 0x67;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_DOLL;
self->animSet = ANIMSET_WPN_DOLL;
@@ -110,7 +121,11 @@ void EntityMariaDollActivate(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x19;
#else
self->unk5A = 0x67;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_DOLL;
self->animSet = ANIMSET_WPN_DOLL;
@@ -263,7 +278,11 @@ void EntityMariaCatAttack(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x14;
#else
self->unk5A = 0x6E;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_CAT;
self->animSet = ANIMSET_WPN_CAT;
@@ -396,7 +415,11 @@ void EntityMariaCatCrash(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x14;
#else
self->unk5A = 0x6E;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_CAT_CRASH;
self->animSet = ANIMSET_WPN_CAT;
@@ -475,7 +498,11 @@ void EntityMariaCatCrashAttack(Entity* self) {
case 0:
self->flags = FLAG_UNK_100000 | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_POS_CAMERA_LOCKED;
#ifdef VERSION_PSP
self->unk5A = 0x14;
#else
self->unk5A = 0x6E;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_CAT;
self->animSet = ANIMSET_WPN_CAT;
@@ -518,7 +545,11 @@ void EntityMariaDragonAttack(Entity* self) {
switch (self->step) {
case 0:
self->flags = FLAG_UNK_100000 | FLAG_KEEP_ALIVE_OFFCAMERA;
#ifdef VERSION_PSP
self->unk5A = 0x15;
#else
self->unk5A = 0x64;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_DRAGON;
self->animSet = ANIMSET_WPN_DRAGON;
@@ -672,7 +703,11 @@ void EntityMariaDragonCrash(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x15;
#else
self->unk5A = 0x64;
#endif
self->zPriority = 0x1C0;
self->palette = PAL_WPN_DRAGON_CRASH;
self->animSet = ANIMSET_WPN_DRAGON;
@@ -718,7 +753,7 @@ void EntityMariaDragonCrash(Entity* self) {
VECTOR vTranslate = {0, 0, 0};
VECTOR vVelocity;
SVECTOR svVelocity;
s32 out;
long out;
if (!IsTargetValid(self->ext.mariaDragon.target)) {
self->ext.mariaDragon.target = FindTarget(self);
}
@@ -751,7 +786,7 @@ void EntityMariaDragonCrash(Entity* self) {
svVelocity.vx = self->ext.mariaDragon.velocity;
svVelocity.vy = 0;
svVelocity.vz = 0;
func_892796C(&svVelocity, &vVelocity, &out);
RotTrans(&svVelocity, &vVelocity, &out);
self->velocityX = vVelocity.vx << 8;
self->velocityY = vVelocity.vy << 8;
FntPrint("%08x : %08x\n", self->velocityX, self->velocityY);
@@ -822,7 +857,11 @@ void EntityMariaDragonCrashBodyPart(Entity* self) {
return;
}
self->flags = FLAG_KEEP_ALIVE_OFFCAMERA | FLAG_HAS_PRIMS;
#ifdef VERSION_PSP
self->unk5A = 0x15;
#else
self->unk5A = 0x64;
#endif
self->zPriority = 0x1C0;
self->palette = PAL_WPN_DRAGON_CRASH;
self->animSet = ANIMSET_WPN_DRAGON;
@@ -851,7 +890,11 @@ void EntityMariaDragonCrashBodyPart(Entity* self) {
prim = &g_PrimBuf[self->primIndex];
var_s2 = D_pspeu_092E5900 - 1;
for (i = 0; i < LEN(D_pspeu_092E5B08); i++) {
#ifdef VERSION_PSP
prim->tpage = 5;
#else
prim->tpage = 0x19;
#endif
prim->clut = self->palette & 0x3FF;
prim->priority = self->zPriority;
if (var_s2 >= 7) {
@@ -876,12 +919,18 @@ void EntityMariaDragonCrashBodyPart(Entity* self) {
SVECTOR sp50;
SVECTOR svPosStart;
SVECTOR sp40;
s32 out;
long out;
s32 angle;
angle =
NormalizeAngle(ratan2(spD0 - var_s5, spD4 - var_s6));
func_89285A0(angle, &m);
SetGeomOffset(var_s6, var_s5);
#ifndef VERSION_PSP
// I do not know why this works. The stack might be messed
// up and function just so happen to match PSP decomp.
spA0.vx = var_s6;
spA0.vy = var_s5;
#endif
TransMatrix(&m, &spA0);
SetRotMatrix(&m);
SetTransMatrix(&m);
@@ -898,10 +947,10 @@ void EntityMariaDragonCrashBodyPart(Entity* self) {
svPosEnd.vx = 16;
svPosEnd.vy = 16;
svPosEnd.vz = 0;
func_892796C(&sp40, &sp60, &out);
func_892796C(&svPosStart, &vPosStart, &out);
func_892796C(&sp50, &sp80, &out);
func_892796C(&svPosEnd, &vPosEnd, &out);
RotTrans(&sp40, &sp60, &out);
RotTrans(&svPosStart, &vPosStart, &out);
RotTrans(&sp50, &sp80, &out);
RotTrans(&svPosEnd, &vPosEnd, &out);
prim->x0 = sp60.vx;
prim->y0 = sp60.vy;
prim->x1 = vPosStart.vx;
@@ -917,8 +966,8 @@ void EntityMariaDragonCrashBodyPart(Entity* self) {
svPosEnd.vx = 16;
svPosEnd.vy = 16;
svPosEnd.vz = 0;
func_892796C(&svPosStart, &vPosStart, &out);
func_892796C(&svPosEnd, &vPosEnd, &out);
RotTrans(&svPosStart, &vPosStart, &out);
RotTrans(&svPosEnd, &vPosEnd, &out);
prim->x0 = xStart;
prim->y0 = yStart;
prim->x1 = vPosStart.vx;
@@ -944,6 +993,10 @@ void EntityMariaDragonCrashBodyPart(Entity* self) {
u = 0x80;
v = 0;
}
#ifndef VERSION_PSP
// Dragon GFXs are re-located in the VRAM on other platforms
u -= 0x80;
#endif
prim->u1 = u;
prim->v1 = v + 0x1F;
prim->u0 = u + 0x1F;
@@ -997,7 +1050,11 @@ void EntityMariaCardinalAttack(Entity* self) {
case 0:
self->flags = FLAG_UNK_100000 | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_POS_CAMERA_LOCKED;
#ifdef VERSION_PSP
self->unk5A = 0x11;
#else
self->unk5A = 0x65;
#endif
self->drawMode = DRAW_TPAGE | DRAW_TPAGE2;
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_CARDINAL;
@@ -1062,7 +1119,11 @@ void EntityMariaCardinalCrash(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x11;
#else
self->unk5A = 0x65;
#endif
self->zPriority = 0x1C0;
self->palette = PAL_WPN_CARDINAL;
self->animSet = ANIMSET_WPN_CARDINAL;
@@ -1292,7 +1353,11 @@ void EntityMariaCardinalCrashFireball(Entity* self) {
}
self->flags =
FLAG_HAS_PRIMS | FLAG_KEEP_ALIVE_OFFCAMERA | FLAG_POS_CAMERA_LOCKED;
#ifdef VERSION_PSP
self->unk5A = 0x11;
#else
self->unk5A = 0x65;
#endif
self->zPriority = 0x1C0;
self->palette = PAL_WPN_CARDINAL;
self->animSet = ANIMSET_WPN_CARDINAL;
@@ -1334,7 +1399,7 @@ void EntityMariaCardinalCrashFireball(Entity* self) {
VECTOR vTransform = {0, 0, 0};
VECTOR vVelocity;
SVECTOR svVelocity;
s32 sp48;
long sp48;
if (self->ext.mariaCardinalCrash.timer < 35) {
if (IsTargetValid2(self->ext.mariaCardinalCrash.target) == 0) {
self->ext.mariaCardinalCrash.target = FindTarget2(self);
@@ -1382,7 +1447,7 @@ void EntityMariaCardinalCrashFireball(Entity* self) {
svVelocity.vx = self->ext.mariaCardinalCrash.velocityX;
svVelocity.vy = 0;
svVelocity.vz = 0;
func_892796C(&svVelocity, &vVelocity, &sp48);
RotTrans(&svVelocity, &vVelocity, &sp48);
self->velocityX = vVelocity.vx << 8;
self->velocityY = vVelocity.vy << 8;
self->posX.val += self->velocityX;
@@ -1396,7 +1461,7 @@ void EntityMariaCardinalCrashFireball(Entity* self) {
prim = &g_PrimBuf[self->primIndex];
for (i = 0; i < 4; i++) {
color = -0x80;
prim->clut = 0x11A;
prim->clut = PAL_WPN_CARDINAL & 0x3FF;
prim->priority = self->zPriority;
prim->drawMode = DRAW_DEFAULT;
switch (i) {
@@ -1434,7 +1499,11 @@ void EntityMariaCardinalCrashFireball(Entity* self) {
prim->v2 = 0x7F;
prim->u3 = 0xCF;
prim->v3 = 0x7F;
prim->tpage = 4;
#ifdef VERSION_PSP
prim->tpage = 0x4;
#else
prim->tpage = 0x19;
#endif
} else {
prim->u0 = 0x98;
prim->v0 = 0x38;
@@ -1444,7 +1513,11 @@ void EntityMariaCardinalCrashFireball(Entity* self) {
prim->v2 = 0x47;
prim->u3 = 0xA7;
prim->v3 = 0x47;
#ifdef VERSION_PSP
prim->tpage = 0x24;
#else
prim->tpage = 0x39;
#endif
prim->drawMode |= FLAG_DRAW_SCALEX | FLAG_DRAW_ROTATE;
if (i == 1) {
color = -0x80;
@@ -1477,7 +1550,11 @@ void EntityMariaTurtleAttack(Entity* self) {
case 0:
self->flags = FLAG_POS_CAMERA_LOCKED | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_UNK_100000;
#ifdef VERSION_PSP
self->unk5A = 0x10;
#else
self->unk5A = 0x6D;
#endif
self->zPriority = PLAYER.zPriority - 8;
self->palette = PAL_WPN_TURTLE;
self->animSet = ANIMSET_WPN_TURTLE;
@@ -1529,7 +1606,11 @@ void EntityMariaTurtleCrash(Entity* self) {
case 0:
self->flags = FLAG_UNK_100000 | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_POS_CAMERA_LOCKED;
#ifdef VERSION_PSP
self->unk5A = 0x10;
#else
self->unk5A = 0x6D;
#endif
self->zPriority = PLAYER.zPriority - 6;
self->palette = PAL_WPN_TURTLE;
self->animSet = ANIMSET_WPN_TURTLE;
@@ -1654,7 +1735,11 @@ void EntityMariaTurtleCrashVortex(Entity* self) {
case 0:
self->flags = FLAG_UNK_100000 | FLAG_KEEP_ALIVE_OFFCAMERA |
FLAG_POS_CAMERA_LOCKED;
#ifdef VERSION_PSP
self->unk5A = 0x10;
#else
self->unk5A = 0x6D;
#endif
self->zPriority = PLAYER.zPriority - 7;
self->palette = PAL_WPN_TURTLE;
self->animSet = ANIMSET_WPN_TURTLE;
-3
View File
@@ -36,9 +36,6 @@ void func_maria_8015C4AC(void) {
g_Entities[1].pose--;
return;
}
#if !defined(VERSION_PSP)
prim = &g_PrimBuf[g_Entities[1].primIndex];
#endif
for (prim = &g_PrimBuf[g_Entities[1].primIndex], i = 0; prim != NULL; i++,
prim = prim->next) {
if (i == g_Entities[1].entityId) {
+2 -9
View File
@@ -4,10 +4,10 @@
// To use Maria as a playable character, add -DUSE_PLAYER_MARIA=1 in cmake
void MAR_Load();
void MARIA_Load();
void InitPlayerMaria(void) {
g_CurrentEntity = &PLAYER;
MAR_Load();
MARIA_Load();
}
int func_8919BA8() { return 0; }
@@ -60,13 +60,6 @@ void func_89285A0(s32 angle, MATRIX* out) {
out->t[0] = 0;
}
void func_892796C(SVECTOR* in, VECTOR* out, s32* unk) {
out->vx = in->vx;
out->vy = in->vy;
out->vz = in->vz;
*unk = 0;
}
s16 func_90E0E30(PrimitiveType kind, s32 count) {
return g_api.AllocPrimitives(kind, count);
}
+30 -10
View File
@@ -3,7 +3,6 @@ package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
@@ -18,13 +17,13 @@ type cueTrack struct {
mode string
}
func performCueAction(cuePath string, action imageAction) error {
func openImageFromCueFile(cuePath string) (*iso9660.Image, error) {
tracks, err := parseTracks(cuePath)
if err != nil {
return err
return nil, err
}
if len(tracks) == 0 {
return errors.New("CUE do not provide any track")
return nil, errors.New("CUE do not provide any track")
}
mainTrack := getFirstTrack(tracks)
@@ -35,7 +34,7 @@ func performCueAction(cuePath string, action imageAction) error {
case "MODE2/2352":
mode = iso9660.TrackMode2_2352
default:
return fmt.Errorf("unrecognized TRACK mode '%s'", mainTrack.mode)
return nil, fmt.Errorf("unrecognized TRACK mode '%s'", mainTrack.mode)
}
baseDir, _ := path.Split(cuePath)
@@ -43,15 +42,36 @@ func performCueAction(cuePath string, action imageAction) error {
f, err := os.OpenFile(binPath, os.O_RDWR, 0)
if err != nil {
return err
return nil, err
}
defer f.Close()
return iso9660.OpenImage(f, mode)
}
image, err := iso9660.OpenImage(f, mode)
func performCueAction(path string, action imageAction) error {
stat, err := os.Stat(path)
if err != nil {
return err
}
var image *iso9660.Image
if stat.Size() < 65536 {
img, err := openImageFromCueFile(path)
if err != nil {
return err
}
image = img
} else {
// most likely a bin file
f, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return err
}
img, err := iso9660.OpenImage(f, iso9660.TrackMode2_2352)
if err != nil {
return err
}
image = img
}
defer image.Close()
return action(image.RootDir())
}
@@ -68,7 +88,7 @@ func getFirstTrack(tracks []cueTrack) cueTrack {
}
func parseTracks(cuePath string) ([]cueTrack, error) {
content, err := ioutil.ReadFile(cuePath)
content, err := os.ReadFile(cuePath)
if err != nil {
return nil, err
}
+17 -9
View File
@@ -13,6 +13,7 @@ type TrackMode int
type ReaderWriterAt interface {
io.ReaderAt
io.WriterAt
io.Closer
}
const (
@@ -25,9 +26,9 @@ var (
)
type Image struct {
reader ReaderWriterAt
mode TrackMode
Pvd PrimaryVolumeDescriptor
file ReaderWriterAt
mode TrackMode
Pvd PrimaryVolumeDescriptor
}
type File struct {
@@ -36,23 +37,27 @@ type File struct {
mode TrackMode
}
func OpenImage(rw ReaderWriterAt, mode TrackMode) (*Image, error) {
pvdSec, err := readSector(rw, 16, mode, false)
func OpenImage(file ReaderWriterAt, mode TrackMode) (*Image, error) {
pvdSec, err := readSector(file, 16, mode, false)
if err != nil {
return nil, err
}
return &Image{
reader: rw,
mode: mode,
Pvd: parsePVD(pvdSec), // TODO assume first volume is always primary
file: file,
mode: mode,
Pvd: parsePVD(pvdSec), // TODO assume first volume is always primary
}, nil
}
func (img *Image) Close() error {
return img.file.Close()
}
func (img *Image) RootDir() File {
return File{
DirectoryEntry: img.Pvd.DirectoryRecord,
handler: img.reader,
handler: img.file,
mode: img.mode,
}
}
@@ -128,6 +133,9 @@ func (file File) WriteFromData(b []byte) error {
if uint32(len(b)) > file.DataLength.LSB {
return fmt.Errorf("data too long (%d > %d)", len(b), file.DataLength.LSB)
}
newB := make([]byte, file.DataLength.LSB)
copy(newB, b)
b = newB
for loc := location(file.ExtentLocation.LSB); len(b) > 0; loc++ {
chunk := b
if len(b) > sectorSize {