diff --git a/CMakeLists.txt b/CMakeLists.txt index 51b7a14..795224c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,12 @@ add_executable(tibia main.c input.c input.h config.c - config.h) + config.h + map.c + map.h + bitmap.c + bitmap.h + Objects.c + Objects.h) target_link_libraries(tibia SDL3::SDL3) diff --git a/Objects.c b/Objects.c new file mode 100644 index 0000000..b40faa6 --- /dev/null +++ b/Objects.c @@ -0,0 +1,202 @@ +// +// Created by rov on 12/27/25. +// + +#include "Objects.h" + +#include +#include +#include +#include +#include + +bool Objects_Init(Objects_t *objects) { + memset(objects->spriteTable, 0, sizeof(objects->spriteTable)); + + for (int i = 0; i < THING_DATA_POOL_SIZE; i++) { + objects->thingDataPool[i] = malloc(1536); + + if (objects->thingDataPool[i]) { + memset(objects->thingDataPool[i], 0, 1536); + } + } + + memset(objects->activeObjectList, 0, sizeof(objects->activeObjectList)); + + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_Init: memory allocated"); + + if (!Objects_LoadData(objects)) { + return false; + } + + return Objects_LoadSprites(objects); +} + +bool Objects_LoadData(Objects_t *objects) { + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadData: loading object data."); + + SDL_IOStream *file = SDL_IOFromFile("MUDOBJ.CLI", "rb"); + if (!file) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Objects_LoadData: couldn't open file MUDOBJ.CLI", + NULL); + return false; + } + + int objectsLoaded = 0; + while (true) { + uint16_t src; + uint8_t categoryID, thingID; + + // Read IDs + if (SDL_ReadIO(file, &categoryID, 1) != 1) { + break; + } + + if (SDL_ReadIO(file, &thingID, 1) != 1) { + break; + } + + // Store header info + ThingHeader *header = (ThingHeader *) ((uint8_t *) objects->thingDataPool[categoryID] + thingID * 6); + + SDL_ReadIO(file, &header->properties, 2); + SDL_ReadIO(file, &src, 2); + header->flags = src + 4; + + // Calculate total sprites needed + const int countWidth = (src >> 4 & 3) + 1; + const int countHeight = (src >> 6 & 3) + 1; + const int countPattern = (src & 0xF) + 1; + const int countLayers = (src >> 8 & 1) + 1; + const int countAnim = (src >> 9 & 1) + 1; + const int countSides = (src >> 10 & 1) + 1; + + // Total sprites to read for this object + const int totalSprites = countWidth * countHeight * countPattern * countLayers * countAnim * countSides; + const size_t bufferSize = sizeof(uint16_t) + totalSprites * sizeof(uint16_t); + + uint16_t *destBuffer = objects->activeObjectList[categoryID]; + if (destBuffer == NULL) { + destBuffer = (uint16_t *) malloc(bufferSize); + if (!destBuffer) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadData: cannot allocate memory"); + break; + } + + destBuffer[0] = 0; + objects->activeObjectList[categoryID] = destBuffer; + } else { + const size_t currentCount = destBuffer[0]; + const size_t newTotalSize = sizeof(uint16_t) + (currentCount + totalSprites) * sizeof(uint16_t); + + destBuffer = (uint16_t *) realloc(destBuffer, newTotalSize); + objects->activeObjectList[categoryID] = destBuffer; + } + + header->spriteIndex = destBuffer[0]; + + // Read sprites + // Pointer to where we start writing new sprites: Buffer + 1 (Skip count) + Current Count + uint16_t *writePtr = &destBuffer[1 + destBuffer[0]]; + const size_t readCount = SDL_ReadIO(file, writePtr, totalSprites * 2); + + // Update the item count in the buffer header + // Note: SDL_ReadIO returns bytes, so we divide by 2 for item count + const uint16_t itemsRead = (uint16_t) (readCount / 2); + destBuffer[0] += itemsRead; + objectsLoaded += itemsRead; + } + + SDL_CloseIO(file); + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadData: data loaded (%d objects).", objectsLoaded); + + return true; +} + +bool Objects_LoadSprites(Objects_t *objects) { + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: loading object sprites."); + + // Free existing sprites to prevent memory leaks + for (int i = 0; i < SPRITE_TABLE_SIZE; i++) { + if (objects->spriteTable[i] != NULL) { + free(objects->spriteTable[i]); + objects->spriteTable[i] = NULL; + } + } + + SDL_IOStream *file = SDL_IOFromFile("MUDOBJ.SPR", "rb"); + if (!file) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Objects_LoadSprites: couldn't open file MUDOBJ.SPR.", + NULL); + return false; + } + + uint16_t spriteCount = 0; + if (SDL_ReadIO(file, &spriteCount, 2) != 2) { + SDL_CloseIO(file); + + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Objects_LoadSprites: couldn't get sprite count.", + NULL); + return false; + } + + int spritesLoaded = 0; + for (int i = 0; i < spriteCount; i++) { + // Read ID (2 bytes) + uint16_t spriteID; + if (SDL_ReadIO(file, &spriteID, 2) != 2) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read sprite ID."); + break; + } + + uint16_t spriteSize; + if (SDL_ReadIO(file, &spriteSize, 2) != 2) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read sprite size."); + break; + } + + // Ensure ID is within bounds + if (spriteID >= SPRITE_TABLE_SIZE) { + SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: Sprite ID %d exceeds table size (%d). Skipping.", + spriteID, SPRITE_TABLE_SIZE); + + // Skip the data bytes so we don't desync the file stream + SDL_SeekIO(file, spriteSize - 2, SDL_IO_SEEK_CUR); + continue; + } + + uint8_t *spriteBuffer = malloc(spriteSize); + if (!spriteBuffer) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: cannot allocate memory"); + break; + } + + objects->spriteTable[spriteID] = spriteBuffer; + + // Store size in the first 2 bytes of the buffer + *(uint16_t *) spriteBuffer = spriteSize; + + // Read data + // Read (Size - 2) bytes into Buffer + 2 + const size_t bytesToRead = spriteSize - 2; + if (bytesToRead > 0) { + if (SDL_ReadIO(file, spriteBuffer + 2, bytesToRead) != bytesToRead) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read full sprite data"); + break; + } + } + + spritesLoaded++; + } + + SDL_CloseIO(file); + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: %d sprites loaded.", spritesLoaded); + + return true; +} + +void Objects_Destroy(const Objects_t *objects) { + for (int i = 0; i < THING_DATA_POOL_SIZE; i++) { + free(objects->thingDataPool[i]); + } +} diff --git a/Objects.h b/Objects.h new file mode 100644 index 0000000..63bff8e --- /dev/null +++ b/Objects.h @@ -0,0 +1,35 @@ +// +// Created by rov on 12/27/25. +// + +#ifndef TIBIA_OBJECTS_H +#define TIBIA_OBJECTS_H + +#define SPRITE_TABLE_SIZE 500 +#define THING_DATA_POOL_SIZE 256 +#define ACTIVE_OBJECT_LIST_SIZE 256 + +#include +#include + +#pragma pack(push, 1) +typedef struct { + uint16_t properties; + uint16_t spriteIndex; + uint16_t flags; +} ThingHeader; +#pragma pack(pop) + +typedef struct Objects { + // TODO: not sure about the name/purpose of these. + void* spriteTable[SPRITE_TABLE_SIZE]; + void* thingDataPool[THING_DATA_POOL_SIZE]; + void* activeObjectList[ACTIVE_OBJECT_LIST_SIZE]; +} Objects_t; + +bool Objects_Init(Objects_t* objects); +bool Objects_LoadData(Objects_t* objects); +bool Objects_LoadSprites(Objects_t* objects); +void Objects_Destroy(const Objects_t* objects); + +#endif //TIBIA_OBJECTS_H \ No newline at end of file diff --git a/app.c b/app.c index 9dd7eaa..2df4b3f 100644 --- a/app.c +++ b/app.c @@ -4,12 +4,30 @@ #include "input.h" void App_Init(App_t *app) { - Window_Init_SDL(); + app->isRunning = false; + + if (!Window_Init_SDL()) { + return; + } + System_QueryMetrics(&app->metrics); - Window_Init(app->metrics.screenWidth, app->metrics.screenHeight); + if (!Window_Init(app->metrics.screenWidth, app->metrics.screenHeight)) { + return; + } Config_Load_Settings(&app->configParams); + if (!Map_Init(&app->map)) { + return; + } + + if (!Bitmap_Init(&app->bitmap)) { + return; + } + + if (!Objects_Init(&app->graphics)) { + return; + } app->isRunning = true; } @@ -21,6 +39,9 @@ void App_Run(App_t *app) { } } -void App_Shutdown(App_t *app) { +void App_Shutdown(const App_t* app) { + Objects_Destroy(&app->graphics); + Map_Destroy(&app->map); + Bitmap_Destroy(&app->bitmap); Window_Shutdown(); } diff --git a/app.h b/app.h index e21fda0..dd9ae68 100644 --- a/app.h +++ b/app.h @@ -3,17 +3,23 @@ #include +#include "bitmap.h" #include "config.h" +#include "Objects.h" +#include "map.h" #include "system.h" typedef struct App { bool isRunning; SystemMetrics_t metrics; ConfigParams_t configParams; + Map_t map; + Bitmap_t bitmap; + Objects_t graphics; } App_t; void App_Init(App_t* app); void App_Run(App_t* app); -void App_Shutdown(App_t* app); +void App_Shutdown(const App_t* app); #endif \ No newline at end of file diff --git a/bitmap.c b/bitmap.c new file mode 100644 index 0000000..a14cce9 --- /dev/null +++ b/bitmap.c @@ -0,0 +1,54 @@ +// +// Created by rov on 12/27/25. +// + +#include "bitmap.h" + +#include +#include +#include + +bool Bitmap_Init(Bitmap_t* bitmap) { + bitmap->tibiaSurface = Bitmap_LoadFromFile("TIBIA.BMP"); + if (!bitmap->tibiaSurface) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Bitmap_Init: couldn't load tibia.bmp", NULL); + return false; + } + + bitmap->marbleSurface = Bitmap_LoadFromFile("MARBLE.BMP"); + if (!bitmap->marbleSurface) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Bitmap_Init: couldn't load marble.bmp", NULL); + return false; + } + + // TODO: Note: here, on the original client, it created a offscreen buffer of 36x36 pixels. + + return true; +} + +void Bitmap_Destroy(const Bitmap_t* bitmap) { + SDL_DestroySurface(bitmap->tibiaSurface); + SDL_DestroySurface(bitmap->marbleSurface); + + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Bitmap_Destroy: surfaces destroyed."); +} + +SDL_Surface* Bitmap_LoadFromFile(const char* filename) { + SDL_IOStream* stream = SDL_IOFromFile(filename, "rb"); + if (!stream) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Bitmap_LoadFromFile: couldn't open file: %s", filename); + return NULL; + } + + return SDL_LoadBMP_IO(stream, true); +} + +SDL_Surface* Bitmap_LoadFromMemory(const void* buffer, const size_t size) { + SDL_IOStream* stream = SDL_IOFromConstMem(buffer, size); + if (!stream) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Bitmap_LoadFromMemory: couldn't read from memory."); + return NULL; + } + + return SDL_LoadBMP_IO(stream, true); +} diff --git a/bitmap.h b/bitmap.h new file mode 100644 index 0000000..ab2854f --- /dev/null +++ b/bitmap.h @@ -0,0 +1,20 @@ +// +// Created by rov on 12/27/25. +// + +#ifndef TIBIA_BITMAP_H +#define TIBIA_BITMAP_H +#include + +typedef struct Bitmap { + SDL_Surface* tibiaSurface; + SDL_Surface* marbleSurface; +} Bitmap_t ; + +bool Bitmap_Init(Bitmap_t* bitmap); +void Bitmap_Destroy(const Bitmap_t* bitmap); + +SDL_Surface* Bitmap_LoadFromFile(const char* filename); +SDL_Surface* Bitmap_LoadFromMemory(const void* buffer, size_t size); + +#endif //TIBIA_BITMAP_H diff --git a/map.c b/map.c new file mode 100644 index 0000000..616606f --- /dev/null +++ b/map.c @@ -0,0 +1,30 @@ +// +// Created by rov on 12/26/25. +// + +#include "map.h" + +#include +#include + +bool Map_Init(Map_t *map) { + // TODO: fix this magical numbers + map->mapData = malloc(29070); + map->mapAuxData = malloc(855); + + if (map->mapData == NULL || map->mapAuxData == NULL) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Map_Init: couldn't allocate memory.", NULL); + return false; + } + + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Map_Init: memory allocated."); + + return true; +} + +void Map_Destroy(const Map_t *map) { + free(map->mapData); + free(map->mapAuxData); + + SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Map_Destroy: memory freed."); +} diff --git a/map.h b/map.h new file mode 100644 index 0000000..8677245 --- /dev/null +++ b/map.h @@ -0,0 +1,18 @@ +// +// Created by rov on 12/26/25. +// + +#ifndef TIBIA_MAP_H +#define TIBIA_MAP_H + +#include + +typedef struct Map { + void* mapData; + void* mapAuxData; +} Map_t; + +bool Map_Init(Map_t* map); +void Map_Destroy(const Map_t* map); + +#endif //TIBIA_MAP_H \ No newline at end of file diff --git a/window.c b/window.c index eb4aed6..624eb1b 100644 --- a/window.c +++ b/window.c @@ -3,14 +3,25 @@ static SDL_Window *window; static SDL_Renderer *renderer; -void Window_Init_SDL() { - SDL_Init(SDL_INIT_VIDEO); +bool Window_Init_SDL() { + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Unable to initialize SDL", NULL); + return false; + } + SDL_SetHint(SDL_HINT_LOGGING, "*=info"); + + return true; } -void Window_Init(int width, int height) { - SDL_CreateWindowAndRenderer( - "Tibia", width, height, 0, &window, &renderer); +bool Window_Init(const int width, const int height) { + if (!SDL_CreateWindowAndRenderer( + "Tibia", width, height, 0, &window, &renderer)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Unable to create SDL Window or Renderer", NULL); + return false; + } + + return true; } void Window_Shutdown() { diff --git a/window.h b/window.h index 6b26c50..4f9ac46 100644 --- a/window.h +++ b/window.h @@ -3,8 +3,8 @@ #include -void Window_Init_SDL(); -void Window_Init(int width, int height); +bool Window_Init_SDL(); +bool Window_Init(int width, int height); void Window_Shutdown(); SDL_Renderer* Window_GetRenderer();