diff --git a/game/interfac.c b/game/interfac.c index 286d7d4..c83eafd 100644 --- a/game/interfac.c +++ b/game/interfac.c @@ -18,10 +18,11 @@ #include #include #include +#include #include #include #include -#include +#include #include "globals.h" #include "engine1.h" #include @@ -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 (cdiffr) 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 (cdiffid; + 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; +} \ No newline at end of file diff --git a/game/skeldal.c b/game/skeldal.c index 305725e..e78fc3a 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -16,6 +16,7 @@ #include #include #include +#include #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 \n\n" - " jmeno mapy\n" - " 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" diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index f946a37..c3cb3d0 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -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 diff --git a/libs/memman.c b/libs/memman.c index 056e120..7f6ae08 100644 --- a/libs/memman.c +++ b/libs/memman.c @@ -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); } } } diff --git a/libs/strlists.c b/libs/strlists.c index 93fcff1..ba8891d 100644 --- a/libs/strlists.c +++ b/libs/strlists.c @@ -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 #include #include +#include typedef struct ini_config_section_tag { using Section = std::map>; @@ -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(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) { diff --git a/platform/ugc.cpp b/platform/ugc.cpp new file mode 100644 index 0000000..1ccf0b9 --- /dev/null +++ b/platform/ugc.cpp @@ -0,0 +1,190 @@ +#include "ugc.h" +#include "config.h" +#include +#include +#include +#include + + +#include + +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(codepoint)); + } + } + return out; +} + +constexpr std::pair 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(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(iter->second); + }); + return out; +} + + +struct UGCItemEx : UGCItem { + std::unique_ptr text_data; + std::filesystem::path _stamp_file; +}; + +struct tag_UGCManager { + std::vector _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(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(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(pstr.c_str()); + + auto tlen = title.size()+1; + auto alen = author.size()+1; + auto dlen = strlen(ddl)+1; + + r.text_data = std::make_unique(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(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); +} + + + diff --git a/platform/ugc.h b/platform/ugc.h new file mode 100644 index 0000000..bff2cce --- /dev/null +++ b/platform/ugc.h @@ -0,0 +1,29 @@ +#include + +#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 diff --git a/platform/windows/map_file.cpp b/platform/windows/map_file.cpp index 4f43e7a..e40fbcd 100644 --- a/platform/windows/map_file.cpp +++ b/platform/windows/map_file.cpp @@ -10,6 +10,7 @@ extern "C" { #include #include #include +#include // 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(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)) { diff --git a/skeldal.ini b/skeldal.ini index c49ddbe..a6ecaa3 100644 --- a/skeldal.ini +++ b/skeldal.ini @@ -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 #