prepare launcher for UGC

This commit is contained in:
Ondrej Novak 2025-07-15 17:37:00 +02:00
parent 86d0919905
commit 4ed39ec344
11 changed files with 342 additions and 121 deletions

View file

@ -18,10 +18,11 @@
#include <ctype.h>
#include <libs/gui.h>
#include <libs/basicobj.h>
#include <libs/strlists.h>
#include <time.h>
#include <libs/mgfplay.h>
#include <libs/wav.h>
#include <fcntl.h>
#include <platform/ugc.h>
#include "globals.h"
#include "engine1.h"
#include <stdarg.h>
@ -1627,81 +1628,90 @@ char ask_save_dialog(char *name_buffer, size_t name_size, char allow_remove) {
}
#if 0
//----------------- JRC LOGO ----------------------------------
static char *launcher_ddl_file = NULL;
static void free_ddl_file_name(void) {
free(launcher_ddl_file);
}
#define SHOWDELAY 125
#define SHOWDEND (SHOWDELAY-32)
const char *run_launcher() {
const char *str_label = texty[198];
TSTR_LIST lst = create_list(100);
TSTR_LIST ddl_lst = create_list(100);
CTL3D ctl = {0,0,4,0};
int selected = 0;
size_t item_count = 1;
typedef struct _hicolpal
{
unsigned blue:5;
unsigned green:5;
unsigned red:5;
}HICOLPAL;
void show_jrc_logo(char *filename)
{
char *pcx;word *pcxw;
char bnk=1;
int xp,yp,i;
word palette[256],*palw;
int cntr,cdiff,cpalf,ccc;
change_music(NULL);
curcolor=0;bar32(0,0,639,479);
showview(0,0,0,0);sleep_ms(1000);
const char *s = build_pathname(2, gpathtable[SR_VIDEO],filename);
if (open_pcx(s,A_8BIT,&pcx)) return;
pcxw=(word *)pcx;
xp=pcxw[0];
yp=pcxw[1];
palw=pcxw+3;
memcpy(palette,palw,256*sizeof(word));
memset(palw,0,256*sizeof(word));
xp/=2;yp/=2;xp=320-xp;yp=240-yp;
cntr=get_timer_value();ccc=0;
do
{
cdiff=(get_timer_value()-cntr)/2;
if (cdiff<SHOWDEND && ccc!=cdiff)
{
cpalf=cdiff;
if (cpalf<32)
for (i=0;i<256;i++)
{
int r=(cpalf<<11),g=(cpalf<<6),b=cpalf,k;
k=palette[i] & 0xF800;if (k>r) palw[i]=r;else palw[i]=k;
k=palette[i] & 0x7e0;if (k>g) palw[i]|=g;else palw[i]|=k;
k=palette[i] & 0x1f;if (k>b) palw[i]|=b;else palw[i]|=k;
UGCManager *ugc = UGC_create();
size_t ugccount = UGC_Fetch(ugc);;
for (size_t i = 0; i < ugccount; ++i) {
UGCItem item = UGC_GetItem(ugc, i);
char buff[60];
const char *title = item.name;
size_t tlen = strlen(title);
const char *author = item.author;
size_t alen = strlen(author);
size_t reserve = sizeof(buff)-4;
char d1 = 0;
char d2 = 0;
if (tlen + alen > reserve) {
if (alen < reserve/2) {tlen = reserve - alen - 3;d1 = 1;}
else if (tlen < reserve/2) {alen = reserve - tlen - 3;d2=1;}
else {
tlen = reserve/2-3; d1 = 1;
alen = reserve/2-3; d2 = 1;
}
}
char *iter = buff;
for (size_t i = 0; i < tlen; ++i) *iter++ = title[i];
if (d1) for (size_t i = 0; i < 3; ++i) *iter++='.';
memcpy(iter, " - ",3); iter+=3;
for (size_t i = 0; i < alen; ++i) *iter++ = author[i];
if (d2) for (size_t i = 0; i < 3; ++i) *iter++='.';
*iter = 0;
str_add(&lst, buff);
str_add(&ddl_lst, item.ddl_path);
++item_count;
}
else if (ccc!=cdiff)
{
cpalf=SHOWDELAY-cdiff;
if (cpalf<32)
for (i=0;i<256;i++)
{
int r,g,b,k=32-cpalf;
b=palette[i];g=b>>5;b&=0x1f;r=g>>6;g&=0x1f;
b-=k;r-=k;g-=k;
if (b<0) b=0;
if (r<0) r=0;
if (g<0) g=0;
palw[i]=b | (r<<11) | (g<<6);
}
str_add(&lst, texty[199]);
str_add(&ddl_lst, "");
curcolor = RGB555(0,0,0);
set_font(H_FBIG,RGB555_ALPHA(31,31,31));
add_window(120,60,400,300,H_WINTXTR,3,20,20);
define(-1,20,10,1,1,0,label,str_label);
set_font(H_FKNIHA,RGB555_ALPHA(31,31,31));
define(9,15,38,335,212,0,&listbox,lst,RGB555(16,16,16),0);
property(&ctl,NULL,NULL,RGB555(0,0,0));c_default(0);
if (item_count>19) {
define(10,355,38,20,212,0,scroll_bar_v,0,item_count-19,19,RGB555(8,8,8));
property(&ctl,NULL,NULL,RGB555(10,10,10));
}
put_picture(xp, yp, pcx);
if (bnk) {
showview(xp, yp, pcxw[0], pcxw[1]);
}
ccc=cdiff;
mix_back_sound(0);
}
while (cdiff<SHOWDELAY && !_bios_keybrd(_KEYBRD_READY));
curcolor=0;bar32(0,0,639,479);
showview(0,0,0,0);
free(pcx);
}
#endif
define(20,20,20,60,20,2,button,texty[43]);property(def_border(5,BAR_COLOR),NULL,NULL,BAR_COLOR);on_control_change(terminate_gui);
define(30,90,20,60,20,2,button,texty[80]);property(def_border(5,BAR_COLOR),NULL,NULL,BAR_COLOR);on_control_change(terminate_gui);
redraw_window();
send_message(E_ADD,E_KEYBOARD,save_dialog_keyboards);
send_message(E_ADD,E_MOUSE,save_dialog_keyboards);
escape();
send_message(E_DONE,E_KEYBOARD,save_dialog_keyboards);
send_message(E_DONE,E_MOUSE,save_dialog_keyboards);
int butt = o_aktual->id;
get_value(0,9,&selected);
char *selddl = strdup(ddl_lst[selected]);
release_list(lst);
release_list(ddl_lst);
close_current();
if (butt != 30 || selddl == NULL || selddl[0] == 0) {
free(selddl);
UGC_Destroy(ugc);
return butt != 30?NULL:"";
}
launcher_ddl_file = selddl;
UGC_StartPlay(ugc, selected);
UGC_Destroy(ugc);
atexit(&free_ddl_file_name);
return launcher_ddl_file;
}

View file

@ -16,6 +16,7 @@
#include <libs/mgfplay.h>
#include <libs/inicfg.h>
#include <platform/save_folder.h>
#include <platform/ugc.h>
#include "globals.h"
#include "resources.h"
//
@ -825,6 +826,8 @@ void cti_texty(void)
//patch stringtable
if (!texty[98]) str_replace(&texty,98,"Ulo\x91it hru jako");
if (!texty[99]) str_replace(&texty,99,"CRT Filter (>720p)");
if (!texty[198]) str_replace(&texty,198,"Zvol dobrodru\x91stv\xA1");
if (!texty[199]) str_replace(&texty,199,"Br\xA0ny Skeldalu (p\x96vodn\xA1 dobrodru\x91stv\xA1)");
str_replace(&texty, 144, "Zrychlit souboje");
str_replace(&texty, 51, "Celkov\x88 Hudba Efekty V\x98\xA8ky Basy Rychlost");
str_replace(&texty,0,"Byl nalezen p\xA9ipojen\x98 ovlada\x87\nPro aktivaci ovlada\x87""e stiskn\x88te kter\x82koliv tla\x87\xA1tko na ovlada\x87i");
@ -888,14 +891,17 @@ const void *boldcz;
void init_DDL_manager() {
const char *ddlfile = build_pathname(2, gpathtable[SR_DATA],"SKELDAL.DDL");
ddlfile = local_strdup(ddlfile);
ddlfile = local_strdup(ddlfile);
init_manager();
for (size_t sz = countof(patch_files); sz > 0;) {
--sz;
if (patch_files[sz]) {
if (!add_patch_file(patch_files[sz])) {
display_error("Can't open resource file (adv_patch): %s", patch_files[sz]);
if (!add_patch_file(ddlfile)) {
display_error("Can't open resource file (main): %s", ddlfile);
abort();
}
for (size_t i = 0; i <= countof(patch_files); ++i) {
if (patch_files[i]) {
if (!add_patch_file(patch_files[i])) {
display_error("Can't open resource file (adv_patch): %s", patch_files[i]);
abort();
}
}
@ -906,10 +912,6 @@ void init_DDL_manager() {
gfx = local_strdup(gfx);
add_patch_file(gfx);
}
if (!add_patch_file(ddlfile)) {
display_error("Can't open resource file (main): %s", ddlfile);
abort();
}
SEND_LOG("(GAME) Memory manager initialized. Using DDL: '%s'",ddlfile);
@ -1196,22 +1198,8 @@ void enter_game(void)
}
static int update_config(void)
{
return 0;
}
void help(void)
{
printf("Pouziti:\n\n S <filename.MAP> <start_sector>\n\n"
"<filename.MAP> jmeno mapy\n"
"<start_sector> Cislo startovaciho sektoru\n"
);
exit(0);
}
extern char nofloors;
/*
@ -1496,11 +1484,6 @@ static void start_from_mapedit(va_list args)
}
#endif
void disable_intro(void)
{
add_field_num(&cur_config,sinit[12].heslo,1);
update_config();
}
/*
* -char def_path[]="";
@ -1560,6 +1543,8 @@ const char *configure_pathtable(const INI_CONFIG *cfg) {
if (ini_get_boolean(paths, "patch_mode", 0)) {
mman_patch = 1;
}
const char *ugc_path = ini_get_string(paths, "local_ugc", "adv");
UGCSetLocalFoler(ugc_path);
return groot;
}
@ -1610,6 +1595,7 @@ int skeldal_gen_string_table_entry_point(const SKELDAL_CONFIG *start_cfg, const
return 0;
}
const char *run_launcher();
int skeldal_entry_point(const SKELDAL_CONFIG *start_cfg)
{
def_mman_group_table(gpathtable);
@ -1665,21 +1651,29 @@ int skeldal_entry_point(const SKELDAL_CONFIG *start_cfg)
init_skeldal(cfg);
if (start_cfg->launcher) {
const char *ddl = run_launcher();
if (ddl==NULL) goto cleanup;
if (ddl[0]) {
add_patch_file(ddl);
reload_ddls();
}
}
int start_task = add_task(65536,start);
escape();
term_task_wait(start_task);
cleanup:;
closemode();
ini_close(cfg);
return 0;
}
#include "version.h"

View file

@ -12,7 +12,7 @@ SET(files basicobj.c
mgifplaya.c
pcx.c
wav_mem.c
strlite.c
strlists.c
cztable.c
minimp3.c
music.cpp

View file

@ -131,7 +131,7 @@ typedef struct ddlmap_info {
char *path;
} TDDLMAP_INFO;
#define MAX_PATCHES 4
#define MAX_PATCHES 8
static TDDLMAP_INFO ddlmap[MAX_PATCHES];
@ -201,7 +201,8 @@ char get_file_entry(int group,const char *name, THANDLE_DATA *h) {
ex=mman_patch && test_file_exist_DOS(group,name);
if (!ex) {
for (int i = 0; i < MAX_PATCHES; ++i) {
for (int i = MAX_PATCHES; i >0 ; ) {
--i;
const TDDLMAP_INFO *nfo = &ddlmap[i];
if (nfo->ptr) {
int sk = get_file_entry_in_table(&nfo->nametable, name);
@ -329,6 +330,7 @@ void reload_ddls(void) {
}
h->status = BK_NOT_LOADED;
h->blockdata = NULL;
get_file_entry(h->path,h->src_file,h);
}
}
}

View file

@ -275,16 +275,4 @@ void listbox(OBJREC *o)
//o->done=string_list_done;
}
/*void main()
{
TSTR_LIST test;
int i,j;
test=read_directory("c:\\windows\\system\\*.*",DIR_BREIF,_A_NORMAL);
j=str_count(test);
for(i=0;i<j;i++)
if (test[i]!=NULL) printf("%s\n",test[i]);
printf("%d souboru.\n",j);
release_list(test);
}
*/

View file

@ -13,6 +13,7 @@ target_sources(skeldal_platform PRIVATE
getopt.c
achievements.cpp
sse_receiver.c
ugc.cpp
)
set(all_libs

View file

@ -5,6 +5,7 @@
#include <map>
#include <string>
#include <string_view>
#include <filesystem>
typedef struct ini_config_section_tag {
using Section = std::map<std::string, std::string, std::less<>>;
@ -59,7 +60,8 @@ void parseIniStream(std::istream& input, Callback &&callback) {
INI_CONFIG* ini_open(const char *filename) {
std::fstream input(filename);
std::filesystem::path fname(reinterpret_cast<const char8_t *>(filename));
std::fstream input(fname);
if (!input) return NULL;
INI_CONFIG *c = new INI_CONFIG;
parseIniStream(input, [&](std::string_view section, std::string_view key, std::string_view value) {

190
platform/ugc.cpp Normal file
View file

@ -0,0 +1,190 @@
#include "ugc.h"
#include "config.h"
#include <fstream>
#include <vector>
#include <filesystem>
#include <string_view>
#include <memory>
std::wstring toWideChar(std::string_view text) {
unsigned int codepoint = 0;
unsigned int bytes = 0;
std::wstring out;
for (char c : text) {
if ((c & 0x80) == 0) out.push_back(c);
else {
if ((c & 0xC0) == 0x80) {
codepoint = (codepoint << 6) | (c & 0x3F);
} else if ((c & 0xE0) == 0xC0) {
bytes=2; codepoint = c & 0x1F;
} else if ((c & 0xF0) == 0xE0) {
bytes=3; codepoint = c & 0x0F;
} else if ((c & 0xF8) == 0xF0) {
bytes=4; codepoint = c & 0x07;
}
--bytes;
if (!bytes) out.push_back(static_cast<wchar_t>(codepoint));
}
}
return out;
}
constexpr std::pair<int,int> cztable[]={
{0x010C,'\x80'},
{0x00FC,'u'},
{0x00E9,'\x82'},
{0x010F,'\x83'},
{0x00E4,'a'},
{0x010E,'\x85'},
{0x0164,'\x86'},
{0x010D,'\x87'},
{0x011B,'\x88'},
{0x011A,'\x89'},
{0x0139,'L'},
{0x00CD,'\x8B'},
{0x013E,'l'},
{0x013A,'l'},
{0x00C4,'A'},
{0x00C1,'\x8F'},
{0x00C9,'\x90'},
{0x017E,'\x91'},
{0x017D,'\x92'},
{0x00F4,'o'},
{0x00F6,'o'},
{0x00D3,'\x95'},
{0x016F,'\x96'},
{0x00DA,'\x97'},
{0x00FD,'\x98'},
{0x00D6,'O'},
{0x00DC,'U'},
{0x0160,'\x9b'},
{0x013D,'L'},
{0x00DD,'\x9d'},
{0x0158,'\x9e'},
{0x0165,'\x9f'},
{0x00E1,'\xA0'},
{0x00ED,'\xA1'},
{0x00F3,'\xA2'},
{0x00FA,'\xA3'},
{0x0148,'\xA4'},
{0x0147,'\xA5'},
{0x016E,'\xA6'},
{0x00D4,'O'},
{0x0161,'\xA8'},
{0x0159,'\xA9'},
{0x0155,'r'},
{0x0154,'R'},
{0x00BC,'\xAC'},
{0x00A7,'\xAD'},
{0x00AB,'<'},
{0x00BB,'>'},
};
std::string toKEYBCS2(const char *text) {
auto wstr = toWideChar(text);
std::string out;
out.resize(wstr.size());
std::transform(wstr.begin(), wstr.end(), out.begin(), [](wchar_t c){
if (c <= 0x80) return static_cast<char>(c);
auto iter = std::find_if(std::begin(cztable), std::end(cztable), [c](const auto &x){return x.first == c;});
if (iter == std::end(cztable)) return '_';
return static_cast<char>(iter->second);
});
return out;
}
struct UGCItemEx : UGCItem {
std::unique_ptr<char[]> text_data;
std::filesystem::path _stamp_file;
};
struct tag_UGCManager {
std::vector<UGCItemEx> _list;
};
UGCManager *UGC_create() {
return new UGCManager;
}
void UGC_Destroy(UGCManager *inst) {
delete inst;
}
static std::filesystem::path ugc_local_path;
void UGCSetLocalFoler(const char *path) {
ugc_local_path = reinterpret_cast<const char8_t *>(path);
}
size_t UGC_Fetch(UGCManager *manager) {
manager->_list.clear();
std::error_code ec;
auto iter = std::filesystem::directory_iterator(ugc_local_path,ec);
if (ec == std::error_code()) {
auto fend = std::filesystem::directory_iterator();
while (iter != fend) {
const auto &entry = *iter;
if (entry.is_directory()) {
auto entry_path =std::filesystem::weakly_canonical(entry.path()) ;
auto info_path = entry_path / "content.ini";
if (std::filesystem::is_regular_file(info_path)) {
INI_CONFIG *cfg = ini_open(reinterpret_cast<const char *>(info_path.u8string().c_str()));
if (cfg) {
const INI_CONFIG_SECTION *section = ini_section_open(cfg, "description");
std::string title = toKEYBCS2(ini_get_string(section, "title", NULL));
std::string author = toKEYBCS2(ini_get_string(section, "author", "unknown author"));
const INI_CONFIG_SECTION *files = ini_section_open(cfg, "files");
const char *ddl = ini_get_string(files, "ddlfile", NULL);
if (ddl && !title.empty()) {
UGCItemEx r;
std::filesystem::path ddlpath = entry_path / ddl;
auto pstr = ddlpath.u8string();
ddl = reinterpret_cast<const char *>(pstr.c_str());
auto tlen = title.size()+1;
auto alen = author.size()+1;
auto dlen = strlen(ddl)+1;
r.text_data = std::make_unique<char[]>(tlen+alen+dlen);
char *c = r.text_data.get();
memcpy(c, title.c_str(), tlen);r.name = c;c+=tlen;
memcpy(c, author.c_str(), alen);r.author = c;c+=alen;
memcpy(c, ddl, dlen);r.ddl_path= c;c+=alen;
auto stampfile = entry_path / "stamp";
std::filesystem::file_time_type tp = std::filesystem::last_write_time(stampfile, ec);
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(tp - std::filesystem::file_time_type::clock::now()
+ std::chrono::system_clock::now());
r.last_played = std::chrono::system_clock::to_time_t(sctp);
r._stamp_file = std::move(stampfile);
manager->_list.push_back(std::move(r));
}
ini_close(cfg);
}
}
}
++iter;
}
}
std::sort(manager->_list.begin(), manager->_list.end(), [](const UGCItem &a, const UGCItem &b){
return a.last_played > b.last_played;
});
return manager->_list.size();
}
UGCItem UGC_GetItem(UGCManager *manager, size_t pos) {
return manager->_list[pos];
}
void UGC_StartPlay(UGCManager *manager, size_t pos) {
std::ofstream out(manager->_list[pos]._stamp_file, std::ios::out|std::ios::trunc);
}

29
platform/ugc.h Normal file
View file

@ -0,0 +1,29 @@
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
const char *name;
const char *ddl_path;
const char *author;
time_t last_played;
} UGCItem;
typedef struct tag_UGCManager UGCManager;
void UGCSetLocalFoler(const char *path);
UGCManager *UGC_create();
size_t UGC_Fetch(UGCManager *manager);
UGCItem UGC_GetItem(UGCManager *manager, size_t pos);
void UGC_StartPlay(UGCManager *manager, size_t pos);
void UGC_Destroy(UGCManager *inst);
#ifdef __cplusplus
}
#endif

View file

@ -10,6 +10,7 @@ extern "C" {
#include <fcntl.h>
#include <sys/stat.h>
#include <stdexcept>
#include <filesystem>
// Funkce pro mapování souboru do paměti
const void* map_file_to_memory_cpp(const char *name, size_t *sz) {
@ -17,8 +18,10 @@ const void* map_file_to_memory_cpp(const char *name, size_t *sz) {
return NULL;
}
HANDLE h = CreateFileA(name, GENERIC_READ, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (h == INVALID_HANDLE_VALUE) throw std::runtime_error(std::string("Failed to open file for mapping: ").append(name));
std::filesystem::path p(reinterpret_cast<const char8_t *>(name));
HANDLE h = CreateFileW(p.c_str(), GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (h == INVALID_HANDLE_VALUE) throw std::runtime_error(std::string("Failed to open file for mapping: ")+p.string());
LARGE_INTEGER fsize;
if (!GetFileSizeEx(h, &fsize)) {

View file

@ -6,6 +6,7 @@
# data = relative path to skeldal.ddl
# language = path language folders
# savegame = path to savegame, if not defined, retrieved from platform settings
# local_ugc = path to local repository for user generated content (if team is enabled it also uses steam workshop path)
[paths]
@ -15,6 +16,7 @@
# data=./
# language=./lang
# savegame = determine default
# local_ugc=./adv
#### video settings
#