From d8f573c678483d74472a9528913d524424011b89 Mon Sep 17 00:00:00 2001 From: violette Date: Sun, 17 Aug 2025 21:44:27 -0400 Subject: [PATCH 1/2] feature: WIP GBA cube is turning, but has some scalling issue vertically while turning allong z also need to implement quad detection along Y axis --- source/main.c | 516 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 source/main.c diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..96760e8 --- /dev/null +++ b/source/main.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: CC0-1.0 +// +// SPDX-FileContributor: Antonio Niño Díaz, 2022 + +#include + +#define GBA_SCREEN_W 240 +#define GBA_SCREEN_H 160 + +#define REG_DISPCNT *((volatile uint16_t *)0x04000000) + +#define REG_KEYINPUT *((volatile uint16_t *)0x04000130) +#define DEFAULT_REG_KEYINPUT *((volatile uint16_t *)0x04000130) +#define KEY_DOWN_NOW(key) (~(REG_KEYINPUT) & key) + +#define KEY_A 1 +#define KEY_B 2 +#define KEY_SELECT 3 +#define KEY_START 4 +#define KEY_DPAD_RIGHT 5 +#define KEY_DPAD_LEFT 6 +#define KEY_DPAD_UP 7 +#define KEY_DPAD_DOWN 8 +#define KEY_TRIGGER_LEFT 9 +#define KEY_TRIGGER_RIGHT 10 + +#define DISPCNT_BG_MODE_MASK (0x7) +#define DISPCNT_BG_MODE(n) ((n) & DISPCNT_BG_MODE_MASK) // 0 to 5 + +#define DISPCNT_BG2_ENABLE (1 << 10) + +#define MEM_VRAM_MODE3_FB ((uint16_t *)0x06000000) + +#define FIXED_POINT int32_t +#define fp 12 +#define SHIFT_THRESHOLD 0.05 +#define SHIFT_THRESHOLD_FP ((1 << fp) * SHIFT_THRESHOLD) + +#define FLOAT2FIXED(value) (int)((value) * (1 << fp)) +#define FIXED2FLOAT(value) ((value) / (float)(1 << fp)) + +static inline uint16_t +RGB15(uint16_t r, uint16_t g, uint16_t b) +{ + return (r & 0x1F) | ((g & 0x1F) << 5) | ((b & 0x1F) << 10); +} + +/////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#define VWIDTH 50 +#define VHEIGHT 50 +#define CUBE_WIDTH 10 +#define CUBE_WIDTH_FP ((1 << fp) * CUBE_WIDTH) + +enum faces { + FACE_FRONT = 0, + FACE_LEFT, + FACE_RIGHT, + FACE_BOTTOM, + FACE_TOP, + FACE_BACK, + NUM_FACES, +}; + +#define STEP 5 +#define STEP_FP ((1 << fp) * STEP) + +#define ACTION_STEP 0.1 +#define ACTION_STEP_FP ((1 << fp) * ACTION_STEP) + +#define PITCH_STEP 0.05 +#define ROLL_STEP 0.05 +#define YAW_STEP 0.05 + +volatile FIXED_POINT K1 = 60; +volatile FIXED_POINT K2 = (2 * CUBE_WIDTH) + 20; + + +#define MULT_FP(a,b) ((a * b) >> fp) + +#define SQ(n) (n * n) +#define SQ_FP(n) (MULT_FP(n, n)) + +#define COORD2INDEX(x, y) (x * VWIDTH + y) +#define COUPLE2INDEX(x) (COORD2INDEX(x[0], x[1])) + +#define GET_ROTATE_X_Q(a) ({ float _a = (FIXED2FLOAT(a)) ; \ + struct Quaternions q = {}; q.w = FLOAT2FIXED(cos(_a * .5)); \ + q.x = FLOAT2FIXED(sin(_a * .5)); q; }) +#define GET_ROTATE_Y_Q(a) ({ float _a = (FIXED2FLOAT(a)) ; \ + struct Quaternions q = {}; q.w = FLOAT2FIXED(cos(_a * .5)); \ + q.y = FLOAT2FIXED(sin(_a * .5)); q; }) +#define GET_ROTATE_Z_Q(a) ({ float _a = (FIXED2FLOAT(a)) ; \ + struct Quaternions q = {}; q.w = FLOAT2FIXED(cos(_a * .5)); \ + q.z = FLOAT2FIXED(sin(_a * .5)); q; }) + +//TODO Idle animations +#define IS_IDLE (Idle.x || Idle.y || Idle.z) +#define RESET_IDLE {Idle.x = 0; Idle.y = 0; Idle.z = 0;} + +struct { + char x; + char y; + char z; +} Idle; + + +struct Quaternions { + FIXED_POINT w; + FIXED_POINT x; + FIXED_POINT y; + FIXED_POINT z; +} Target, Current, Last; + +FIXED_POINT interpolationStep = 0; +FIXED_POINT zBuffer[VHEIGHT * VWIDTH]; +char output[VHEIGHT * VWIDTH]; + +static volatile char shouldBreak = 1; +static volatile char currentlyMoving = 0; +static volatile char currentCountR = 0; +static volatile char frontFacingFace = FACE_FRONT; + +void +normalize(struct Quaternions *q) +{ + float n = sqrt(FIXED2FLOAT(SQ_FP(q->w) + SQ_FP(q->x) + + SQ_FP(q->y) + SQ_FP(q->z))); + if (n == 0) + return; + q->w = FLOAT2FIXED(FIXED2FLOAT(q->w) / n); + q->x = FLOAT2FIXED(FIXED2FLOAT(q->x) / n); + q->y = FLOAT2FIXED(FIXED2FLOAT(q->y) / n); + q->z = FLOAT2FIXED(FIXED2FLOAT(q->z) / n); +} + +struct Quaternions +mult(struct Quaternions q, FIXED_POINT x, FIXED_POINT y, FIXED_POINT z) +{ + //p = q * p * qbar + struct Quaternions res; + + res.w = 0; + res.x = MULT_FP(x, (SQ_FP(q.w) + SQ_FP(q.x) - SQ_FP(q.y) - SQ_FP(q.z))) + + (MULT_FP(y, (MULT_FP(q.x, q.y) - MULT_FP(q.w, q.z))) * 2) + + (MULT_FP(z, (MULT_FP(q.x, q.z) + MULT_FP(q.w, q.y))) * 2); + + res.y = (MULT_FP(x, (MULT_FP(q.x, q.y) + MULT_FP(q.w,q.z))) * 2) + + (MULT_FP(y, (SQ_FP(q.w) - SQ_FP(q.x) + SQ_FP(q.y) - SQ_FP(q.z)))) + + (MULT_FP(z, (MULT_FP(q.y, q.z) - MULT_FP(q.w, q.x))) << 2); + + res.z = (MULT_FP(x, (MULT_FP(q.x, q.z) - MULT_FP(q.w, q.y)))* 2) + + (MULT_FP(y, (MULT_FP(q.y, q.z) + MULT_FP(q.w, q.x))) * 2) + + MULT_FP(z, (SQ_FP(q.w) - SQ_FP(q.x) - SQ_FP(q.y) + SQ_FP(q.z))); + + return res; +} + +struct Quaternions +multQ(struct Quaternions p, struct Quaternions q) +{ + if (p.x <= SHIFT_THRESHOLD_FP && p.x >= -SHIFT_THRESHOLD_FP + && p.y <= SHIFT_THRESHOLD_FP && p.y >= -SHIFT_THRESHOLD_FP + && p.z <= SHIFT_THRESHOLD_FP && p.z >= -SHIFT_THRESHOLD_FP) + return q; + + if (q.x <= SHIFT_THRESHOLD_FP && q.x >= -SHIFT_THRESHOLD_FP + && q.y <= SHIFT_THRESHOLD_FP && q.y >= -SHIFT_THRESHOLD_FP + && q.z <= SHIFT_THRESHOLD_FP && q.z >= -SHIFT_THRESHOLD_FP) + return p; + + struct Quaternions res = { + .w = MULT_FP(p.w, q.w) - MULT_FP(p.x, q.x) - + MULT_FP(p.y, q.y) - MULT_FP(p.z, q.z), + .x = MULT_FP(p.w, q.x) + MULT_FP(p.x, q.w) + + MULT_FP(p.y, q.z) - MULT_FP(p.z, q.y), + .y = MULT_FP(p.w, q.y) - MULT_FP(p.x, q.z) + + MULT_FP(p.y, q.w) + MULT_FP(p.z, q.x), + .z = MULT_FP(p.w, q.z) + MULT_FP(p.x, q.y) - + MULT_FP(p.y, q.x) + MULT_FP(p.z, q.w), + }; + + return res; +} + +uint16_t +chooseColor(char c) +{ + switch (c) { + case FACE_FRONT: + return RGB15(31, 0, 0); + case FACE_BACK: + return RGB15(31, 15, 31); + case FACE_BOTTOM: + return RGB15(31, 0, 31); + case FACE_LEFT: + return RGB15(0, 0, 31); + case FACE_RIGHT: + return RGB15(0, 31, 31); + case FACE_TOP: + return RGB15(0, 31, 0); + default: + // BG + return RGB15(31, 31, 31); + } +} + +char +chooseMainFace() +{ + int total = 0; + int faces[NUM_FACES] = {0}; + + for (int k = 0; k < VWIDTH * VHEIGHT; ++k) + if (output[k] >= 0 && output[k] < NUM_FACES) { + faces[output[k]]++; + ++total; + } + + int max = 0, idx = 0; + for (int k = 0; k < NUM_FACES; ++k) + if (faces[k] > max) { + max = faces[k]; + idx = k; + } + + frontFacingFace = max > total * 0.9 ? idx : -1; + return frontFacingFace; +} + +void +fill_quads(char current_face, char top_left[2], char top_right[2], + char bot_left[2], char bot_right[2]) +{ + // calc slope foreach side + float slope_top = 0; + float slope_bot = 0; + float slope_left = INFINITY; + float slope_right = -INFINITY; + + if (top_left[0] != top_right[0]) + slope_top = (float)(top_right[1] - top_left[1]) / + (float)(top_right[0] - top_left[0]); + if (bot_left[0] == bot_right[0]) + slope_bot = (float)(bot_right[1] - bot_left[1]) / + (float)(bot_right[0] - bot_left[0]); + if (top_left[0] == bot_left[0]) + slope_left = (float)(bot_left[1] - top_left[1]) / + (float)(bot_left[0] - top_left[0]); + if (top_right[0] == bot_right[0]) + slope_right = (float)(bot_right[1] - top_right[1]) / + (float)(bot_right[0] - top_right[0]); + + int top = top_right[1] > top_left[1] ? top_right[1]: top_left[1]; + int bot = bot_right[1] > bot_left[1] ? bot_right[1]: bot_left[1]; + int left = top_left[0] > bot_left[0] ? top_left[0]: bot_left[0]; + int right = top_right[0] > bot_right[0] ? top_right[0]: bot_right[0]; + for (int y = top ; y <= bot ; ++y) { + for (int x = left ; x <= right ; ++x) { + if (slope_top * x + top <= y) + // TODO side check + output[COORD2INDEX(x, y)] = current_face; + } + } +} + +void +detect_and_fill_quads() +{ + for (int current_face = 0 ; current_face < NUM_FACES ; ++current_face) { + char top_left [2] = {VWIDTH, VHEIGHT}; + char top_right[2] = {0, VHEIGHT}; + char bot_left [2] = {VWIDTH, 0}; + char bot_right[2] = {0, 0}; + for (char y = 0; y < VHEIGHT; ++y) { + for (char x = 0; x < VWIDTH; ++x) { + if (output[COORD2INDEX(x, y)] != current_face) + continue; + if (x <= top_left[0] && y <= top_left[1]){ + top_left[0] = x; + top_left[1] = y; + } + if (x >= top_right[0] && y <= top_right[1]){ + top_right[0] = x; + top_right[1] = y; + } + if (x <= bot_left[0] && y >= bot_left[1]){ + bot_left[0] = x; + bot_left[1] = y; + } + if (x >= bot_right[0] && y >= bot_right[1]){ + bot_right[0] = x; + bot_right[1] = y; + } + } + } + fill_quads(current_face, top_left, top_right, bot_left, bot_right); + } + +} + +void +printAscii() +{ + // TODO scale up + MEM_VRAM_MODE3_FB[120 + 80 * GBA_SCREEN_W] = RGB15(currentCountR, 31 - currentCountR, 0); + MEM_VRAM_MODE3_FB[136 + 80 * GBA_SCREEN_W] = RGB15(currentCountR, 31 - currentCountR, 0); + MEM_VRAM_MODE3_FB[120 + 96 * GBA_SCREEN_W] = RGB15(currentCountR, 31 - currentCountR, 0); + currentCountR = currentCountR == 31 ? 0 : 31; + + //detect_and_fill_quads(); + + for (int i = 0; i < VHEIGHT; ++i) { + for (int j = 0; j < VWIDTH; ++j) { + char prevc = 0; + char *c = output + (i * VWIDTH + j); + MEM_VRAM_MODE3_FB[(i + 50) * GBA_SCREEN_W + j + 50] = chooseColor(*c); + } + } +} + +void +rotateCube(FIXED_POINT cubeX, FIXED_POINT cubeY, FIXED_POINT cubeZ, char ch) +{ + struct Quaternions q = mult(Current, cubeX, cubeY, cubeZ); + + int x = q.x >> fp; + int y = q.y >> fp; + + // not fixed point yet!! + float invZ = (1 << fp) / (float)(q.z + K2 * (1 << fp)); + + int screenX = (int)(VWIDTH * 0.5) + (int)((x) * K1) * invZ; + int screenY = (int)(VHEIGHT * 0.5) + (int)((y) * K1) * invZ; + //TODO luminescence + + if (screenX > VWIDTH || screenX < 0) return; + + int idx = screenY * VWIDTH + screenX; + if (idx >= 0 && idx < VWIDTH * VHEIGHT) { + invZ = FLOAT2FIXED(invZ); + if (zBuffer[idx] < invZ) { + zBuffer[idx] = invZ; + output[idx] = ch; + } + } +} + +struct Quaternions +interpolate(struct Quaternions qa, struct Quaternions qb) +{ + frontFacingFace = -1; + struct Quaternions res; + float cosHalfTheta = + FIXED2FLOAT(MULT_FP(qa.w, qb.w) + + MULT_FP(qa.x, qb.x) + + MULT_FP(qa.y, qb.y) + + MULT_FP(qa.z, qb.z)); + //if qa = qb or qa = -qb then theta = 0 and we can return qa + if (cosHalfTheta >= 1.0 || cosHalfTheta <= -1.0) { + res.w = qa.w; + res.x = qa.x; + res.y = qa.y; + res.z = qa.z; + goto exit; + } + if (cosHalfTheta < 0) { + qb.w = -qb.w; + qb.x = -qb.x; + qb.y = -qb.y; + qb.z = qb.z; + cosHalfTheta = -cosHalfTheta; + } + + float halfTheta = acos(cosHalfTheta); + float sinHalfTheta = sqrt(1.0 - cosHalfTheta * cosHalfTheta); + //if theta = 180 degrees then result is not fully defined + // we could rotate around any axis normal to qa or qb + if (sinHalfTheta < 0.001 && sinHalfTheta > -0.001) { + res.w = ((qa.w >> 1) + (qb.w >> 1)); + res.x = ((qa.x >> 1) + (qb.x >> 1)); + res.y = ((qa.y >> 1) + (qb.y >> 1)); + res.z = ((qa.z >> 1) + (qb.z >> 1)); + goto exit; + } + + + FIXED_POINT ratioA = FLOAT2FIXED(sin((1 - FIXED2FLOAT(interpolationStep)) * halfTheta) / sinHalfTheta); + FIXED_POINT ratioB = FLOAT2FIXED(sin(FIXED2FLOAT(interpolationStep) * halfTheta) / sinHalfTheta); + + res.w = (MULT_FP(qa.w, ratioA) + MULT_FP(qb.w, ratioB)); + res.x = (MULT_FP(qa.x, ratioA) + MULT_FP(qb.x, ratioB)); + res.y = (MULT_FP(qa.y, ratioA) + MULT_FP(qb.y, ratioB)); + res.z = (MULT_FP(qa.z, ratioA) + MULT_FP(qb.z, ratioB)); + +exit: + interpolationStep += ACTION_STEP_FP; + return res; +} + +void +handleAngle(char input) +{ + // TODO + if (currentlyMoving == 0) { + currentlyMoving = input; + Last = Current; + switch (input) { + case 'w': + case 'W': + Target = multQ(GET_ROTATE_X_Q(FLOAT2FIXED(M_PI_2)), Target); + break; + case 'a': + case 'A': + Target = multQ(GET_ROTATE_Y_Q(-FLOAT2FIXED(M_PI_2)), Target); + break; + case 's': + case 'S': + Target = multQ(GET_ROTATE_X_Q(-FLOAT2FIXED(M_PI_2)), Target); + break; + case 'd': + case 'D': + Target = multQ(GET_ROTATE_Y_Q(FLOAT2FIXED(M_PI_2)), Target); + break; + case 'q': + case 'Q': + Target = multQ(GET_ROTATE_Z_Q(-FLOAT2FIXED(M_PI_2)), Target); + break; + case 'e': + case 'E': + Target = multQ(GET_ROTATE_Z_Q(FLOAT2FIXED(M_PI_2)), Target); + break; + default: + currentlyMoving = 0; + //TODO idle movement + } + normalize(&Target); + } else { + if (interpolationStep < (1 << fp) - ACTION_STEP_FP * 2) { + Current = interpolate(Current, Target); + normalize(&Current); + } + else { + Current = Target; + interpolationStep = 0; + currentlyMoving = 0; + } + } +} + +char +getInput() +{ + // TODO + char c = 'd'; + handleAngle(c); + return c; +} + + +int +main() +{ + REG_DISPCNT = DISPCNT_BG_MODE(3) | DISPCNT_BG2_ENABLE; + + Current = GET_ROTATE_Z_Q(0); + + while (1) { + memset(output, NUM_FACES, VWIDTH * VHEIGHT); + memset(zBuffer, 0xffffffff, VWIDTH * VHEIGHT * sizeof(FIXED_POINT)); + + for (FIXED_POINT cubeX = -CUBE_WIDTH_FP + STEP_FP ; + cubeX <= CUBE_WIDTH_FP - STEP_FP; cubeX += STEP_FP) { + for (FIXED_POINT cubeY = -CUBE_WIDTH_FP + STEP_FP; + cubeY <= CUBE_WIDTH_FP - STEP_FP; cubeY += STEP_FP) { + switch (frontFacingFace) { + case FACE_FRONT: + rotateCube(cubeX, cubeY, -CUBE_WIDTH_FP, FACE_FRONT); + break; + case FACE_LEFT: + rotateCube(-CUBE_WIDTH_FP, cubeX, cubeY, FACE_LEFT); + break; + case FACE_RIGHT: + rotateCube(CUBE_WIDTH_FP, cubeX, cubeY, FACE_RIGHT); + break; + case FACE_BOTTOM: + rotateCube(cubeX, -CUBE_WIDTH_FP, cubeY, FACE_BOTTOM); + break; + case FACE_TOP: + rotateCube(cubeX, CUBE_WIDTH_FP, cubeY, FACE_TOP); + break; + case FACE_BACK: + rotateCube(cubeX, cubeY, CUBE_WIDTH_FP, FACE_BACK); + break; + default: // idk render all + rotateCube(cubeX, cubeY, -CUBE_WIDTH_FP, FACE_FRONT); + rotateCube(-CUBE_WIDTH_FP, cubeX, cubeY, FACE_LEFT); + rotateCube(CUBE_WIDTH_FP, cubeX, cubeY, FACE_RIGHT); + rotateCube(cubeX, -CUBE_WIDTH_FP, cubeY, FACE_TOP); + rotateCube(cubeX, CUBE_WIDTH_FP, cubeY, FACE_BOTTOM); + rotateCube(cubeX, cubeY, CUBE_WIDTH_FP, FACE_BACK); + } + } + } + + printAscii(); + getInput(); + } +} From 044d31fa473bea0598a9776a13aa705cf95da27c Mon Sep 17 00:00:00 2001 From: violette Date: Thu, 21 Aug 2025 22:24:02 -0400 Subject: [PATCH 2/2] idk wip --- Makefile | 169 +++++++++++++++++++++++- gbafix/.gitignore | 1 + gbafix/Makefile | 25 ++++ gbafix/gbafix.c | 285 +++++++++++++++++++++++++++++++++++++++++ source/main.c | 141 +++++++++++--------- source/sys/gba_cart.ld | 233 +++++++++++++++++++++++++++++++++ source/sys/gba_crt0.s | 142 ++++++++++++++++++++ source/sys/syscalls.c | 177 +++++++++++++++++++++++++ 8 files changed, 1111 insertions(+), 62 deletions(-) create mode 100644 gbafix/.gitignore create mode 100644 gbafix/Makefile create mode 100644 gbafix/gbafix.c create mode 100644 source/sys/gba_cart.ld create mode 100644 source/sys/gba_crt0.s create mode 100644 source/sys/syscalls.c diff --git a/Makefile b/Makefile index 1540438..f15de24 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,166 @@ -CFLAGS = -lm +# SPDX-License-Identifier: CC0-1.0 +# +# SPDX-FileContributor: Antonio Niño Díaz, 2022 -build: - cc ./main.c $(CFLAGS) -DVBUF -o ./cube +# User config +# =========== -run: build - ./cube +# ROM config +# ---------- + +NAME := vi +GAME_TITLE := "VIOLET'S CV" +GAME_CODE := "00" + +# Defines passed to all files +# --------------------------- + +DEFINES := + +# Libraries +# --------- + +LIBS := -lc +LIBDIRS := # A list of paths + +# Include paths +# ------------- + +INCLUDES := # A list of paths + +# Tools +# ----- + +PREFIX := arm-none-eabi- +CC := $(PREFIX)gcc +CXX := $(PREFIX)g++ +OBJDUMP := $(PREFIX)objdump +OBJCOPY := $(PREFIX)objcopy +MKDIR := mkdir +RM := rm -rf + +# Verbose flag +# ------------ + +# `make V=` builds the binary in verbose build mode +V := @ + +# Directories +# ----------- + +SOURCEDIR := source +BUILDDIR := build + +# Build artfacts +# -------------- + +ELF := $(NAME).elf +DUMP := $(NAME).dump +ROM := $(NAME).gba +MAP := $(NAME).map + +GBAFIX := gbafix/gbafix + +# Source files +# ------------ + +SOURCES_S := $(wildcard $(SOURCEDIR)/*.s $(SOURCEDIR)/**/*.s) +SOURCES_C := $(wildcard $(SOURCEDIR)/*.c $(SOURCEDIR)/**/*.c) +SOURCES_CPP := $(wildcard $(SOURCEDIR)/*.cpp $(SOURCEDIR)/**/*.cpp) + +# Compiler and linker flags +# ------------------------- + +DEFINES += -D__GBA__ + +ARCH := -mcpu=arm7tdmi -mtune=arm7tdmi + +WARNFLAGS := -Wall + +INCLUDEFLAGS := $(foreach path,$(INCLUDES),-I$(path)) \ + $(foreach path,$(LIBDIRS),-I$(path)/include) + +LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib) + +ASFLAGS += -x assembler-with-cpp $(DEFINES) $(ARCH) \ + -mthumb -mthumb-interwork $(INCLUDEFLAGS) \ + -ffunction-sections -fdata-sections -lm + +CFLAGS += -std=gnu11 $(WARNFLAGS) $(DEFINES) $(ARCH) \ + -mthumb -mthumb-interwork $(INCLUDEFLAGS) -O2 \ + -ffunction-sections -fdata-sections -lm -g + +CXXFLAGS += -std=gnu++14 $(WARNFLAGS) $(DEFINES) $(ARCH) \ + -mthumb -mthumb-interwork $(INCLUDEFLAGS) -O2 \ + -ffunction-sections -fdata-sections \ + -fno-exceptions -fno-rtti + +LDFLAGS := -mthumb -mthumb-interwork $(LIBDIRSFLAGS) \ + -Wl,-Map,$(MAP) -Wl,--gc-sections \ + -specs=nano.specs -T source/sys/gba_cart.ld \ + -Wl,--start-group $(LIBS) -Wl,--end-group -lm + +# Intermediate build files +# ------------------------ + +OBJS := \ + $(patsubst $(SOURCEDIR)/%.s,$(BUILDDIR)/%.s.o,$(SOURCES_S)) \ + $(patsubst $(SOURCEDIR)/%.c,$(BUILDDIR)/%.c.o,$(SOURCES_C)) \ + $(patsubst $(SOURCEDIR)/%.cpp,$(BUILDDIR)/%.cpp.o,$(SOURCES_CPP)) + +DEPS := $(OBJS:.o=.d) + +# Rules +# ----- + +$(BUILDDIR)/%.s.o : $(SOURCEDIR)/%.s + @echo " AS $<" + @$(MKDIR) -p $(@D) # Build target's directory if it doesn't exist + $(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $< + +$(BUILDDIR)/%.c.o : $(SOURCEDIR)/%.c + @echo " CC $<" + @$(MKDIR) -p $(@D) # Build target's directory if it doesn't exist + $(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $< + +$(BUILDDIR)/%.cpp.o : $(SOURCEDIR)/%.cpp + @echo " CXX $<" + @$(MKDIR) -p $(@D) # Build target's directory if it doesn't exist + $(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $< + + +# Targets +# ------- + +.PHONY: all clean dump + +all: $(ROM) + +$(GBAFIX): + $(V)cd gbafix && make + +$(ELF): $(OBJS) + @echo " LD $@" + $(V)$(CC) -o $@ $(OBJS) $(LDFLAGS) + +$(ROM): $(ELF) $(GBAFIX) + @echo " OBJCOPY $<" + $(V)$(OBJCOPY) -O binary $< $@ + @echo " GBAFIX $@" + $(V)$(GBAFIX) $@ -t$(GAME_TITLE) -c$(GAME_CODE) + +$(DUMP): $(ELF) + @echo " OBJDUMP $@" + $(V)$(OBJDUMP) -h -C -S $< > $@ + +dump: $(DUMP) + +clean: + @echo " CLEAN" + $(V)$(RM) $(ROM) $(ELF) $(DUMP) $(MAP) $(BUILDDIR) + $(V)cd gbafix && make clean + +# Include dependency files if they exist +# -------------------------------------- + +-include $(DEPS) diff --git a/gbafix/.gitignore b/gbafix/.gitignore new file mode 100644 index 0000000..23abdd2 --- /dev/null +++ b/gbafix/.gitignore @@ -0,0 +1 @@ +gbafix diff --git a/gbafix/Makefile b/gbafix/Makefile new file mode 100644 index 0000000..c6bbdf8 --- /dev/null +++ b/gbafix/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: CC0-1.0 +# +# SPDX-FileContributor: Antonio Niño Díaz, 2022 + +NAME := gbafix + +CC := gcc +RM := rm -rf + +# `make V=` builds the binary in verbose build mode +V := @ + +CFLAGS := -Wall -O3 + +.PHONY: all clean + +all: $(NAME) + +$(NAME): gbafix.c + @echo " HOSTCC $<" + $(V)$(CC) $(CFLAGS) -o $@ $< + +clean: + @echo " CLEAN" + $(V)$(RM) $(NAME) diff --git a/gbafix/gbafix.c b/gbafix/gbafix.c new file mode 100644 index 0000000..7f8ef7f --- /dev/null +++ b/gbafix/gbafix.c @@ -0,0 +1,285 @@ +/* + "$Id: gbafix.c,v 1.2 2008-07-30 17:12:51 wntrmute Exp $" + + DevkitPro GBA ROM fix utility + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. + + Please report all bugs and problems through the bug tracker at + "http://sourceforge.net/tracker/?group_id=114505&atid=668551". + + "$Header: /lvm/shared/ds/ds/cvs/devkitpro-cvsbackup/tools/gba/gbatools/gbafix.c,v 1.2 2008-07-30 17:12:51 wntrmute Exp $" + +*/ +//--------------------------------------------------------------------------------- +// gbafix.c +//--------------------------------------------------------------------------------- +/* + Gameboy Advance ROM fixer (by Dark Fader / BlackThunder / WinterMute / Diegoisawesome) + Validates header of GBA roms. + + History + ------- + v1.05 - added debug offset argument, (Diegoisawesome) + v1.04 - converted to plain C, (WinterMute) + v1.03 - header.fixed, header.device_type + v1.02 - redefined the options (rgbfix style), checksum=0 + v1.01 - fix in parameters + v1.00 - logo, complement +*/ + +#pragma pack(1) + +#include +#include +#include +#include + +#define VER "1.05" +#define ARGV argv[arg] +#define VALUE (ARGV+2) +#define NUMBER strtoul(VALUE, NULL, 0) + +typedef struct +{ + uint32_t start_code; // B instruction + uint8_t logo[0xA0-0x04]; // logo data + uint8_t title[0xC]; // game title name + uint32_t game_code; // + uint16_t maker_code; // + uint8_t fixed; // 0x96 + uint8_t unit_code; // 0x00 + uint8_t device_type; // 0x00 + uint8_t unused[7]; // + uint8_t game_version; // 0x00 + uint8_t complement; // 800000A0..800000BC + uint16_t checksum; // 0x0000 +} Header; + + +Header header; + +unsigned short checksum_without_header = 0; + +const Header good_header = +{ + // start_code + 0xEA00002E, + // logo + { 0x24,0xFF,0xAE,0x51,0x69,0x9A,0xA2,0x21,0x3D,0x84,0x82,0x0A,0x84,0xE4,0x09,0xAD, + 0x11,0x24,0x8B,0x98,0xC0,0x81,0x7F,0x21,0xA3,0x52,0xBE,0x19,0x93,0x09,0xCE,0x20, + 0x10,0x46,0x4A,0x4A,0xF8,0x27,0x31,0xEC,0x58,0xC7,0xE8,0x33,0x82,0xE3,0xCE,0xBF, + 0x85,0xF4,0xDF,0x94,0xCE,0x4B,0x09,0xC1,0x94,0x56,0x8A,0xC0,0x13,0x72,0xA7,0xFC, + 0x9F,0x84,0x4D,0x73,0xA3,0xCA,0x9A,0x61,0x58,0x97,0xA3,0x27,0xFC,0x03,0x98,0x76, + 0x23,0x1D,0xC7,0x61,0x03,0x04,0xAE,0x56,0xBF,0x38,0x84,0x00,0x40,0xA7,0x0E,0xFD, + 0xFF,0x52,0xFE,0x03,0x6F,0x95,0x30,0xF1,0x97,0xFB,0xC0,0x85,0x60,0xD6,0x80,0x25, + 0xA9,0x63,0xBE,0x03,0x01,0x4E,0x38,0xE2,0xF9,0xA2,0x34,0xFF,0xBB,0x3E,0x03,0x44, + 0x78,0x00,0x90,0xCB,0x88,0x11,0x3A,0x94,0x65,0xC0,0x7C,0x63,0x87,0xF0,0x3C,0xAF, + 0xD6,0x25,0xE4,0x8B,0x38,0x0A,0xAC,0x72,0x21,0xD4,0xF8,0x07 } , + // title + { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, + // game code + 0x00000000, + // maker code + 0x3130, + // fixed + 0x96, + // unit_code + 0x00, + // device type + 0x00, + // unused + { 0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, + // game version + 0x00, + // complement + 0x00, + // checksum + 0x0000 +}; + +//--------------------------------------------------------------------------------- +char HeaderComplement(void) +/*--------------------------------------------------------------------------------- + Calculate Header complement check +---------------------------------------------------------------------------------*/ +{ + int n; + char c = 0; + char *p = (char *)&header + 0xA0; + for (n=0; n<0xBD-0xA0; n++) + { + c += *p++; + } + return -(0x19+c); +} + + +//--------------------------------------------------------------------------------- +int main(int argc, char *argv[]) +//--------------------------------------------------------------------------------- +{ + int arg; + char *argfile = 0; + FILE *infile; + + int size,bit; + + // show syntax + if (argc <= 1) + { + printf("GBA ROM fixer v"VER" by Dark Fader / BlackThunder / WinterMute / Diegoisawesome \n"); + printf("Syntax: gbafix [-p] [-t[title]] [-c] [-m] [-r] [-d]\n"); + printf("\n"); + printf("parameters:\n"); + printf(" -p Pad to next exact power of 2. No minimum size!\n"); + printf(" -t[] Patch title. Stripped filename if none given.\n"); + printf(" -c<game_code> Patch game code (four characters)\n"); + printf(" -m<maker_code> Patch maker code (two characters)\n"); + printf(" -r<version> Patch game version (number)\n"); + printf(" -d<debug> Enable debugging handler and set debug entry point (0 or 1)\n"); + return -1; + } + + // get filename + for (arg=1; arg<argc; arg++) + { + if ((ARGV[0] != '-')) { argfile=ARGV; break; } + } + + // check filename + if (!argfile) + { + printf("Filename needed!\n"); + return -1; + } + + // read file + infile = fopen(argfile, "r+b"); + if (!infile) { printf("Error opening input file!\n"); return -1; } + fseek(infile, 0, SEEK_SET); + if (fread(&header, sizeof(header), 1, infile) != 1) + { + printf("Error reading header of input file!\n"); + return -1; + } + + // fix some data + memcpy(header.logo, good_header.logo, sizeof(header.logo)); + memcpy(&header.fixed, &good_header.fixed, sizeof(header.fixed)); + memcpy(&header.device_type, &good_header.device_type, sizeof(header.device_type)); + + // parse command line + for (arg=1; arg<argc; arg++) + { + if ((ARGV[0] == '-')) + { + switch (ARGV[1]) + { + case 'p': // pad + { + fseek(infile, 0, SEEK_END); + size = ftell(infile); + for (bit=31; bit>=0; bit--) if (size & (1<<bit)) break; + if (size != (1<<bit)) + { + int todo = (1<<(bit+1)) - size; + while (todo--) fputc(0xFF, infile); + } + fseek(infile, 0, SEEK_SET); + break; + } + + case 't': // title + { + char title[256]; + memset(title, 0, sizeof(title)); + if (VALUE[0]) + { + strncpy(title, VALUE, sizeof(header.title)); + } + else + { + // use filename + char s[256], *begin=s, *t; strcpy(s, argfile); + t = strrchr(s, '\\'); if (t) begin = t+1; + t = strrchr(s, '/'); if (t) begin = t+1; + t = strrchr(s, '.'); if (t) *t = 0; + snprintf(title, sizeof(header.title), "%s", begin); + printf("%s\n",begin); + } + memcpy(header.title, title, sizeof(header.title)); // copy + break; + } + + case 'c': // game code + { + //if (!VALUE[0]) { printf("Need value for %s\n", ARGV); break; } + //header.game_code = NUMBER; + header.game_code = VALUE[0] | VALUE[1]<<8 | VALUE[2]<<16 | VALUE[3]<<24; + break; + } + + case 'm': // maker code + { + //if (!VALUE[0]) { printf("Need value for %s\n", ARGV); break; } + //header.maker_code = (unsigned short)NUMBER; + header.maker_code = VALUE[0] | VALUE[1]<<8; + break; + } + + case 'v': // ignored, compatability with other gbafix + { + break; + } + + case 'r': // version + { + if (!VALUE[0]) { printf("Need value for %s\n", ARGV); break; } + header.game_version = (unsigned char)NUMBER; + break; + } + + case 'd': // debug + { + if (!VALUE[0]) { printf("Need value for %s\n", ARGV); break; } + header.logo[0x9C-0x04] = 0xA5; // debug enable + header.device_type = (unsigned char)((NUMBER & 1) << 7); // debug handler entry point + break; + } + + default: + { + printf("Invalid option: %s\n", ARGV); + } + } + } + } + + // update complement check & total checksum + header.complement = 0; + header.checksum = 0; // must be 0 + header.complement = HeaderComplement(); + //header.checksum = checksum_without_header + HeaderChecksum(); + + fseek(infile, 0, SEEK_SET); + fwrite(&header, sizeof(header), 1, infile); + fclose(infile); + + printf("ROM fixed!\n"); + + return 0; +} diff --git a/source/main.c b/source/main.c index 96760e8..5d58d7a 100644 --- a/source/main.c +++ b/source/main.c @@ -88,7 +88,7 @@ volatile FIXED_POINT K2 = (2 * CUBE_WIDTH) + 20; #define SQ(n) (n * n) #define SQ_FP(n) (MULT_FP(n, n)) -#define COORD2INDEX(x, y) (x * VWIDTH + y) +#define COORD2INDEX(x, y) (y * VWIDTH + x) #define COUPLE2INDEX(x) (COORD2INDEX(x[0], x[1])) #define GET_ROTATE_X_Q(a) ({ float _a = (FIXED2FLOAT(a)) ; \ @@ -117,7 +117,7 @@ struct Quaternions { FIXED_POINT x; FIXED_POINT y; FIXED_POINT z; -} Target, Current, Last; +} Target, Current; FIXED_POINT interpolationStep = 0; FIXED_POINT zBuffer[VHEIGHT * VWIDTH]; @@ -235,38 +235,62 @@ chooseMainFace() return frontFacingFace; } -void -fill_quads(char current_face, char top_left[2], char top_right[2], - char bot_left[2], char bot_right[2]) +char +isInQuad(char curr[2], char top[2], char left[2], + char right[2], char bot[2]) { - // calc slope foreach side - float slope_top = 0; - float slope_bot = 0; - float slope_left = INFINITY; - float slope_right = -INFINITY; + char *points[4] = {top, left, bot, right}; - if (top_left[0] != top_right[0]) - slope_top = (float)(top_right[1] - top_left[1]) / - (float)(top_right[0] - top_left[0]); - if (bot_left[0] == bot_right[0]) - slope_bot = (float)(bot_right[1] - bot_left[1]) / - (float)(bot_right[0] - bot_left[0]); - if (top_left[0] == bot_left[0]) - slope_left = (float)(bot_left[1] - top_left[1]) / - (float)(bot_left[0] - top_left[0]); - if (top_right[0] == bot_right[0]) - slope_right = (float)(bot_right[1] - top_right[1]) / - (float)(bot_right[0] - top_right[0]); + char pos = 0, neg = 0; + char x = curr[0]; + char y = curr[1];; + int d; - int top = top_right[1] > top_left[1] ? top_right[1]: top_left[1]; - int bot = bot_right[1] > bot_left[1] ? bot_right[1]: bot_left[1]; - int left = top_left[0] > bot_left[0] ? top_left[0]: bot_left[0]; - int right = top_right[0] > bot_right[0] ? top_right[0]: bot_right[0]; - for (int y = top ; y <= bot ; ++y) { - for (int x = left ; x <= right ; ++x) { - if (slope_top * x + top <= y) - // TODO side check - output[COORD2INDEX(x, y)] = current_face; + for (char i = 0; i < 4; ++i) { + if (points[i][0] == curr[0] && points[i][1] == curr[1]) + return 1; + + //Form a segment between the i'th point + char x1 = points[i][0]; + char y1 = points[i][1]; + + //And the i+1'th, or if i is the last, with the first point + char i2 = (i + 1) % 4; + + char x2 = points[i2][0]; + char y2 = points[i2][1]; + + + //Compute the cross product + d = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1); + + if (d > 0) ++pos; + if (d < 0) ++neg; + + //If the sign changes, then point is outside + if (pos > 0 && neg > 0) + return 0; + } + + return 1; +} + +void +fill_quads(char current_face, char top[2], char left[2], + char right[2], char bot[2]) +{ + if (current_face != 0) return; + output[COUPLE2INDEX(top)] = RGB15(0, 0, 15); + output[COUPLE2INDEX(left)] = RGB15(0, 0, 15); + output[COUPLE2INDEX(right)] = RGB15(0, 0, 15); + + for (int y = top[1] ; y < bot[1] ; ++y) { + for (int x = left[0] ; x < right[0] ; ++x) { + char curr[2] = {x, y}; + if (isInQuad(curr, top, left, right, bot)) + //zbuffer issue + {} + //output[COORD2INDEX(x, y)] = current_face; } } } @@ -275,33 +299,37 @@ void detect_and_fill_quads() { for (int current_face = 0 ; current_face < NUM_FACES ; ++current_face) { - char top_left [2] = {VWIDTH, VHEIGHT}; - char top_right[2] = {0, VHEIGHT}; - char bot_left [2] = {VWIDTH, 0}; - char bot_right[2] = {0, 0}; + char last_top [2] = {VWIDTH, VHEIGHT}; + char last_left[2] = {VWIDTH, 0}; + char last_right [2] = {0, 0}; + char last_bot[2] = {0, 0}; + char top [2] = {VWIDTH, VHEIGHT}; + char left[2] = {VWIDTH, 0}; + char right [2] = {0, 0}; + char bot[2] = {0, 0}; for (char y = 0; y < VHEIGHT; ++y) { for (char x = 0; x < VWIDTH; ++x) { if (output[COORD2INDEX(x, y)] != current_face) continue; - if (x <= top_left[0] && y <= top_left[1]){ - top_left[0] = x; - top_left[1] = y; + if (x <= left[0]) { + left[0] = x; + left[1] = y; } - if (x >= top_right[0] && y <= top_right[1]){ - top_right[0] = x; - top_right[1] = y; + if (y <= top[1]) { + top[0] = x; + top[1] = y; } - if (x <= bot_left[0] && y >= bot_left[1]){ - bot_left[0] = x; - bot_left[1] = y; + if (x >= right[0]) { + right[0] = x; + right[1] = y; } - if (x >= bot_right[0] && y >= bot_right[1]){ - bot_right[0] = x; - bot_right[1] = y; + if (y >= bot[1]) { + bot[0] = x; + bot[1] = y; } } } - fill_quads(current_face, top_left, top_right, bot_left, bot_right); + fill_quads(current_face, top, left, right, bot); } } @@ -315,7 +343,7 @@ printAscii() MEM_VRAM_MODE3_FB[120 + 96 * GBA_SCREEN_W] = RGB15(currentCountR, 31 - currentCountR, 0); currentCountR = currentCountR == 31 ? 0 : 31; - //detect_and_fill_quads(); + detect_and_fill_quads(); for (int i = 0; i < VHEIGHT; ++i) { for (int j = 0; j < VWIDTH; ++j) { @@ -411,31 +439,30 @@ handleAngle(char input) // TODO if (currentlyMoving == 0) { currentlyMoving = input; - Last = Current; switch (input) { case 'w': case 'W': - Target = multQ(GET_ROTATE_X_Q(FLOAT2FIXED(M_PI_2)), Target); + Target = multQ(GET_ROTATE_X_Q(FLOAT2FIXED(M_PI_2)), Current); break; case 'a': case 'A': - Target = multQ(GET_ROTATE_Y_Q(-FLOAT2FIXED(M_PI_2)), Target); + Target = multQ(GET_ROTATE_Y_Q(-FLOAT2FIXED(M_PI_2)), Current); break; case 's': case 'S': - Target = multQ(GET_ROTATE_X_Q(-FLOAT2FIXED(M_PI_2)), Target); + Target = multQ(GET_ROTATE_X_Q(-FLOAT2FIXED(M_PI_2)), Current); break; case 'd': case 'D': - Target = multQ(GET_ROTATE_Y_Q(FLOAT2FIXED(M_PI_2)), Target); + Target = multQ(GET_ROTATE_Y_Q(FLOAT2FIXED(M_PI_2)), Current); break; case 'q': case 'Q': - Target = multQ(GET_ROTATE_Z_Q(-FLOAT2FIXED(M_PI_2)), Target); + Target = multQ(GET_ROTATE_Z_Q(-FLOAT2FIXED(M_PI_2)), Current); break; case 'e': case 'E': - Target = multQ(GET_ROTATE_Z_Q(FLOAT2FIXED(M_PI_2)), Target); + Target = multQ(GET_ROTATE_Z_Q(FLOAT2FIXED(M_PI_2)), Current); break; default: currentlyMoving = 0; @@ -480,7 +507,7 @@ main() cubeX <= CUBE_WIDTH_FP - STEP_FP; cubeX += STEP_FP) { for (FIXED_POINT cubeY = -CUBE_WIDTH_FP + STEP_FP; cubeY <= CUBE_WIDTH_FP - STEP_FP; cubeY += STEP_FP) { - switch (frontFacingFace) { + switch (FACE_FRONT) { case FACE_FRONT: rotateCube(cubeX, cubeY, -CUBE_WIDTH_FP, FACE_FRONT); break; diff --git a/source/sys/gba_cart.ld b/source/sys/gba_cart.ld new file mode 100644 index 0000000..d190710 --- /dev/null +++ b/source/sys/gba_cart.ld @@ -0,0 +1,233 @@ +/* + * SPDX-License-Identifier: CC0-1.0 + * + * SPDX-FileContributor: Antonio Niño Díaz, 2022 + */ + +/* + * Some links with information about linker scripts: + * + * - https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/specialsections.html + * + * - https://blog.thea.codes/the-most-thoroughly-commented-linker-script/ + * + * - http://beefchunk.com/documentation/sys-programming/binary_formats/elf/elf_from_the_programmers_perspective/node4.html + */ + +OUTPUT_FORMAT("elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(entrypoint) + +MEMORY { + ROM : ORIGIN = 0x08000000, LENGTH = 32M + IWRAM : ORIGIN = 0x03000000, LENGTH = 32K + EWRAM : ORIGIN = 0x02000000, LENGTH = 256K +} + +/* Set stack pointers the same way as the BIOS */ + +__STACK_SVC_SIZE__ = 0x40; +__STACK_IRQ_SIZE__ = 0xA0; +__STACK_USR_SIZE_MIN__ = 0x200; /* Minimum size, the real size may be bigger */ + +__STACK_END__ = ORIGIN(IWRAM) + LENGTH(IWRAM) - 0x20; /* Used by BIOS */ +__STACK_SVC_END__ = __STACK_END__; +__STACK_SVC_START__ = __STACK_SVC_END__ - __STACK_SVC_SIZE__; +__STACK_IRQ_END__ = __STACK_SVC_START__; +__STACK_IRQ_START__ = __STACK_IRQ_END__ - __STACK_IRQ_SIZE__; +__STACK_USR_END__ = __STACK_IRQ_START__; +__STACK_USR_START__ = __STACK_USR_END__ - __STACK_USR_SIZE_MIN__; +__STACK_START__ = __STACK_USR_START__; + +SECTIONS +{ + /* + * ROM sections + * ============ + */ + + /* Header and crt0 */ + + .gba_crt0 : ALIGN(4) + { + KEEP (*(.gba_crt0)) + } > ROM + + /* Code */ + + .text : ALIGN(4) + { + *(.text) + *(.text*) + *(.gnu.linkonce.t.*) /* Used for vague linking */ + + /* ARM/Thumb interworking code */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + + /* Array of functions to be called at the start of the program */ + KEEP(*(.init)) + /* Array of functions to be called at the end of the program */ + KEEP(*(.fini)) + } > ROM + + /* Read-only data */ + + .rodata : ALIGN(4) + { + *(.rodata) + *(.rodata*) + *(.gnu.linkonce.r.*) /* Used for vague linking */ + } > ROM + + /* + * Required for C++ and for C programs that try to examine backtraces. Each + * function that can throw exceptions has entries in exidx and extab. + * + * - exidx is used to contain index entries for stack unwinding. + * - extab names sections containing exception unwinding information. + */ + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > ROM + + .ARM.exidx : ALIGN(4) + { + PROVIDE_HIDDEN(__exidx_start = .); + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + PROVIDE_HIDDEN(__exidx_end = .); + } > ROM + + /* Array of functions to be called during initialization of the program */ + + .preinit_array : ALIGN(4) + { + __preinit_array_start = .; + KEEP(*(.preinit_array*)) + __preinit_array_end = .; + } > ROM + + /* + * Array of functions to be called during initialization of the program. + * They are called after calling everything in .preinit_array + */ + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array*)) + __init_array_end = .; + } > ROM + + /* Array of functions called when the program ends. */ + + .fini_array : ALIGN(4) + { + __fini_array_start = .; + KEEP(*(SORT(.fini_array.*))) + KEEP(*(.fini_array*)) + __fini_array_end = .; + } > ROM + + /* + * IWRAM sections + * ============== + */ + + /* Uninitialized variables */ + + .bss (NOLOAD) : ALIGN(4) + { + *(.bss) + *(.bss*) + *(.gnu.linkonce.b.*) /* Used for vague linking */ + *(COMMON) /* In case -fcommon is used in gcc (not used by default) */ + . = ALIGN(4); + } > IWRAM + + __IWRAM_BSS_START__ = ADDR(.bss); + __IWRAM_BSS_SIZE__ = SIZEOF(.bss); + __IWRAM_BSS_END__ = __IWRAM_BSS_START__ + __IWRAM_BSS_SIZE__; + + /* Read-write data goes into IWRAM */ + + .data : ALIGN(4) + { + __DATA_START__ = .; + *(.data) + *(.data*) + *(.gnu.linkonce.d.*) /* Used for vague linking */ + . = ALIGN(4); + __DATA_END__ = .; + } > IWRAM AT> ROM + + __DATA_SIZE__ = __DATA_END__ - __DATA_START__; + __DATA_LMA__ = LOADADDR(.data); + + /* Sections that the user requests to add to IWRAM */ + + .iwram : ALIGN(4) + { + __IWRAM_START__ = .; + *(.iwram) + *(.iwram*) + *iwram.*(.text*) + *iwram.*(.data*) + . = ALIGN(4); + __IWRAM_END__ = .; + } > IWRAM AT> ROM + + __IWRAM_SIZE__ = __IWRAM_END__ - __IWRAM_START__; + __IWRAM_LMA__ = LOADADDR(.iwram); + + /* The stack goes afterwards, check that there aren't overflows */ + + ASSERT(__IWRAM_END__ <= __STACK_START__, + "Not enough free IWRAM for stack") + + /* Calculate real size of the user stack */ + + __STACK_USR_SIZE__ = __STACK_USR_END__ - __IWRAM_END__; + + /* + * EWRAM sections + * ============== + */ + + /* + * Uninitialized data explicitly placed in EWRAM. This section must be + * called ".sbss" for compatibility with devkitARM. + */ + + .sbss (NOLOAD) : ALIGN(4) + { + *(.sbss) + *(.sbss*) + . = ALIGN(4); + } > EWRAM + + /* Initialized data explicitly placed in EWRAM */ + + .ewram : ALIGN(4) + { + __EWRAM_START__ = .; + *(.ewram) + *(.ewram*) + *ewram.*(.text*) + *ewram.*(.data*) + . = ALIGN(4); + __EWRAM_END__ = .; + } > EWRAM AT > ROM + + __EWRAM_SIZE__ = __EWRAM_END__ - __EWRAM_START__; + __EWRAM_LMA__ = LOADADDR(.ewram); + + /* The heap information should be after the last EWRAM section */ + + __HEAP_START__ = __EWRAM_END__; + __HEAP_END__ = ORIGIN(EWRAM) + LENGTH(EWRAM); + __HEAP_SIZE__ = __HEAP_END__ - __HEAP_START__; +} diff --git a/source/sys/gba_crt0.s b/source/sys/gba_crt0.s new file mode 100644 index 0000000..a731f1d --- /dev/null +++ b/source/sys/gba_crt0.s @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: CC0-1.0 +// +// SPDX-FileContributor: Antonio Niño Díaz, 2022 + + .section .gba_crt0, "ax" + .global entrypoint + .cpu arm7tdmi + + .arm + +entrypoint: + b header_end + + .fill 156, 1, 0 // Nintendo Logo + .fill 12, 1, 0 // Game Title + .fill 4, 1, 0 // Game Code + .byte 0x30, 0x30 // Maker Code ("00") + .byte 0x96 // Fixed Value (must be 0x96) + .byte 0x00 // Main unit code + .byte 0x00 // Device Type + .fill 7, 1, 0 // Reserved Area + .byte 0x00 // Software version + .byte 0x00 // Complement check (header checksum) + .byte 0x00, 0x00 // Reserved Area + +header_end: + b start_vector + + // Multiboot Header Entries + .byte 0 // Boot mode + .byte 0 // Slave ID Number + .fill 26, 1, 0 // Not used + .word 0 // JOYBUS entrypoint + + .align + +start_vector: + + // Disable interrupts + mov r0, #0x4000000 + mov r1, #0 + str r1, [r0, #0x208] // IME + + // Setup IRQ mode stack + mov r0, #0x12 + msr cpsr, r0 + ldr sp, =__STACK_IRQ_END__ + + // Setup system mode stack + mov r0, #0x1F + msr cpsr, r0 + ldr sp, =__STACK_USR_END__ + + // Switch to Thumb mode + add r0, pc, #1 + bx r0 + + .thumb + + // Clear IWRAM + ldr r0, =#0x3000000 + ldr r1, =#(32 * 1024) + bl mem_zero + + // Copy data section from ROM to RAM + ldr r0, =__DATA_LMA__ + ldr r1, =__DATA_START__ + ldr r2, =__DATA_SIZE__ + bl mem_copy + + // Copy IWRAM data from ROM to RAM + ldr r0, =__IWRAM_LMA__ + ldr r1, =__IWRAM_START__ + ldr r2, =__IWRAM_SIZE__ + bl mem_copy + + // Clear EWRAM + ldr r0, =#0x2000000 + ldr r1, =#(256 * 1024) + bl mem_zero + + // Copy EWRAM data from ROM to RAM + ldr r0, =__EWRAM_LMA__ + ldr r1, =__EWRAM_START__ + ldr r2, =__EWRAM_SIZE__ + bl mem_copy + + // Global constructors + ldr r2, =__libc_init_array + bl blx_r2_trampoline + + // Call main() + mov r0, #0 // int argc + mov r1, #0 // char *argv[] + ldr r2, =main + bl blx_r2_trampoline + + // Global destructors + ldr r2, =__libc_fini_array + bl blx_r2_trampoline + + // If main() returns, reboot the GBA using SoftReset + swi #0x00 + +// r0 = Base address +// r1 = Size +mem_zero: + and r1, r1 + beq 2f // Return if size is 0 + + mov r2, #0 +1: + stmia r0!, {r2} + sub r1, #4 + bne 1b + +2: + bx lr + +// r0 = Source address +// r1 = Destination address +// r2 = Size +mem_copy: + and r2, r2 + beq 2f // Return if size is 0 + +1: + ldmia r0!, {r3} + stmia r1!, {r3} + sub r2, #4 + bne 1b + +2: + bx lr + +// r2 = Address to jump to +blx_r2_trampoline: + bx r2 + + .align + .pool + .end diff --git a/source/sys/syscalls.c b/source/sys/syscalls.c new file mode 100644 index 0000000..f10a3ae --- /dev/null +++ b/source/sys/syscalls.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: CC0-1.0 +// +// SPDX-FileContributor: Antonio Niño Díaz, 2022 + +#include <errno.h> +#include <sys/stat.h> +#include <sys/times.h> +#include <time.h> + +// This file implements stubs for system calls. For more information about it, +// check the documentation of newlib: +// +// https://sourceware.org/newlib/libc.html#Syscalls + +#undef errno +extern int errno; + +char *__env[1] = { 0 }; +char **environ = __env; + +int _getpid(void) +{ + return 1; +} + +int _kill(int pid, int sig) +{ + (void)pid; + (void)sig; + + errno = EINVAL; + return -1; +} + +void _exit(int status) +{ + _kill(status, -1); + + // Hang, there is nowhere to go + while (1); +} + +__attribute__((weak)) int _read(int file, char *ptr, int len) +{ + (void)file; + (void)ptr; + + return len; +} + +__attribute__((weak)) int _write(int file, char *ptr, int len) +{ + (void)file; + (void)ptr; + + return len; +} + +int _close(int file) +{ + (void)file; + + return -1; +} + + +int _fstat(int file, struct stat *st) +{ + (void)file; + + st->st_mode = S_IFCHR; + return 0; +} + +int _isatty(int file) +{ + (void)file; + + return 1; +} + +int _lseek(int file, int ptr, int dir) +{ + (void)file; + (void)ptr; + (void)dir; + + return 0; +} + +int _open(char *path, int flags, ...) +{ + (void)path; + (void)flags; + + return -1; +} + +int _wait(int *status) +{ + (void)status; + + errno = ECHILD; + return -1; +} + +int _unlink(char *name) +{ + (void)name; + + errno = ENOENT; + return -1; +} + +int _times(struct tms *buf) +{ + (void)buf; + + return -1; +} + +int _stat(char *file, struct stat *st) +{ + (void)file; + + st->st_mode = S_IFCHR; + return 0; +} + +int _link(char *old, char *new) +{ + (void)old; + (void)new; + + errno = EMLINK; + return -1; +} + +int _fork(void) +{ + errno = EAGAIN; + return -1; +} + +int _execve(char *name, char **argv, char **env) +{ + (void)name; + (void)argv; + (void)env; + + errno = ENOMEM; + return -1; +} + +void *_sbrk(int incr) +{ + // Symbols defined by the linker + extern char __HEAP_START__[]; + extern char __HEAP_END__[]; + const uintptr_t HEAP_START = (uintptr_t) __HEAP_START__; + const uintptr_t HEAP_END = (uintptr_t) __HEAP_END__; + + // Pointer to the current end of the heap + static uintptr_t heap_end = HEAP_START; + + if (heap_end + incr > HEAP_END) + { + errno = ENOMEM; + return (void *)-1; + } + + uintptr_t prev_heap_end = heap_end; + + heap_end += incr; + + return (void *)prev_heap_end; +}