Compare commits

..

2 commits

Author SHA1 Message Date
Scott Duensing
f28bbd476e Library loading / unloading working. 2025-12-21 19:39:05 -06:00
Scott Duensing
646ef619b0 Makefile now builds DYNs magically. 2025-12-20 19:35:54 -06:00
14 changed files with 391 additions and 180 deletions

View file

@ -22,6 +22,9 @@
#
# This Makefile is terrible. I have no idea what I'm doing.
# Tools
CC := gcc
AR := ar
@ -29,7 +32,7 @@ DXE3GEN := dxe3gen
EXE2COFF := exe2coff
# Compiler Settings
CFLAGS := -DPLATFORM_DOS -Wall
CFLAGS := -DPLATFORM_DOS -Wall -MD
LDFLAGS :=
# Output Directories
@ -42,7 +45,7 @@ FNT := $(BIN)/fonts
FIN := font/in
# Include Paths
INC := ../include include 3rdparty 3rdparty/pthreads/include
INC := include 3rdparty 3rdparty/pthreads/include roo_e
# Font Compiler Source and Target - NOTE: This is a Linux binary!
FONT_ELF := font
@ -50,17 +53,11 @@ FONT_SRC := $(shell find font/src -name '*.c')
FONT_OBJ := $(FONT_SRC:%=$(OBJ)/%.o)
FONT_LIB := -lm
# Roo/E Dynamic Libraries
DYNS_EXE := stbimage.dyn
DYNS_SRC := $(shell find dyn/stbimage -name '*.c')
DYNS_OBJ := $(DYNS_SRC:%=$(OBJ)/%.o)
DYNS_LIB :=
# Roo/E Source and Target
ROOE_EXE := roo_e.exe
ROOE_SRC := $(shell find roo_e -name '*.c')
ROOE_OBJ := $(ROOE_SRC:%=$(OBJ)/%.o)
ROOE_LIB := 3rdparty/pthreads/lib/libgthreads.a -lgcc
ROOE_LIB := 3rdparty/pthreads/lib/libgthreads.a -lgcc -lm
# MultiPlayer Game Client
MPGC_EXE := kpsmpgc.app
@ -68,13 +65,17 @@ MPGC_SRC := $(shell find kpsmpgc -name '*.c')
MPGC_OBJ := $(MPGC_SRC:%=$(OBJ)/%.o)
MPGC_LIB :=
FONTS := $(FNT)/vga4x8.fnt $(FNT)/vga8x8.fnt $(FNT)/vga8x14.fnt $(FNT)/vga8x16.fnt
DYNS := kpsvideo stbimage
FONTS := vga4x8 vga8x8 vga8x14 vga8x16
# Wiring
FONT_PNGS := $(subst $(FNT),$(FIN),$(FONTS))
FONT_PNGS := $(subst .fnt,.png,$(FONTS_PNGS))
INC_FLAGS := $(addprefix -I,$(INC))
CFLAGS += $(INC_FLAGS)
APP_OBJS := $(ROOE_OBJ) $(MPGC_OBJ)
FONTS := $(addprefix $(FNT)/,$(addsuffix .fnt,$(FONTS)))
FONT_PNGS := $(subst .fnt,.png,$(subst $(FNT),$(FIN),$(FONTS)))
DYN_FILES := $(foreach DIR,$(DYNS),$(DYN)/$(DIR).dyn)
DYN_SOURCE := $(foreach DIR,$(DYNS),$(shell find dyn/$(DIR) -name '*.c' -or -name '*.h'))
# Force User to Run This Using Toolchains
.PHONY: use_build_script
@ -83,7 +84,7 @@ use_build_script:
# Build Everything
.PHONY: dos
dos: $(BIN)/$(ROOE_EXE) $(APP)/$(MPGC_EXE) $(DYN)/$(DYNS_EXE)
dos: $(BIN)/$(ROOE_EXE) $(APP)/$(MPGC_EXE) $(DYN_FILES) sdk_extra
.PHONY: linux
linux: $(FONTS)
@ -98,7 +99,7 @@ $(BIN)/$(FONT_ELF): $(FONT_OBJ) Makefile
mkdir -p $(dir $@) $(BIN)/fonts
$(CC) $(FONT_OBJ) -o $@ $(LDFLAGS) $(FONT_LIB)
$(FONTS) &: $(FIN)/vga4x8.png $(FIN)/vga4x8.png $(FIN)/vga4x8.png $(FIN)/vga4x8.png $(BIN)/$(FONT_ELF)
$(FONTS) &: $(FONT_PNGS) $(BIN)/$(FONT_ELF)
cd $(BIN) && ./$(FONT_ELF)
# Roo/E Target
@ -115,15 +116,27 @@ $(APP)/$(MPGC_EXE): $(MPGC_OBJ) Makefile
mkdir -p $(dir $@)
$(DXE3GEN) -o $@ $(MPGC_OBJ) -U $(LDFLAGS) $(MPGC_LIB)
# DYNS Target
$(DYN)/$(DYNS_EXE): $(DYNS_OBJ) Makefile
mkdir -p $(dir $@)
$(AR) rcs $(OBJ)/$(DYNS_EXE).a $(DYNS_OBJ)
$(DXE3GEN) -o $@ -Y $@.a --whole-archive -U $(OBJ)/$(DYNS_EXE).a $(LDFLAGS) $(DYNS_LIB)
mkdir -p $(SDK)
mv $(DYN)/$(DYNS_EXE).a $(SDK)/.
# DYN Targets - This is NOT how make should be used.
.PHONY: dyns
$(DYN_FILES): $(DYN_SOURCE) Makefile
echo $(DYN_SOURCE)
mkdir -p $(DYN) $(OBJ)/dyn/$(basename $(notdir $@)) $(SDK)/lib $(SDK)/include
$(foreach CFILE,$(shell find dyn/$(basename $(notdir $@)) -name '*.c'),$(CC) $(CFLAGS) -o $(OBJ)/dyn/$(basename $(notdir $@))/$(subst dyn/$(basename $(notdir $@))/,,$(CFILE).o) -c $(CFILE) && ) true
$(AR) rcs $(OBJ)/dyn/$(basename $(notdir $@)).a $(OBJ)/dyn/$(basename $(notdir $@))/*.o
$(DXE3GEN) -o $@ -Y $@.a --whole-archive -U $(OBJ)/dyn/$(basename $(notdir $@)).a $(LDFLAGS)
mv $@.a $(SDK)/lib/.
if [ ! -z "$(shell find dyn/$(basename $(notdir $@)) -name '*.h')" ]; then cp $(shell find dyn/$(basename $(notdir $@)) -name '*.h') $(SDK)/include/.; fi
# Extra SDK Files
.PHONEY: sdk_extra
sdk_extra: 3rdparty/stbds.h 3rdparty/stbimage.h Makefile
cp 3rdparty/stbds.h 3rdparty/stbimage.h ${SDK}/include/.
cp roo_e/*.h ${SDK}/include/.
# Build C Files
$(OBJ)/%.c.o: %.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
# Track Header Changes in APPs and Roo/E
-include $(APP_OBJS:.o=.d)

37
dyn/kpsvideo/kpsvideo.c Normal file
View file

@ -0,0 +1,37 @@
/* Roo/E, the Kangaroo Punch Portable GUI Toolkit
* Copyright (C) 2026 Scott Duensing
*
* http://kangaroopunch.com
*
*
* This file is part of Roo/E.
*
* Roo/E is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Roo/E 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 General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Roo/E. If not, see <https://www.gnu.org/licenses/>.
*/
#include "roo_e.h"
#include "kpsvideo.h"
int dynStart(void) {
printf("kpsvideo starting!\n");
return 0;
}
int dynStop(void) {
printf("kpsvideo stopping!\n");
return 0;
}

