├── my.rc ├── 1616holo.ico ├── holocure.png ├── exec_bin.c ├── deploy.sh ├── README.md ├── versions.xml ├── test.c └── main.c /my.rc: -------------------------------------------------------------------------------- 1 | id ICON "1616holo.ico" -------------------------------------------------------------------------------- /1616holo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khronii/HoloCure-Translation-Launcher/HEAD/1616holo.ico -------------------------------------------------------------------------------- /holocure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khronii/HoloCure-Translation-Launcher/HEAD/holocure.png -------------------------------------------------------------------------------- /exec_bin.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | system ("cd /D .\\bin && start \"\" \".\\HoloCureKR.exe\""); 6 | } 7 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | if [ "$#" -ne 2 ]; 4 | then 5 | printf "Usage: ./deploy.sh exe directory\n" 6 | fi 7 | 8 | list=$(ldd $1 | grep /mingw64 | sed 's/.dll.*/.dll/') 9 | for dll in $list; 10 | do 11 | pkg=`pacman -Qo $dll | sed 's/.* is owned by //' | tr ' ' '-'` 12 | pkglist="$pkglist $pkg" 13 | done 14 | # remove duplicates 15 | pkglist=`echo $pkglist | tr ' ' '\n' | sort | uniq` 16 | printf "$pkglist\n" 17 | 18 | mkdir -p "$2" 19 | 20 | for pkg in $pkglist 21 | do 22 | tmp=`mktemp -d` 23 | cd $tmp 24 | if [ -f /var/cache/pacman/pkg/$pkg-any.pkg.tar.xz ]; then 25 | tar -xf /var/cache/pacman/pkg/$pkg-any.pkg.tar.xz 26 | fi 27 | if [ -f /var/cache/pacman/pkg/$pkg-any.pkg.tar.zst ]; then 28 | tar --use-compress-program=unzstd -xf /var/cache/pacman/pkg/$pkg-any.pkg.tar.zst 29 | fi 30 | # more fine-grained control is possible here 31 | cp -r $PWD/mingw64/bin $2 32 | cp -r $PWD/mingw64/share $2 33 | done 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## git에 포함된 외부 리소스 4 | 5 | * [로고 한글화](https://twitter.com/rito__321/status/1544307080567214080) 6 | * [deploy.sh](https://sourceforge.net/p/msys2/tickets/34/#1cb7) 7 | 8 | ## 패키지에 포함된 외부 리소스 9 | 10 | * [unzip](https://gnuwin32.sourceforge.net/packages/unzip.htm) 11 | * mingw-w64-x86_64-atk 12 | * mingw-w64-x86_64-brotli 13 | * mingw-w64-x86_64-bzip2 14 | * mingw-w64-x86_64-cairo 15 | * mingw-w64-x86_64-expat 16 | * mingw-w64-x86_64-fontconfig 17 | * mingw-w64-x86_64-freetype 18 | * mingw-w64-x86_64-fribidi 19 | * mingw-w64-x86_64-gcc-libs 20 | * mingw-w64-x86_64-gdk-pixbuf2 21 | * mingw-w64-x86_64-gettext 22 | * mingw-w64-x86_64-glib2 23 | * mingw-w64-x86_64-graphite2 24 | * mingw-w64-x86_64-gtk3 25 | * mingw-w64-x86_64-harfbuzz 26 | * mingw-w64-x86_64-libdatrie 27 | * mingw-w64-x86_64-libepoxy 28 | * mingw-w64-x86_64-libffi 29 | * mingw-w64-x86_64-libiconv 30 | * mingw-w64-x86_64-libpng 31 | * mingw-w64-x86_64-libthai 32 | * mingw-w64-x86_64-libwinpthread 33 | * mingw-w64-x86_64-pango 34 | * mingw-w64-x86_64-pcre 35 | * mingw-w64-x86_64-pixman 36 | * mingw-w64-x86_64-zlib 37 | -------------------------------------------------------------------------------- /versions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | 8 | enum 9 | { 10 | H_NAME = 0, 11 | H_VERSION, 12 | H_LINK, 13 | H_CHKSUM, 14 | H_N_COLUMNS 15 | }; 16 | 17 | 18 | 19 | typedef struct 20 | { 21 | gchar *name; 22 | gchar *version; 23 | gchar *link; 24 | gchar *chksum; 25 | } 26 | version_info; 27 | 28 | 29 | 30 | typedef struct 31 | { 32 | GtkWidget *cb; 33 | GtkWidget *progress; 34 | GtkWidget *status; 35 | 36 | GtkButton *self; 37 | const version_info *info; 38 | } button_context; 39 | 40 | 41 | 42 | static GList* ver_lst; //version_info* 43 | static int preferred; 44 | 45 | 46 | 47 | static void 48 | h_start_button_callback (GtkButton* self, 49 | gpointer user_data); 50 | 51 | static void 52 | h_start_button_download_status (goffset current, 53 | goffset total, 54 | gpointer user_data); 55 | 56 | static void 57 | h_start_button_extract_data (GObject* source_object, 58 | GAsyncResult* res, 59 | gpointer user_data); 60 | 61 | static void 62 | h_start_button_start_game (GPid pid, 63 | gint wait_status, 64 | gpointer user_data); 65 | 66 | 67 | 68 | static void 69 | to_linux_path (gchar *path) 70 | { 71 | path[1] = path[0]; 72 | path[0] = '/'; 73 | for(size_t i = 0; path[i] != '\0'; i++) 74 | if (path[i] == '\\') path[i] = '/'; 75 | } 76 | 77 | 78 | 79 | static void 80 | h_xml_start (GMarkupParseContext *context, 81 | const gchar *element_name, 82 | const gchar **attribute_names, 83 | const gchar **attribute_values, 84 | gpointer user_data, 85 | GError **error) 86 | { 87 | GList **list = (GList **)user_data; 88 | version_info* ver = g_new0 (version_info, 1); 89 | 90 | const gchar *temp_name; 91 | 92 | for (const gchar **names = attribute_names, **values = attribute_values; 93 | *names != NULL; 94 | names++, values++) 95 | { 96 | if (g_strcmp0 (*names, "name") == 0) 97 | temp_name = *values; // ***** 98 | else if (g_strcmp0 (*names, "version") == 0) 99 | ver->version = g_strdup (*values); 100 | else if (g_strcmp0 (*names, "link") == 0) 101 | ver->link = g_strdup (*values); 102 | else if (g_strcmp0 (*names, "chksum") == 0) 103 | ver->chksum = g_strdup (*values); 104 | } 105 | 106 | ver->name = g_strdup_printf ("%s (%s)", temp_name, ver->version); 107 | 108 | *list = g_list_append (*list, ver); 109 | } 110 | 111 | 112 | 113 | static void 114 | h_free_version_info (void *ver) 115 | { 116 | version_info *v = ver; 117 | g_free (v->name); 118 | g_free (v->version); 119 | g_free (v->link); 120 | g_free (v->chksum); 121 | 122 | g_free (v); 123 | } 124 | 125 | 126 | 127 | static GList* 128 | h_init_version_info_list (int *preferred) 129 | { 130 | gchar *path = g_build_path ("//", g_getenv ("localappdata"), "HoloCureKR", NULL); 131 | gchar *versions_xml = g_build_path ("//", path, "versions.xml", NULL); 132 | //gchar *pref = g_build_path ("//", path, "pref.txt", NULL); 133 | GFile *file = g_file_new_for_path(path); 134 | g_file_make_directory (file, NULL, NULL); 135 | g_object_unref (file); 136 | g_free (path); 137 | 138 | *preferred = 0; //default - latest 139 | 140 | /* 141 | if (g_file_test (prefname, G_FILE_TEST_EXISTS)) 142 | { 143 | GList *lst = NULL; 144 | return lst; 145 | } 146 | */ 147 | 148 | //Download! 149 | //TODO: ADD CANCELLABLE; g_timeout_add_seconds 150 | GFile *src = g_file_new_for_uri ("https://raw.githubusercontent.com/khronii/HoloCure-Translation-Launcher/main/versions.xml"); 151 | GFile *dest = g_file_new_for_path (versions_xml); 152 | g_file_copy (src,dest,G_FILE_COPY_OVERWRITE,NULL,NULL,NULL,NULL); 153 | g_object_unref (src); 154 | g_object_unref (dest); 155 | 156 | gchar *buf; 157 | gsize len; 158 | g_file_get_contents (versions_xml, &buf, &len, NULL); 159 | 160 | g_free (versions_xml); 161 | 162 | GMarkupParser parser = { 163 | .start_element = h_xml_start, 164 | .end_element = NULL, 165 | .text = NULL, 166 | .passthrough = NULL, 167 | .error = NULL}; 168 | 169 | GList *lst = NULL; 170 | GMarkupParseContext *context = g_markup_parse_context_new(&parser, 0, (gpointer) &lst, NULL); 171 | 172 | if(g_markup_parse_context_parse(context, buf, len, NULL)) 173 | { 174 | version_info* ver = g_new0 (version_info, 1); 175 | *ver = (version_info) { 176 | g_strdup ("최신 빌드"), 177 | g_strdup ("?.?.?"), 178 | g_strdup (""), 179 | g_strdup ("LATEST") 180 | }; 181 | lst = g_list_prepend (lst, ver); 182 | } 183 | 184 | g_free(buf); 185 | 186 | return lst; //FAIL시 lst == NULL 187 | } 188 | 189 | 190 | 191 | static void 192 | h_free_version_info_list (GList *ver) 193 | { 194 | g_list_free_full(ver, h_free_version_info); 195 | } 196 | 197 | 198 | 199 | static GdkPixbuf* 200 | h_gdk_pixbuf_scale_width(GdkPixbuf* buf, 201 | int width) 202 | { 203 | int w = gdk_pixbuf_get_width(buf); 204 | int h = gdk_pixbuf_get_height(buf); 205 | 206 | return gdk_pixbuf_scale_simple(buf, width, h * width / w, GDK_INTERP_HYPER); 207 | } 208 | 209 | static GtkWidget* 210 | h_get_info_label() 211 | { 212 | GtkWidget* label; 213 | 214 | const gchar *text = 215 | "게임 공식 홈페이지 : itch.io\n" 216 | "코딩/번역 : [출처_필요]\n" 217 | "번역 : Flora (@flora_852)\n" 218 | "로고 한글화 : 리노(@rito__321)"; 219 | label = gtk_label_new (NULL); 220 | gtk_label_set_markup (GTK_LABEL (label), text); 221 | 222 | return label; 223 | } 224 | 225 | static GtkWidget* 226 | h_get_version_combo_box() 227 | { 228 | GtkWidget *cbox; 229 | GtkListStore *store = gtk_list_store_new (H_N_COLUMNS, 230 | G_TYPE_STRING, 231 | G_TYPE_STRING, 232 | G_TYPE_STRING, 233 | G_TYPE_STRING); 234 | 235 | GtkTreeIter iter; 236 | for (GList* elem = ver_lst; elem; elem = elem->next) 237 | { 238 | version_info* v = (version_info*) elem->data; 239 | gtk_list_store_append (store, &iter); 240 | gtk_list_store_set (store, &iter, 241 | H_NAME, v->name, 242 | H_VERSION, v->version, 243 | H_LINK, v->link, 244 | H_CHKSUM, v->chksum, 245 | -1); 246 | } 247 | 248 | cbox = gtk_combo_box_new (); 249 | gtk_combo_box_set_id_column (GTK_COMBO_BOX(cbox), H_NAME); 250 | gtk_combo_box_set_model (GTK_COMBO_BOX(cbox), GTK_TREE_MODEL(store)); 251 | 252 | gtk_cell_layout_clear (GTK_CELL_LAYOUT(cbox)); 253 | GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new (); 254 | gtk_cell_layout_pack_end (GTK_CELL_LAYOUT(cbox), 255 | text_renderer, TRUE ); 256 | gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(cbox), 257 | text_renderer, 258 | "text", 259 | H_NAME); 260 | 261 | gtk_combo_box_set_active(GTK_COMBO_BOX(cbox), 0); 262 | 263 | return cbox; 264 | } 265 | 266 | static void 267 | h_start_button_callback (GtkButton* self, 268 | gpointer user_data) 269 | { 270 | button_context* ctx = user_data; 271 | const gchar *localappdata = g_getenv ("localappdata"); 272 | int sel = gtk_combo_box_get_active(GTK_COMBO_BOX(ctx->cb)); 273 | sel = (sel <= 0) ? 1 : sel; 274 | ctx->info = (version_info *) g_list_nth_data (ver_lst, sel); 275 | ctx->self = self; 276 | const gchar *link = ctx->info->link; 277 | gchar *zip_name = g_strconcat(ctx->info->chksum,".zip",NULL); 278 | gchar *zip_path = g_build_path ("\\", localappdata, "HoloCureKR", zip_name, NULL); 279 | 280 | //LOCK BUTTON HERE 281 | gtk_widget_set_sensitive (GTK_WIDGET(self), FALSE); 282 | 283 | gboolean no_zip = !g_file_test (zip_path, G_FILE_TEST_EXISTS); 284 | if (no_zip) 285 | { 286 | g_info ("Download!"); 287 | 288 | gchar *temp_name = g_strconcat(ctx->info->chksum,".zip.part",NULL); 289 | gchar *temp_path = g_build_path ("\\", localappdata, "HoloCureKR", temp_name, NULL); 290 | //Download! 291 | GFile *src = g_file_new_for_uri (link); 292 | GFile *dest = g_file_new_for_path (temp_path); 293 | g_file_copy_async (src,dest,G_FILE_COPY_OVERWRITE,G_PRIORITY_DEFAULT_IDLE-10,NULL, 294 | h_start_button_download_status,user_data, 295 | h_start_button_extract_data,user_data); 296 | g_object_unref (src); 297 | g_object_unref (dest); 298 | g_free (temp_name); 299 | g_free (temp_path); 300 | } 301 | 302 | g_free(zip_name); 303 | g_free(zip_path); 304 | 305 | if (!no_zip) 306 | h_start_button_extract_data (0, 0, user_data); 307 | } 308 | 309 | static void 310 | h_start_button_download_status (goffset current, 311 | goffset total, 312 | gpointer user_data) 313 | { 314 | button_context* ctx = user_data; 315 | 316 | if (current != total) 317 | { 318 | gchar *progress = g_strdup_printf ("%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "(%.1f%%)", (gint64) current, (gint64) total, (float)current/(float)total*100); 319 | gtk_button_set_label (ctx->self, progress); 320 | g_free (progress); 321 | 322 | return; 323 | } 324 | else 325 | gtk_button_set_label (ctx->self, "Start!"); 326 | } 327 | 328 | static void 329 | h_start_button_extract_data (GObject* source_object, 330 | GAsyncResult* res, 331 | gpointer user_data) 332 | { 333 | button_context* ctx = user_data; 334 | 335 | const gchar *localappdata = g_getenv ("localappdata"); 336 | gchar *zip_name = g_strconcat(ctx->info->chksum,".zip",NULL); 337 | gchar *zip_path = g_build_path ("\\", localappdata, "HoloCureKR", zip_name, NULL); 338 | gchar *game_path = g_build_path ("\\", localappdata, "HoloCureKR", ctx->info->chksum, NULL); 339 | gchar *game_exe = g_build_path ("\\", game_path, "HoloCure.exe", NULL); 340 | 341 | gchar *temp_name = g_strconcat(ctx->info->chksum,".zip.part",NULL); 342 | gchar *temp_path = g_build_path ("\\", localappdata, "HoloCureKR", temp_name, NULL); 343 | if (g_file_test (temp_path, G_FILE_TEST_EXISTS)) 344 | { 345 | GFile *temp = g_file_new_for_path (temp_path); 346 | GFile *zip = g_file_new_for_path (zip_path); 347 | g_file_move (temp, zip, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL); 348 | g_object_unref (temp); 349 | g_object_unref (zip); 350 | } 351 | g_free (temp_name); 352 | g_free (temp_path); 353 | 354 | gboolean no_game = !g_file_test (game_exe, G_FILE_TEST_EXISTS); 355 | if (no_game) 356 | { 357 | g_info ("Extract!"); 358 | 359 | //Extract! 360 | GFile *file = g_file_new_for_path (game_path); 361 | g_file_make_directory (file, NULL, NULL); 362 | g_object_unref(file); 363 | 364 | GPid pid; 365 | 366 | to_linux_path (zip_path); 367 | to_linux_path (game_path); 368 | gchar *unzip_arg[] = {"unzip", "-a", zip_path, "-d", game_path, NULL}; 369 | g_spawn_async (NULL, unzip_arg, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL); 370 | g_child_watch_add_full (G_PRIORITY_DEFAULT_IDLE, pid, h_start_button_start_game, user_data, NULL); 371 | } 372 | 373 | g_free(zip_name); 374 | g_free(zip_path); 375 | g_free(game_path); 376 | g_free(game_exe); 377 | 378 | if (!no_game) 379 | h_start_button_start_game(0, 0, user_data); 380 | } 381 | 382 | static void 383 | h_start_button_start_game (GPid pid, 384 | gint wait_status, 385 | gpointer user_data) 386 | { 387 | if (pid != 0) 388 | g_spawn_close_pid (pid); 389 | button_context* ctx = user_data; 390 | const gchar *localappdata = g_getenv ("localappdata"); 391 | gchar *game_path = g_build_path ("\\", localappdata, "HoloCureKR", ctx->info->chksum, NULL); 392 | gchar *game_exe = g_build_path ("\\", game_path, "HoloCure.exe", NULL); 393 | 394 | gchar *game_arg[] = {game_exe, NULL}; 395 | g_spawn_async (game_path, game_arg, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, NULL, NULL); 396 | 397 | //TODO: unlock button as async callback 398 | gtk_widget_set_sensitive (GTK_WIDGET(ctx->self), TRUE); 399 | 400 | g_free(game_path); 401 | g_free(game_exe); 402 | } 403 | 404 | static void 405 | activate (GtkApplication* app, 406 | gpointer user_data) 407 | { 408 | GtkWidget *window; 409 | GtkWidget *grid; 410 | GdkPixbuf *image_buf; 411 | 412 | window = gtk_application_window_new (app); 413 | gtk_window_set_title (GTK_WINDOW (window), "HoloCure 한글패치 전용 런쳐"); 414 | //gtk_window_set_default_size (GTK_WINDOW (window), 800, 680); 415 | 416 | grid = gtk_grid_new(); 417 | gtk_container_add (GTK_CONTAINER(window), grid); 418 | 419 | if ((image_buf = gdk_pixbuf_new_from_file("holocure.png",NULL)) != NULL) 420 | { 421 | GdkPixbuf *image_scaled_buf = h_gdk_pixbuf_scale_width(image_buf, 700); 422 | GtkWidget *image = gtk_image_new_from_pixbuf(image_scaled_buf); 423 | g_object_unref(image_buf); 424 | g_object_unref(image_scaled_buf); 425 | 426 | gtk_grid_attach(GTK_GRID(grid), image, 0,0, 2,1); 427 | } 428 | else 429 | { 430 | g_info ("Image load failed"); 431 | } 432 | 433 | GtkWidget *cb = h_get_version_combo_box(); 434 | GtkWidget *start_button = gtk_button_new_with_label("Start!"); 435 | button_context *ctx = g_new(button_context, 1); 436 | *ctx = (button_context) {.cb=cb, .progress = NULL, .status = NULL}; 437 | g_signal_connect_data (start_button, 438 | "clicked", 439 | G_CALLBACK(h_start_button_callback), 440 | (gpointer) ctx, 441 | (GClosureNotify) g_free, //It works on my computer(R) 442 | 0); 443 | 444 | gtk_grid_attach(GTK_GRID(grid), h_get_info_label(), 0,1, 2,1); 445 | gtk_grid_attach(GTK_GRID(grid), cb, 0,4, 1,1); 446 | gtk_grid_attach(GTK_GRID(grid), start_button, 1,4, 1,1); 447 | 448 | gtk_widget_show_all (window); 449 | } 450 | 451 | int 452 | main (int argc, 453 | char **argv) 454 | { 455 | GtkApplication *app; 456 | int status; 457 | 458 | ver_lst = h_init_version_info_list (&preferred); 459 | 460 | app = gtk_application_new ("kr.holocure.launcher", G_APPLICATION_FLAGS_NONE); 461 | g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); 462 | status = g_application_run (G_APPLICATION (app), argc, argv); 463 | g_object_unref (app); 464 | 465 | h_free_version_info_list (ver_lst); 466 | 467 | return status; 468 | } 469 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | 9 | enum 10 | { 11 | H_NAME = 0, 12 | H_VERSION, 13 | H_LINK, 14 | H_CHKSUM, 15 | H_N_COLUMNS 16 | }; 17 | 18 | 19 | 20 | typedef struct 21 | { 22 | gchar *name; 23 | gchar *version; 24 | gchar *link; 25 | gchar *chksum; 26 | } 27 | version_info; 28 | 29 | 30 | 31 | typedef struct 32 | { 33 | GtkWidget *cb; 34 | GtkWidget *progress; 35 | GtkWidget *status; 36 | 37 | GtkButton *self; 38 | const version_info *info; 39 | } button_context; 40 | 41 | 42 | 43 | static GList* ver_lst; //version_info* 44 | static int preferred; 45 | 46 | 47 | 48 | static void 49 | h_start_button_callback (GtkButton* self, 50 | gpointer user_data); 51 | 52 | static void 53 | h_start_button_download_status (goffset current, 54 | goffset total, 55 | gpointer user_data); 56 | 57 | static void 58 | h_start_button_extract_data (GObject* source_object, 59 | GAsyncResult* res, 60 | gpointer user_data); 61 | 62 | static void 63 | h_start_button_start_game (GPid pid, 64 | gint wait_status, 65 | gpointer user_data); 66 | 67 | 68 | 69 | static void 70 | h_xml_start (GMarkupParseContext *context, 71 | const gchar *element_name, 72 | const gchar **attribute_names, 73 | const gchar **attribute_values, 74 | gpointer user_data, 75 | GError **error) 76 | { 77 | GList **list = (GList **)user_data; 78 | version_info* ver = g_new0 (version_info, 1); 79 | 80 | const gchar *temp_name; 81 | 82 | for (const gchar **names = attribute_names, **values = attribute_values; 83 | *names != NULL; 84 | names++, values++) 85 | { 86 | if (g_strcmp0 (*names, "name") == 0) 87 | temp_name = *values; // ***** 88 | else if (g_strcmp0 (*names, "version") == 0) 89 | ver->version = g_strdup (*values); 90 | else if (g_strcmp0 (*names, "link") == 0) 91 | ver->link = g_strdup (*values); 92 | else if (g_strcmp0 (*names, "chksum") == 0) 93 | ver->chksum = g_strdup (*values); 94 | } 95 | 96 | ver->name = g_strdup_printf ("%s (%s)", temp_name, ver->version); 97 | 98 | *list = g_list_append (*list, ver); 99 | } 100 | 101 | 102 | 103 | static void 104 | h_free_version_info (void *ver) 105 | { 106 | version_info *v = ver; 107 | g_free (v->name); 108 | g_free (v->version); 109 | g_free (v->link); 110 | g_free (v->chksum); 111 | 112 | g_free (v); 113 | } 114 | 115 | 116 | 117 | static gboolean 118 | h_has_to_download_xml (const gchar *path) 119 | { 120 | GFile *file = g_file_new_for_path (path); 121 | 122 | GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL); 123 | g_object_unref (file); 124 | if (info == NULL) 125 | return TRUE; 126 | 127 | guint64 t_file = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); 128 | guint64 t_now = g_get_real_time () / (1000 * 1000); 129 | g_object_unref (info); 130 | 131 | if ((t_now - t_file) > 60 * 60 * 3) 132 | return TRUE; 133 | 134 | return FALSE; //Fresh XML! 135 | } 136 | 137 | 138 | 139 | static GList* 140 | h_init_version_info_list (int *preferred) 141 | { 142 | gchar *path = g_build_path ("\\", g_getenv ("localappdata"), "HoloCureKR", NULL); 143 | gchar *versions_xml = g_build_path ("\\", path, "versions.xml", NULL); 144 | GFile *file = g_file_new_for_path(path); 145 | g_file_make_directory (file, NULL, NULL); 146 | g_object_unref (file); 147 | g_free (path); 148 | 149 | gchar *preferred_chksum = NULL; 150 | 151 | *preferred = 0; //default - latest 152 | 153 | gchar *pref = g_build_path ("\\", path, "pref.txt", NULL); 154 | if (g_file_test (pref, G_FILE_TEST_EXISTS)) 155 | g_file_get_contents (pref, &preferred_chksum, NULL, NULL); 156 | g_free (pref); 157 | 158 | if (h_has_to_download_xml (versions_xml)) 159 | { 160 | GFile *src = g_file_new_for_uri ("https://raw.githubusercontent.com/khronii/HoloCure-Translation-Launcher/main/versions.xml"); 161 | GFile *dest = g_file_new_for_path (versions_xml); 162 | g_file_copy (src,dest,G_FILE_COPY_OVERWRITE,NULL,NULL,NULL,NULL); 163 | g_object_unref (src); 164 | g_object_unref (dest); 165 | } 166 | 167 | gchar *buf; 168 | gsize len; 169 | g_file_get_contents (versions_xml, &buf, &len, NULL); 170 | 171 | g_free (versions_xml); 172 | 173 | GMarkupParser parser = { 174 | .start_element = h_xml_start, 175 | .end_element = NULL, 176 | .text = NULL, 177 | .passthrough = NULL, 178 | .error = NULL}; 179 | 180 | GList *lst = NULL; 181 | GMarkupParseContext *context = g_markup_parse_context_new(&parser, 0, (gpointer) &lst, NULL); 182 | 183 | if (g_markup_parse_context_parse(context, buf, len, NULL)) 184 | { 185 | version_info* ver = g_new0 (version_info, 1); 186 | *ver = (version_info) { 187 | g_strdup ("최신 빌드"), 188 | g_strdup ("?.?.?"), 189 | g_strdup (""), 190 | g_strdup ("LATEST") 191 | }; 192 | lst = g_list_prepend (lst, ver); 193 | 194 | //get preferred 195 | size_t i = 0; 196 | for (GList *elem = lst;elem != NULL; elem = elem->next, i++) 197 | { 198 | version_info *ver = (version_info *) elem->data; 199 | if (g_strcmp0(ver->chksum, preferred_chksum) == 0) 200 | { 201 | *preferred = i; 202 | break; 203 | } 204 | } 205 | } 206 | 207 | g_free (buf); 208 | g_free (preferred_chksum); 209 | 210 | return lst; //FAIL시 lst == NULL 211 | } 212 | 213 | 214 | 215 | static void 216 | h_free_version_info_list (GList *ver) 217 | { 218 | g_list_free_full(ver, h_free_version_info); 219 | } 220 | 221 | 222 | 223 | static GdkPixbuf* 224 | h_gdk_pixbuf_scale_width(GdkPixbuf* buf, 225 | int width) 226 | { 227 | int w = gdk_pixbuf_get_width(buf); 228 | int h = gdk_pixbuf_get_height(buf); 229 | 230 | return gdk_pixbuf_scale_simple(buf, width, h * width / w, GDK_INTERP_HYPER); 231 | } 232 | 233 | static GtkWidget* 234 | h_get_info_label() 235 | { 236 | GtkWidget* label; 237 | 238 | const gchar *text = 239 | "게임 공식 홈페이지 : itch.io\n" 240 | "코딩/번역 감수 : [출처_필요]\n" 241 | "번역 : Flora (@flora_852)\n" 242 | "로고 한글화 : 리노(@rito__321)"; 243 | label = gtk_label_new (NULL); 244 | gtk_label_set_markup (GTK_LABEL (label), text); 245 | 246 | return label; 247 | } 248 | 249 | static GtkWidget* 250 | h_get_version_combo_box(int preferred) 251 | { 252 | GtkWidget *cbox; 253 | GtkListStore *store = gtk_list_store_new (H_N_COLUMNS, 254 | G_TYPE_STRING, 255 | G_TYPE_STRING, 256 | G_TYPE_STRING, 257 | G_TYPE_STRING); 258 | 259 | GtkTreeIter iter; 260 | for (GList* elem = ver_lst; elem; elem = elem->next) 261 | { 262 | version_info* v = (version_info*) elem->data; 263 | gtk_list_store_append (store, &iter); 264 | gtk_list_store_set (store, &iter, 265 | H_NAME, v->name, 266 | H_VERSION, v->version, 267 | H_LINK, v->link, 268 | H_CHKSUM, v->chksum, 269 | -1); 270 | } 271 | 272 | cbox = gtk_combo_box_new (); 273 | gtk_combo_box_set_id_column (GTK_COMBO_BOX(cbox), H_NAME); 274 | gtk_combo_box_set_model (GTK_COMBO_BOX(cbox), GTK_TREE_MODEL(store)); 275 | 276 | gtk_cell_layout_clear (GTK_CELL_LAYOUT(cbox)); 277 | GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new (); 278 | gtk_cell_layout_pack_end (GTK_CELL_LAYOUT(cbox), 279 | text_renderer, TRUE ); 280 | gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(cbox), 281 | text_renderer, 282 | "text", 283 | H_NAME); 284 | 285 | gtk_combo_box_set_active(GTK_COMBO_BOX(cbox), preferred); 286 | 287 | return cbox; 288 | } 289 | 290 | static void 291 | h_start_button_callback (GtkButton* self, 292 | gpointer user_data) 293 | { 294 | //LOCK BUTTON HERE 295 | gtk_widget_set_sensitive (GTK_WIDGET(self), FALSE); 296 | 297 | button_context* ctx = user_data; 298 | const gchar *localappdata = g_getenv ("localappdata"); 299 | int sel = gtk_combo_box_get_active(GTK_COMBO_BOX(ctx->cb)); 300 | 301 | gchar *pref = g_build_path ("\\", localappdata, "HoloCureKR", "pref.txt", NULL); 302 | version_info *selected_real = (version_info *) g_list_nth_data (ver_lst, sel); 303 | g_file_set_contents (pref, selected_real->chksum, strlen (selected_real->chksum), NULL); 304 | g_free (pref); 305 | 306 | sel = (sel <= 0) ? 1 : sel; 307 | ctx->info = (version_info *) g_list_nth_data (ver_lst, sel); 308 | ctx->self = self; 309 | const gchar *link = ctx->info->link; 310 | 311 | gchar *game_exe = g_build_path ("\\", localappdata, "HoloCureKR", ctx->info->chksum, "HoloCure.exe", NULL); 312 | gboolean is_exe = g_file_test (game_exe, G_FILE_TEST_EXISTS); 313 | g_free (game_exe); 314 | if (is_exe) 315 | { 316 | h_start_button_start_game(0, 0, user_data); 317 | return; 318 | } 319 | 320 | gchar *zip_name = g_strconcat(ctx->info->chksum,".zip",NULL); 321 | gchar *zip_path = g_build_path ("\\", localappdata, "HoloCureKR", zip_name, NULL); 322 | gboolean no_zip = !g_file_test (zip_path, G_FILE_TEST_EXISTS); 323 | if (no_zip) 324 | { 325 | g_info ("Download!"); 326 | 327 | gchar *temp_name = g_strconcat(ctx->info->chksum,".zip.part",NULL); 328 | gchar *temp_path = g_build_path ("\\", localappdata, "HoloCureKR", temp_name, NULL); 329 | //Download! 330 | GFile *src = g_file_new_for_uri (link); 331 | GFile *dest = g_file_new_for_path (temp_path); 332 | g_file_copy_async (src,dest,G_FILE_COPY_OVERWRITE,G_PRIORITY_DEFAULT_IDLE-10,NULL, 333 | h_start_button_download_status,user_data, 334 | h_start_button_extract_data,user_data); 335 | g_object_unref (src); 336 | g_object_unref (dest); 337 | g_free (temp_name); 338 | g_free (temp_path); 339 | } 340 | 341 | g_free(zip_name); 342 | g_free(zip_path); 343 | 344 | if (!no_zip) 345 | h_start_button_extract_data (0, 0, user_data); 346 | } 347 | 348 | static void 349 | h_start_button_download_status (goffset current, 350 | goffset total, 351 | gpointer user_data) 352 | { 353 | button_context* ctx = user_data; 354 | 355 | if (current != total) 356 | { 357 | gchar *progress = g_strdup_printf ("%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "(%.1f%%)", (gint64) current, (gint64) total, (float)current/(float)total*100); 358 | gtk_button_set_label (ctx->self, progress); 359 | g_free (progress); 360 | 361 | return; 362 | } 363 | else 364 | gtk_button_set_label (ctx->self, "Start!"); 365 | } 366 | 367 | static void 368 | h_start_button_extract_data (GObject* source_object, 369 | GAsyncResult* res, 370 | gpointer user_data) 371 | { 372 | button_context* ctx = user_data; 373 | 374 | const gchar *localappdata = g_getenv ("localappdata"); 375 | gchar *zip_name = g_strconcat(ctx->info->chksum,".zip",NULL); 376 | gchar *zip_path = g_build_path ("\\", localappdata, "HoloCureKR", zip_name, NULL); 377 | gchar *game_path = g_build_path ("\\", localappdata, "HoloCureKR", ctx->info->chksum, NULL); 378 | gchar *game_exe = g_build_path ("\\", game_path, "HoloCure.exe", NULL); 379 | 380 | gchar *temp_name = g_strconcat(ctx->info->chksum,".zip.part",NULL); 381 | gchar *temp_path = g_build_path ("\\", localappdata, "HoloCureKR", temp_name, NULL); 382 | if (g_file_test (temp_path, G_FILE_TEST_EXISTS)) 383 | { 384 | GFile *temp = g_file_new_for_path (temp_path); 385 | GFile *zip = g_file_new_for_path (zip_path); 386 | g_file_move (temp, zip, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL); 387 | g_object_unref (temp); 388 | g_object_unref (zip); 389 | } 390 | g_free (temp_name); 391 | g_free (temp_path); 392 | 393 | gboolean no_game = !g_file_test (game_exe, G_FILE_TEST_EXISTS); 394 | if (no_game) 395 | { 396 | g_info ("Extract!"); 397 | 398 | //Extract! 399 | GFile *file = g_file_new_for_path (game_path); 400 | g_file_make_directory (file, NULL, NULL); 401 | g_object_unref(file); 402 | 403 | GPid pid; 404 | 405 | gchar *cdir = g_get_current_dir(); 406 | gchar *path_unzip = g_build_path ("\\", cdir, "unzip.exe", NULL); 407 | gchar *unzip_arg[] = {path_unzip, "-a", zip_path, "-d", game_path, NULL}; 408 | g_spawn_async (NULL, unzip_arg, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL); 409 | g_child_watch_add_full (G_PRIORITY_DEFAULT_IDLE, pid, h_start_button_start_game, user_data, NULL); 410 | g_free (cdir); 411 | g_free (path_unzip); 412 | } 413 | 414 | g_free(zip_name); 415 | g_free(zip_path); 416 | g_free(game_path); 417 | g_free(game_exe); 418 | 419 | if (!no_game) 420 | h_start_button_start_game(0, 0, user_data); 421 | } 422 | 423 | static void 424 | h_start_button_start_game (GPid pid, 425 | gint wait_status, 426 | gpointer user_data) 427 | { 428 | button_context* ctx = user_data; 429 | const gchar *localappdata = g_getenv ("localappdata"); 430 | gchar *game_path = g_build_path ("\\", localappdata, "HoloCureKR", ctx->info->chksum, NULL); 431 | gchar *game_exe = g_build_path ("\\", game_path, "HoloCure.exe", NULL); 432 | 433 | if (pid != 0) 434 | { 435 | g_spawn_close_pid (pid); 436 | 437 | gchar *zip_name = g_strconcat(ctx->info->chksum,".zip",NULL); 438 | gchar *zip_path = g_build_path ("\\", localappdata, "HoloCureKR", zip_name, NULL); 439 | GFile *zip_file = g_file_new_for_path ("zip_path"); 440 | 441 | g_file_delete (zip_file, NULL, NULL); 442 | 443 | g_object_unref (zip_file); 444 | g_free (zip_name); 445 | g_free (zip_path); 446 | } 447 | 448 | gchar *game_arg[] = {game_exe, NULL}; 449 | g_spawn_async (game_path, game_arg, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, NULL, NULL); 450 | 451 | //TODO: unlock button as async callback 452 | gtk_widget_set_sensitive (GTK_WIDGET(ctx->self), TRUE); 453 | 454 | g_free(game_path); 455 | g_free(game_exe); 456 | } 457 | 458 | static void 459 | activate (GtkApplication* app, 460 | gpointer user_data) 461 | { 462 | GtkWidget *window; 463 | GtkWidget *grid; 464 | GdkPixbuf *image_buf; 465 | 466 | window = gtk_application_window_new (app); 467 | gtk_window_set_title (GTK_WINDOW (window), "HoloCure 한글패치 전용 런쳐"); 468 | //gtk_window_set_default_size (GTK_WINDOW (window), 800, 680); 469 | 470 | grid = gtk_grid_new(); 471 | gtk_container_add (GTK_CONTAINER(window), grid); 472 | 473 | if ((image_buf = gdk_pixbuf_new_from_file("holocure.png",NULL)) != NULL) 474 | { 475 | GdkPixbuf *image_scaled_buf = h_gdk_pixbuf_scale_width(image_buf, 700); 476 | GtkWidget *image = gtk_image_new_from_pixbuf(image_scaled_buf); 477 | g_object_unref(image_buf); 478 | g_object_unref(image_scaled_buf); 479 | 480 | gtk_grid_attach(GTK_GRID(grid), image, 0,0, 2,1); 481 | } 482 | else 483 | { 484 | g_info ("Image load failed"); 485 | } 486 | 487 | GtkWidget *cb = h_get_version_combo_box(preferred); 488 | GtkWidget *start_button = gtk_button_new_with_label("Start!"); 489 | button_context *ctx = g_new(button_context, 1); 490 | *ctx = (button_context) {.cb=cb, .progress = NULL, .status = NULL}; 491 | g_signal_connect_data (start_button, 492 | "clicked", 493 | G_CALLBACK(h_start_button_callback), 494 | (gpointer) ctx, 495 | (GClosureNotify) g_free, //It works on my computer(R) 496 | 0); 497 | 498 | gtk_grid_attach(GTK_GRID(grid), h_get_info_label(), 0,1, 2,1); 499 | gtk_grid_attach(GTK_GRID(grid), cb, 0,4, 1,1); 500 | gtk_grid_attach(GTK_GRID(grid), start_button, 1,4, 1,1); 501 | 502 | gtk_widget_show_all (window); 503 | } 504 | 505 | int 506 | main (int argc, 507 | char **argv) 508 | { 509 | GtkApplication *app; 510 | int status; 511 | 512 | ver_lst = h_init_version_info_list (&preferred); 513 | 514 | app = gtk_application_new ("kr.holocure.launcher", G_APPLICATION_FLAGS_NONE); 515 | g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); 516 | status = g_application_run (G_APPLICATION (app), argc, argv); 517 | g_object_unref (app); 518 | 519 | h_free_version_info_list (ver_lst); 520 | 521 | return status; 522 | } 523 | --------------------------------------------------------------------------------