27
dyn/kpsvideo/kpsvideo.h Normal file
View file

@ -0,0 +1,27 @@
/* Roo/E, the Kangaroo Punch Portable GUI Toolkit
* Copyright (C) 2026 Scott Duensing
*
* http://kangaroopunch.com
*
*
* This file is part of Roo/E.
*
* Roo/E is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Roo/E 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 General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Roo/E. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef KPSVIDEO_H
#define KPSVIDEO_H
#endif // KPSVIDEO_H

View file

@ -21,10 +21,10 @@
*/
#include <stdio.h>
#include "roo_e.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "stbimage.h"
int dynStart(void) {

2
env.sh
View file

@ -25,4 +25,4 @@
eval "$(../toolchains/toolchains.sh use x86 dos)"
export C_INCLUDE_PATH=${HOME}/code/toolchains/x-tools/djgpp/i586-pc-msdosdjgpp/sys-include
export C_INCLUDE_PATH=${HOME}/code/toolchains/x-tools/djgpp/i586-pc-msdosdjgpp/sys-include:roo_e

View file

@ -30,7 +30,7 @@
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#include "stb_image.h"
#include "stbimage.h"
#include "stddclmr.h"

View file

@ -21,16 +21,16 @@
*/
#include <stdio.h>
#include "stb_image.h"
#include "roo_e.h"
#include "stbimage.h"
void __attribute__((constructor)) registerApp(void) {
ROO_CONSTRUCTOR registerApp(void) {
printf("App Loaded!\n");
}
void __attribute__((destructor)) unregisterApp(void) {
ROO_DESTRUCTOR unregisterApp(void) {
printf("App Unloaded!\n");
}
@ -43,3 +43,9 @@ int appMain(const int argc, char* argv[]) {
printf("stbi_failure_reason: [%s]\n", stbi_failure_reason());
return 0;
}
int appStop(RooStopReasonT why) {
printf("App Stopped!\n");
return ROO_ABORT_NONE;
}

View file

@ -22,22 +22,11 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <dirent.h>
#include <math.h>
#include "pthread.h"
// ReSharper disable once CppUnusedIncludeDirective
#include "stddclmr.h"
#include "util.h"
#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"
#include "roo_e.h"
int startApp(const char *appName, int argc, char *argv[]);
static RooNamedPointerT **_appList = NULL; // NOLINT
#ifdef PLATFORM_DOS
@ -118,55 +107,83 @@ void *dxeResolver(const char *symbol) {
void *to;
} funcPtrCast;
printf("%s: undefined symbol in dynamic module!\n", symbol);
printf("ROO/E: %s: undefined symbol in dynamic module!\n", symbol);
funcPtrCast.from = lastResort;
return funcPtrCast.to; // NOLINT
}
static int lastResort(void) {
printf("Last resort function called!\n");
printf("ROO/E: Last resort function called!\n");
return 0;
}
#endif // PLATFORM_DOS
int startApp(const char *appName, const int argc, char *argv[]) {
void *dxe = NULL;
int rooStartApp(const char *appName, const int argc, char *argv[], RooNamedPointerT **handle) {
RooNamedPointerT *namedPointer = NULL;
int result = -1;
char *filename = NULL;
int (*appMain)(const int argc, char *argv[]);
int (*appMain)(const int argc, char *argv[]); // NOLINT
union {
void *from;
int (*to)(const int argc, char *argv[]);
} appPtrCast;
} appPtrCast; // NOLINT
dlerror(); // Clear any existing error.
filename = utilCreateString("app/%s.app", appName);
dxe = dlopen(filename, RTLD_LAZY);
if (!dxe) {
printf("Unable to load %s! %s\n", filename, dlerror());
NEW(RooNamedPointerT, namedPointer);
namedPointer->name = utilCreateString("app/%s.app", appName);
namedPointer->pointer = dlopen(namedPointer->name, RTLD_LAZY);
if (!namedPointer->pointer) {
printf("ROO/E: Unable to load %s! %s\n", namedPointer->name, dlerror());
} else {
appPtrCast.from = dlsym(dxe, "_appMain");
appPtrCast.from = dlsym(namedPointer->pointer, "_appMain");
appMain = appPtrCast.to; // NOLINT
result = appMain(argc, argv);
arrput(_appList, namedPointer);
if (handle != NULL) *handle = namedPointer->pointer;
}
dlclose(dxe);
return result;
}
RooAbortReasonT rooStopApp(RooNamedPointerT **appHandle, RooStopReasonT reason) {
RooAbortReasonT abort = ROO_ABORT_NONE;
RooNamedPointerT *namedPointer = NULL;
int (*appStop)(RooStopReasonT reason);
union {
void *from;
int (*to)(RooStopReasonT reason);
} appPtrCast; // NOLINT
namedPointer = *appHandle;
appPtrCast.from = dlsym(namedPointer->pointer, "_appStop");
if (appPtrCast.from != NULL) {
appStop = appPtrCast.to; // NOLINT
abort = appStop(reason);
}
if (abort == ROO_ABORT_NONE) {
dlclose(namedPointer->pointer);
DEL(namedPointer->name);
DEL(namedPointer);
}
return abort;
}
int main(const int argc, char *argv[]) {
void *dxe = NULL;
DIR *dir = NULL;
struct dirent *dirent = NULL;
char *filename = NULL;
int (*dynInit)(void);
int result;
RooAbortReasonT abort;
RooNamedPointerT **dynList = NULL;
RooNamedPointerT *namedPointer = NULL;
int (*dynInitStop)(void);
union {
void *from;
@ -184,40 +201,80 @@ int main(const int argc, char *argv[]) {
#endif // PLATFORM_DOS
// Load libraries.
debug("Loading dynamic libraries.\n");
debug("ROO/E: Loading dynamic libraries.\n");
if ((dir = opendir("dyn/")) == NULL) {
printf("Unable to open dyn directory!\n");
printf("ROO/E: Unable to open dyn directory!\n");
exit(1);
}
while ((dirent = readdir(dir))) {
if ((dirent->d_type == DT_REG) || (dirent->d_type == DT_LNK)) {
if (utilEndsWith(dirent->d_name, ".dyn", false)) {
filename = utilCreateString("dyn/%s", dirent->d_name);
NEW(RooNamedPointerT, namedPointer);
namedPointer->name = utilCreateString("dyn/%s", dirent->d_name);
dlerror(); // Clear any existing error.
dxe = dlopen(filename,RTLD_LAZY | RTLD_GLOBAL);
if (!dxe) {
printf("Unable to load %s! %s\n", filename, dlerror());
namedPointer->pointer = dlopen(namedPointer->name,RTLD_LAZY | RTLD_GLOBAL);
if (!namedPointer->pointer) {
printf("ROO/E: Unable to load %s! %s\n", namedPointer->name, dlerror());
exit(1);
}
debug("Loaded %s.\n", filename);
dynPtrCast.from = dlsym(dxe, "_dynStart");
debug("ROO/E: Loaded %s.\n", namedPointer->name);
dynPtrCast.from = dlsym(namedPointer->pointer, "_dynStart");
if (dynPtrCast.from != NULL) {
dynInit = dynPtrCast.to; // NOLINT
debug("Starting %s.\n", filename);
result = dynInit();
dynInitStop = dynPtrCast.to; // NOLINT
debug("ROO/E: Starting %s.\n", namedPointer->name);
result = dynInitStop();
if (result != 0) {
printf("%s failed to start!\n", filename);
printf("ROO/E: %s failed to start!\n", namedPointer->name);
exit(1);
}
}
free(filename);
arrput(dynList, namedPointer);
}
}
}
closedir(dir);
// Load the Shell.
result = startApp("kpsmpgc", 0, NULL);
debug("ROO/E: Loaded %d libraries.\n", arrlen(dynList));
// Load the Shell. This needs to be configurable.
result = rooStartApp("kpsmpgc", 0, NULL, NULL);
// Run as long as apps are running.
while (arrlen(_appList) > 0) {
// Wait for all apps to exit.
// Unload APPs.
while (arrlen(_appList) > 0) {
namedPointer = _appList[0]; // NOLINT
debug("ROO/E: Stopping %s\n", namedPointer->name);
abort = rooStopApp(&namedPointer, ROO_STOP_SHUTDOWN);
if (abort == ROO_ABORT_NONE) {
arrdel(_appList, 0);
} else {
break; // Somebody didn't want to stop.
}
}
}
// Unload DYNs
debug("ROO/E: Unloading %d libraries.\n", arrlen(dynList));
while (arrlen(dynList) > 0) {
namedPointer = dynList[0]; // NOLINT
debug("ROO/E: Stopping %s\n", namedPointer->name);
dynPtrCast.from = dlsym(namedPointer->pointer, "_dynStop");
if (dynPtrCast.from != NULL) {
dynInitStop = dynPtrCast.to; // NOLINT
result = dynInitStop();
if (result != 0) {
printf("ROO/E: %s failed to stop!\n", namedPointer->name);
exit(1);
}
}
arrdel(dynList, 0);
dlclose(namedPointer->pointer);
DEL(namedPointer->name);
DEL(namedPointer);
}
return result;
}

71
roo_e/roo_e.h Normal file
View file

@ -0,0 +1,71 @@
/*
* Roo/E, the Kangaroo Punch Portable GUI Toolkit
* Copyright (C) 2026 Scott Duensing
*
* http://kangaroopunch.com
*
*
* This file is part of Roo/E.
*
* Roo/E is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Roo/E 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 General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Roo/E. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ROO_E_H
#define ROO_E_H
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <dirent.h>
#include <math.h>
#include "pthread.h"
#include "stbds.h"
#include "util.h"
#include "stddclmr.h"
#define ROO_CONSTRUCTOR void __attribute__((constructor))
#define ROO_DESTRUCTOR void __attribute__((destructor))
// Allocation helpers. ***TODO*** Check for failure.
#define NEW(t,v) (v)=(t*)malloc(sizeof(t))
#define DEL(v) {if(v) {free(v); v=NULL;}}
typedef struct RooNamedPointerS {
char *name;
void *pointer;
} RooNamedPointerT;
typedef enum {
ROO_STOP_EXITING = 0, // App quit
ROO_STOP_SHUTDOWN, // Roo/E shutdown
ROO_STOP_COUNT
} RooStopReasonT;
typedef enum {
ROO_ABORT_NONE = 0, // Don't abort stopping the app
ROO_ABORT_CANCEL, // Try to keep the app alive
ROO_ABORT_COUNT
} RooAbortReasonT;
int rooStartApp(const char *appName, int argc, char *argv[], RooNamedPointerT **handle);
RooAbortReasonT rooStopApp(RooNamedPointerT **appHandle, RooStopReasonT reason);
#endif // ROO_E_H