1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2010-2011 Thibault Duponchelle
5  * Copyright (c) 2010-2011 Benjamin Moody
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <gtk/gtk.h>
30 #include <glib/gstdio.h>
31 #include <ticalcs.h>
32 #include <ticonv.h>
33 #include <string.h>
34 #include <tilem.h>
35 #include <scancodes.h>
36 
37 #include "gui.h"
38 #include "emucore.h"
39 #include "ti81prg.h"
40 #include "msgbox.h"
41 
42 /**************** Internal link emulation ****************/
43 
44 /* Open cable */
ilp_open(CableHandle * cbl)45 static int ilp_open(CableHandle* cbl)
46 {
47 	TilemCalcEmulator* emu = cbl->priv;
48 
49 	tilem_em_lock(emu);
50 
51 	if (emu->ilp_active) {
52 		fprintf(stderr, "INTERNAL ERROR: cable already opened\n");
53 		tilem_em_unlock(emu);
54 		return 1;
55 	}
56 
57 	emu->ilp_active = TRUE;
58 	tilem_linkport_graylink_reset(emu->calc);
59 	tilem_em_unlock(emu);
60 	return 0;
61 }
62 
63 /* Close cable */
ilp_close(CableHandle * cbl)64 static int ilp_close(CableHandle* cbl)
65 {
66 	TilemCalcEmulator* emu = cbl->priv;
67 
68 	tilem_em_lock(emu);
69 
70 	if (!emu->ilp_active) {
71 		fprintf(stderr, "INTERNAL ERROR: cable already closed\n");
72 		tilem_em_unlock(emu);
73 		return 1;
74 	}
75 
76 	emu->ilp_active = FALSE;
77 	emu->calc->linkport.linkemu = TILEM_LINK_EMULATOR_NONE;
78 	tilem_linkport_graylink_reset(emu->calc);
79 	tilem_em_unlock(emu);
80 	return 0;
81 }
82 
83 /* Reset cable */
ilp_reset(CableHandle * cbl)84 static int ilp_reset(CableHandle* cbl)
85 {
86 	TilemCalcEmulator* emu = cbl->priv;
87 
88 	tilem_em_lock(emu);
89 	tilem_linkport_graylink_reset(emu->calc);
90 	tilem_em_unlock(emu);
91 	return 0;
92 }
93 
94 /* Send data to calc */
ilp_send(CableHandle * cbl,uint8_t * data,uint32_t count)95 static int ilp_send(CableHandle* cbl, uint8_t* data, uint32_t count)
96 {
97 	TilemCalcEmulator* emu = cbl->priv;
98 	int timeout = cbl->timeout * 100000;
99 
100 	tilem_em_lock(emu);
101 	while (count > 0) {
102 		if (tilem_em_send_byte(emu, data[0], timeout, TRUE)) {
103 			tilem_em_unlock(emu);
104 			return ERROR_WRITE_TIMEOUT;
105 		}
106 		data++;
107 		count--;
108 	}
109 	tilem_em_unlock(emu);
110 	return 0;
111 }
112 
113 /* cool-down period required after receiving and before sending */
114 #define COOLDOWN 10000
115 
116 /* Receive data from calc */
ilp_recv(CableHandle * cbl,uint8_t * data,uint32_t count)117 static int ilp_recv(CableHandle* cbl, uint8_t* data, uint32_t count)
118 {
119 	TilemCalcEmulator* emu = cbl->priv;
120 	int timeout = cbl->timeout * 100000;
121 	int value;
122 
123 	tilem_em_lock(emu);
124 	while (count > 0) {
125 		value = tilem_em_get_byte(emu, timeout, TRUE);
126 		if (value < 0) {
127 			tilem_em_unlock(emu);
128 			return ERROR_READ_TIMEOUT;
129 		}
130 		data[0] = value;
131 		data++;
132 		count--;
133 	}
134 	tilem_em_delay(emu, COOLDOWN, TRUE);
135 	tilem_em_unlock(emu);
136 	return 0;
137 }
138 
139 /* Check if ready */
ilp_check(CableHandle * cbl,int * status)140 static int ilp_check(CableHandle* cbl, int* status)
141 {
142 	TilemCalcEmulator* emu = cbl->priv;
143 
144 	tilem_em_lock(emu);
145 
146 	*status = STATUS_NONE;
147 	if (emu->calc->linkport.lines)
148 		*status |= STATUS_RX;
149 	if (emu->calc->linkport.extlines)
150 		*status |= STATUS_TX;
151 
152 	tilem_em_unlock(emu);
153 	return 0;
154 }
155 
156 /* Open a cable */
internal_link_handle_new(TilemCalcEmulator * emu)157 static CableHandle* internal_link_handle_new(TilemCalcEmulator* emu)
158 {
159 	CableHandle* cbl;
160 
161 	cbl = ticables_handle_new(CABLE_ILP, PORT_0);
162 	if (!cbl)
163 		return NULL;
164 
165 	cbl->priv = emu;
166 	cbl->cable->open = ilp_open;
167 	cbl->cable->close = ilp_close;
168 	cbl->cable->reset = ilp_reset;
169 	cbl->cable->send = ilp_send;
170 	cbl->cable->recv = ilp_recv;
171 	cbl->cable->check = ilp_check;
172 
173 	return cbl;
174 }
175 
176 /**************** Automatic link menu ****************/
177 
178 /* Run a key (wait, press, wait; release; wait) */
run_with_key(TilemCalcEmulator * emu,int key)179 static void run_with_key(TilemCalcEmulator* emu, int key)
180 {
181 	tilem_em_delay(emu, 50000, TRUE);
182 	tilem_keypad_press_key(emu->calc, key);
183 	tilem_em_delay(emu, 50000, TRUE);
184 	tilem_keypad_release_key(emu->calc, key);
185 	tilem_em_delay(emu, 50000, TRUE);
186 }
187 
188 /* Automatically press key to be in the receive mode (ti82 and ti85) */
prepare_for_link_send(TilemCalcEmulator * emu)189 static void prepare_for_link_send(TilemCalcEmulator* emu)
190 {
191 	tilem_em_lock(emu);
192 	tilem_em_wake_up(emu, TRUE);
193 	if (emu->calc->hw.model_id == TILEM_CALC_TI82) {
194 		run_with_key(emu, TILEM_KEY_2ND);
195 		run_with_key(emu, TILEM_KEY_MODE);
196 		run_with_key(emu, TILEM_KEY_2ND);
197 		run_with_key(emu, TILEM_KEY_GRAPHVAR);
198 		run_with_key(emu, TILEM_KEY_RIGHT);
199 		run_with_key(emu, TILEM_KEY_ENTER);
200 	}
201 	else if (emu->calc->hw.model_id == TILEM_CALC_TI85) {
202 		run_with_key(emu, TILEM_KEY_MODE);
203 		run_with_key(emu, TILEM_KEY_MODE);
204 		run_with_key(emu, TILEM_KEY_MODE);
205 		run_with_key(emu, TILEM_KEY_2ND);
206 		run_with_key(emu, TILEM_KEY_GRAPHVAR);
207 		run_with_key(emu, TILEM_KEY_WINDOW);
208 	}
209 	tilem_em_unlock(emu);
210 }
211 
212 /* Automatically press key to be in the send mode (ti82 and ti85) */
prepare_for_link_receive(TilemCalcEmulator * emu)213 static void prepare_for_link_receive(TilemCalcEmulator *emu)
214 {
215 	tilem_em_lock(emu);
216 	tilem_em_wake_up(emu, TRUE);
217 	if (emu->calc->hw.model_id == TILEM_CALC_TI82) {
218 		run_with_key(emu, TILEM_KEY_2ND);
219 		run_with_key(emu, TILEM_KEY_MODE);
220 		run_with_key(emu, TILEM_KEY_2ND);
221 		run_with_key(emu, TILEM_KEY_GRAPHVAR);
222 		run_with_key(emu, TILEM_KEY_ENTER);
223 		tilem_em_delay(emu, 10000000, TRUE);
224 		run_with_key(emu, TILEM_KEY_RIGHT);
225 		run_with_key(emu, TILEM_KEY_ENTER);
226 	}
227 	else if (emu->calc->hw.model_id == TILEM_CALC_TI85) {
228 		run_with_key(emu, TILEM_KEY_MODE);
229 		run_with_key(emu, TILEM_KEY_MODE);
230 		run_with_key(emu, TILEM_KEY_MODE);
231 		run_with_key(emu, TILEM_KEY_2ND);
232 		run_with_key(emu, TILEM_KEY_GRAPHVAR);
233 		run_with_key(emu, TILEM_KEY_YEQU);
234 		run_with_key(emu, TILEM_KEY_GRAPH);
235 		tilem_em_delay(emu, 10000000, TRUE);
236 		run_with_key(emu, TILEM_KEY_ZOOM);
237 		tilem_em_delay(emu, 10000000, TRUE);
238 		run_with_key(emu, TILEM_KEY_YEQU);
239 	}
240 	tilem_em_unlock(emu);
241 }
242 
243 /**************** Calc handle ****************/
244 
245 static GStaticPrivate current_emu_key = G_STATIC_PRIVATE_INIT;
246 
247 /* ticalcs progress bar callback */
pbar_do_update()248 static void pbar_do_update()
249 {
250 	TilemCalcEmulator *emu = g_static_private_get(&current_emu_key);
251 	CalcUpdate *upd = emu->link_update;
252 	gdouble frac;
253 
254 	if (upd->max1 > 0 && upd->max2 > 0)
255 		frac = ((gdouble) upd->cnt1 / upd->max1 + upd->cnt2) / upd->max2;
256 	else if (upd->max1 > 0)
257 		frac = ((gdouble) upd->cnt1 / upd->max1);
258 	else if (upd->max2 > 0)
259 		frac = ((gdouble) upd->cnt2 / upd->max2);
260 	else
261 		frac = -1.0;
262 
263 	tilem_em_set_progress(emu, frac, upd->text);
264 }
265 
266 /* Get the calc model (compatible for ticalcs) */
get_calc_model(TilemCalc * calc)267 int get_calc_model(TilemCalc *calc)
268 {
269 	return model_to_calcmodel(calc->hw.model_id);
270 }
271 
272 /* Create a calc handle */
begin_link(TilemCalcEmulator * emu,CableHandle ** cbl,CalcHandle ** ch,const char * title)273 void begin_link(TilemCalcEmulator *emu, CableHandle **cbl, CalcHandle **ch,
274                 const char *title)
275 {
276 	tilem_em_unlock(emu);
277 
278 	*cbl = internal_link_handle_new(emu);
279 
280 	emu->link_update->max1 = 0;
281 	emu->link_update->max2 = 0;
282 	emu->link_update->text[0] = 0;
283 
284 	emu->link_update->pbar = &pbar_do_update;
285 	emu->link_update->label = &pbar_do_update;
286 
287 	g_static_private_set(&current_emu_key, emu, NULL);
288 
289 	tilem_em_set_progress_title(emu, title);
290 
291 	*ch = ticalcs_handle_new(get_calc_model(emu->calc));
292 	if (!*ch) {
293 		g_critical("unsupported calc");
294 		return;
295 	}
296 
297 	ticalcs_update_set(*ch, emu->link_update);
298 	ticalcs_cable_attach(*ch, *cbl);
299 }
300 
301 /* Destroy calc handle */
end_link(TilemCalcEmulator * emu,CableHandle * cbl,CalcHandle * ch)302 void end_link(TilemCalcEmulator *emu, CableHandle *cbl, CalcHandle *ch)
303 {
304 	tilem_em_set_progress_title(emu, NULL);
305 
306 	ticalcs_cable_detach(ch);
307 	ticalcs_handle_del(ch);
308 	ticables_handle_del(cbl);
309 
310 	tilem_em_lock(emu);
311 }
312 
313 /**************** Error messages ****************/
314 
get_tilibs_error(int errcode)315 static char * get_tilibs_error(int errcode)
316 {
317 	char *p = NULL;
318 
319 	if (!ticalcs_error_get(errcode, &p)
320 	    || !ticables_error_get(errcode, &p)
321 	    || !tifiles_error_get(errcode, &p))
322 		return p;
323 	else
324 		return g_strdup_printf("Unknown error (%d)", errcode);
325 }
326 
get_ti81_error(int errcode)327 static char * get_ti81_error(int errcode)
328 {
329 	switch (errcode) {
330 	case TI81_ERR_FILE_IO:
331 		return g_strdup("File I/O error");
332 
333 	case TI81_ERR_INVALID_FILE:
334 		return g_strdup("Not a valid PRG file");
335 
336 	case TI81_ERR_MEMORY:
337 		return g_strdup("The calculator does not have enough free memory"
338 		                " to load the program.");
339 
340 	case TI81_ERR_SLOTS_FULL:
341 		return g_strdup("All calculator program slots are in use. "
342 		                " You must delete an existing program before"
343 		                " loading a new program.");
344 
345 	case TI81_ERR_BUSY:
346 		return g_strdup("The calculator is currently busy.  Please"
347 		                " exit to the home screen before loading"
348 		                " programs.");
349 
350 	default:
351 		return g_strdup_printf("Unknown error code (%d)", errcode);
352 	}
353 }
354 
show_error(TilemCalcEmulator * emu,const char * title,const char * message)355 void show_error(TilemCalcEmulator *emu,
356                        const char *title, const char *message)
357 {
358 	if (emu->ewin)
359 		messagebox11(emu->ewin->window, GTK_MESSAGE_ERROR,
360 		             "%s", title, "%s", message);
361 	else
362 		g_printerr("\n=== %s ===\n%s\n\n", title, message);
363 }
364 
365 /**************** Sending files ****************/
366 
367 /* Send a file to TI-81 */
send_file_ti81(TilemCalcEmulator * emu,struct TilemSendFileInfo * sf)368 static gboolean send_file_ti81(TilemCalcEmulator *emu, struct TilemSendFileInfo *sf)
369 {
370 	TI81Program *prgm = NULL;
371 	FILE *f;
372 	int errnum;
373 	sf->error_message = NULL;
374 
375 	f = g_fopen(sf->filename, "rb");
376 	if (!f) {
377 		sf->error_message = g_strdup_printf
378 			("Failed to open %s for reading: %s",
379 			 sf->display_name, g_strerror(errno));
380 		return FALSE;
381 	}
382 
383 	if (ti81_read_prg_file(f, &prgm)) {
384 		sf->error_message = g_strdup_printf
385 			("The file %s is not a valid TI-81 program file.",
386 			 sf->display_name);
387 		fclose(f);
388 		return FALSE;
389 	}
390 
391 	fclose(f);
392 
393 	tilem_em_wake_up(emu, TRUE);
394 
395 	prgm->info.slot = sf->slot;
396 	errnum = ti81_load_program(emu->calc, prgm);
397 	ti81_program_free(prgm);
398 
399 	if (errnum && !emu->task_abort)
400 		sf->error_message = get_ti81_error(errnum);
401 	return (errnum == 0);
402 }
403 
404 /* Get application name */
get_app_name(const FlashContent * flashc,char * name)405 static gboolean get_app_name(const FlashContent *flashc, char *name)
406 {
407 	int i;
408 	const unsigned char *data;
409 	unsigned int type, length;
410 
411 	if (flashc->num_pages < 1 || flashc->pages[0]->size < 6
412 	    || flashc->pages[0]->data[0] != 0x80
413 	    || flashc->pages[0]->data[1] != 0x0f)
414 		return FALSE;
415 
416 	i = 6;
417 	data = flashc->pages[0]->data;
418 	while (i < flashc->pages[0]->size && i < 128) {
419 		type = (data[i] << 8 | (data[i + 1] & 0xf0));
420 		length = data[i + 1] & 0x0f;
421 		i += 2;
422 
423 		if (length == 0x0d) {
424 			length = data[i];
425 			i++;
426 		}
427 		else if (length == 0x0e) {
428 			length = (data[i] << 8 | data[i + 1]);
429 			i += 2;
430 		}
431 		else if (length == 0x0f) {
432 			return FALSE;
433 		}
434 
435 		if (type == 0x8070)
436 			return FALSE;
437 
438 		if (type == 0x8040) {
439 			memcpy(name, data + i, length > 8 ? 8 : length);
440 			return TRUE;
441 		}
442 	}
443 	return FALSE;
444 }
445 
446 /* Try to delete an existing Flash app before we send a replacement */
try_delete_app(CalcHandle * ch,const FlashContent * flashc)447 static void try_delete_app(CalcHandle *ch, const FlashContent *flashc)
448 {
449 	VarRequest vr;
450 
451 	/* TI-73 does not support remote deletion */
452 	if (ch->model == CALC_TI73)
453 		return;
454 
455 	memset(&vr, 0, sizeof(VarRequest));
456 	if (!get_app_name(flashc, vr.name))
457 		return;
458 
459 	/* Why does this use type 0x14 and not 0x24?  I don't know. */
460 	vr.type = 0x14;
461 	ticalcs_calc_del_var(ch, &vr);
462 	/* if an error occurs, ignore it */
463 }
464 
465 /* Send a file using ticalcs2 */
send_file_linkport(TilemCalcEmulator * emu,struct TilemSendFileInfo * sf)466 static gboolean send_file_linkport(TilemCalcEmulator *emu, struct TilemSendFileInfo *sf)
467 {
468 	CalcModel model;
469 	FileClass cls;
470 	CableHandle *cbl;
471 	CalcHandle *ch;
472 	FileContent *filec;
473 	BackupContent *backupc;
474 	FlashContent *flashc;
475 	TigContent *tigc;
476 	CalcMode mode;
477 	int e;
478 	char *desc;
479 
480 	model = get_calc_model(emu->calc);
481 	cls = tifiles_file_get_class(sf->filename);
482 
483 	desc = g_strdup_printf("Sending %s", sf->display_name);
484 
485 	/* Read input file */
486 
487 	switch (cls) {
488 	case TIFILE_SINGLE:
489 	case TIFILE_GROUP:
490 	case TIFILE_REGULAR:
491 		filec = tifiles_content_create_regular(model);
492 		e = tifiles_file_read_regular(sf->filename, filec);
493 		if (!e) {
494 			begin_link(emu, &cbl, &ch, desc);
495 			if (sf->first)
496 				prepare_for_link_send(emu);
497 			mode = (sf->last ? MODE_SEND_LAST_VAR : MODE_NORMAL);
498 			e = ticalcs_calc_send_var(ch, mode, filec);
499 			end_link(emu, cbl, ch);
500 		}
501 		tifiles_content_delete_regular(filec);
502 		break;
503 
504 	case TIFILE_BACKUP:
505 		backupc = tifiles_content_create_backup(model);
506 		e = tifiles_file_read_backup(sf->filename, backupc);
507 		if (!e) {
508 			begin_link(emu, &cbl, &ch, desc);
509 			prepare_for_link_send(emu);
510 			e = ticalcs_calc_send_backup(ch, backupc);
511 			end_link(emu, cbl, ch);
512 		}
513 		tifiles_content_delete_backup(backupc);
514 		break;
515 
516 	case TIFILE_FLASH:
517 	case TIFILE_OS:
518 	case TIFILE_APP:
519 		flashc = tifiles_content_create_flash(model);
520 		e = tifiles_file_read_flash(sf->filename, flashc);
521 		if (!e) {
522 			begin_link(emu, &cbl, &ch, desc);
523 			ticables_options_set_timeout(cbl, 30 * 10);
524 			prepare_for_link_send(emu);
525 			if (tifiles_file_is_os(sf->filename))
526 				e = ticalcs_calc_send_os(ch, flashc);
527 			else if (tifiles_file_is_app(sf->filename)) {
528 				try_delete_app(ch, flashc);
529 				e = ticalcs_calc_send_app(ch, flashc);
530 			}
531 			else
532 				e = ticalcs_calc_send_cert(ch, flashc);
533 			end_link(emu, cbl, ch);
534 		}
535 		tifiles_content_delete_flash(flashc);
536 		break;
537 
538 	case TIFILE_TIGROUP:
539 		tigc = tifiles_content_create_tigroup(model, 0);
540 		e = tifiles_file_read_tigroup(sf->filename, tigc);
541 		if (!e) {
542 			begin_link(emu, &cbl, &ch, desc);
543 			prepare_for_link_send(emu);
544 			e = ticalcs_calc_send_tigroup(ch, tigc, TIG_ALL);
545 			end_link(emu, cbl, ch);
546 		}
547 		tifiles_content_delete_tigroup(tigc);
548 		break;
549 
550 	default:
551 		g_free(desc);
552 		sf->error_message = g_strdup_printf
553 			("The file %s is not a valid program or"
554 			 " variable file.",
555 			 sf->display_name);
556 		return FALSE;
557 	}
558 
559 	g_free(desc);
560 	if (e && !emu->task_abort)
561 		sf->error_message = get_tilibs_error(e);
562 	return (e == 0);
563 }
564 
send_file_main(TilemCalcEmulator * emu,gpointer data)565 gboolean send_file_main(TilemCalcEmulator *emu, gpointer data)
566 {
567 	struct TilemSendFileInfo *sf = data;
568 	/*emu->ilp.finished_cond = g_cond_new(); */
569 
570 	if (emu->calc->hw.model_id == TILEM_CALC_TI81)
571 		return send_file_ti81(emu, sf);
572 	else
573 		return send_file_linkport(emu, sf);
574 }
575 
send_file_finished(TilemCalcEmulator * emu,gpointer data,gboolean cancelled)576 static void send_file_finished(TilemCalcEmulator *emu, gpointer data,
577                                gboolean cancelled)
578 {
579 	struct TilemSendFileInfo *sf = data;
580 
581 	if (sf->error_message && !cancelled)
582 		show_error(emu, "Unable to send file", sf->error_message);
583 
584 	g_free(sf->filename);
585 	g_free(sf->display_name);
586 	g_free(sf->error_message);
587 	g_slice_free(struct TilemSendFileInfo, sf);
588 
589 	/*g_cond_broadcast(emu->ilp.finished_cond);*/
590 
591 
592 }
593 
tilem_link_send_file(TilemCalcEmulator * emu,const char * filename,int slot,gboolean first,gboolean last)594 void tilem_link_send_file(TilemCalcEmulator *emu, const char *filename,
595                           int slot, gboolean first, gboolean last)
596 {
597 	struct TilemSendFileInfo *sf;
598 
599 	sf = g_slice_new0(struct TilemSendFileInfo);
600 	sf->filename = g_strdup(filename);
601 	sf->display_name = g_filename_display_basename(filename);
602 	sf->slot = slot;
603 	sf->first = first;
604 	sf->last = last;
605 
606 	tilem_calc_emulator_begin(emu, &send_file_main,
607 	                          &send_file_finished, sf);
608 }
609 
610 /**************** Get directory listing ****************/
611 
612 /* Make a copy of a TilemVarEntry */
tilem_var_entry_copy(const TilemVarEntry * tve)613 TilemVarEntry *tilem_var_entry_copy(const TilemVarEntry *tve)
614 {
615 	TilemVarEntry *nve;
616 
617 	g_return_val_if_fail(tve != NULL, NULL);
618 
619 	nve = g_slice_new(TilemVarEntry);
620 	*nve = *tve;
621 
622 	if (tve->ve) {
623 		nve->ve = g_slice_new(VarEntry);
624 		*nve->ve = *tve->ve;
625 		nve->ve->data = g_memdup(tve->ve->data, tve->ve->size);
626 	}
627 	if (tve->name_str)
628 		nve->name_str = g_strdup(tve->name_str);
629 	if (tve->type_str)
630 		nve->type_str = g_strdup(tve->type_str);
631 	if (tve->slot_str)
632 		nve->slot_str = g_strdup(tve->slot_str);
633 	if (tve->file_ext)
634 		nve->file_ext = g_strdup(tve->file_ext);
635 	if (tve->filetype_desc)
636 		nve->filetype_desc = g_strdup(tve->filetype_desc);
637 
638 	return nve;
639 }
640 
641 /* Free a TilemVarEntry */
tilem_var_entry_free(TilemVarEntry * tve)642 void tilem_var_entry_free(TilemVarEntry *tve)
643 {
644 	g_return_if_fail(tve != NULL);
645 	if (tve->ve) {
646 		g_free(tve->ve->data);
647 		g_slice_free(VarEntry, tve->ve);
648 	}
649 	g_free(tve->name_str);
650 	g_free(tve->type_str);
651 	g_free(tve->slot_str);
652 	g_free(tve->file_ext);
653 	g_free(tve->filetype_desc);
654 	g_slice_free(TilemVarEntry, tve);
655 }
656 
657 struct dirlistinfo {
658 	GSList *list;
659 	char *error_message;
660 	gboolean aborted;
661 	gboolean no_gui;
662 };
663 
664 /* Convert tifiles VarEntry into a TilemVarEntry */
convert_ve(TilemCalcEmulator * emu,VarEntry * ve,gboolean is_flash)665 static TilemVarEntry *convert_ve(TilemCalcEmulator *emu, VarEntry *ve,
666                                  gboolean is_flash)
667 {
668 	TilemVarEntry *tve = g_slice_new0(TilemVarEntry);
669 	CalcModel tfmodel = get_calc_model(emu->calc);
670 	const char *model_str;
671 	const char *type_str;
672 	const char *fext;
673 
674 	tve->model = emu->calc->hw.model_id;
675 
676 	tve->ve = g_slice_new(VarEntry);
677 	*tve->ve = *ve;
678 	if (ve->data)
679 		tve->ve->data = g_memdup(ve->data, ve->size);
680 
681 	tve->size = ve->size;
682 	tve->archived = (ve->attr & ATTRB_ARCHIVED ? TRUE : FALSE);
683 	tve->can_group = TRUE;
684 
685 	tve->name_str = ticonv_varname_to_utf8(tfmodel, ve->name, ve->type);
686 	g_strchomp(tve->name_str);
687 	tve->type_str = g_strdup(tifiles_vartype2string(tfmodel, ve->type));
688 	fext = tifiles_vartype2fext(tfmodel, ve->type);
689 	tve->file_ext = g_ascii_strdown(fext, -1);
690 
691 	/* FIXME: the filetype_desc string is used as a description in
692 	   the file chooser.  It should be written in the same style
693 	   as other such strings (e.g., "TI-83 Plus programs" rather
694 	   than "TI83+ Program".)  But this is better than nothing. */
695 	model_str = tifiles_model_to_string(tfmodel);
696 	type_str = tifiles_vartype2type(tfmodel, ve->type);
697 	tve->filetype_desc = g_strdup_printf("%s %s", model_str, type_str);
698 
699 	tve->can_group = !is_flash;
700 
701 	return tve;
702 }
703 
704 /* Convert a complete directory listing */
convert_dir_list(TilemCalcEmulator * emu,GNode * root,gboolean is_flash,GSList ** list)705 static void convert_dir_list(TilemCalcEmulator *emu, GNode *root,
706                              gboolean is_flash, GSList **list)
707 {
708 	GNode *dir, *var;
709 	VarEntry *ve;
710 	TilemVarEntry *tve;
711 
712 	if (!root)
713 		return;
714 
715 	for (dir = root->children; dir; dir = dir->next) {
716 		for (var = dir->children; var; var = var->next) {
717 			ve = var->data;
718 			tve = convert_ve(emu, ve, is_flash);
719 			*list = g_slist_prepend(*list, tve);
720 		}
721 	}
722 }
723 
724 /* Request directory listing using ticalcs */
get_dirlist_silent(TilemCalcEmulator * emu,struct dirlistinfo * dl)725 static gboolean get_dirlist_silent(TilemCalcEmulator *emu,
726                                    struct dirlistinfo *dl)
727 {
728 	CableHandle *cbl;
729 	CalcHandle *ch;
730 	GNode *vars = NULL, *apps = NULL;
731 	GSList *list = NULL;
732 	int e = 0;
733 
734 	begin_link(emu, &cbl, &ch, "Reading variable list");
735 	prepare_for_link_receive(emu);
736 
737 	if (ticalcs_calc_features(ch) & OPS_DIRLIST) {
738 		e = ticalcs_calc_get_dirlist(ch, &vars, &apps);
739 		if (!e) {
740 			convert_dir_list(emu, vars, FALSE, &list);
741 			convert_dir_list(emu, apps, TRUE, &list);
742 		}
743 		ticalcs_dirlist_destroy(&vars);
744 		ticalcs_dirlist_destroy(&apps);
745 	}
746 
747 	end_link(emu, cbl, ch);
748 
749 	dl->list = g_slist_reverse(list);
750 
751 	dl->aborted = emu->task_abort;
752 
753 	if (e && !emu->task_abort)
754 		dl->error_message = get_tilibs_error(e);
755 	return (e == 0);
756 }
757 
758 /* Transfer variables non-silently using ticalcs */
get_dirlist_nonsilent(TilemCalcEmulator * emu,struct dirlistinfo * dl)759 static gboolean get_dirlist_nonsilent(TilemCalcEmulator *emu,
760                                       struct dirlistinfo *dl)
761 {
762 	CableHandle *cbl;
763 	CalcHandle *ch;
764 	FileContent *fc;
765 	VarEntry *head_entry;
766 	TilemVarEntry *tve;
767 	GSList *list = NULL;
768 	int e, i;
769 
770 	begin_link(emu, &cbl, &ch, "Receiving variables");
771 	prepare_for_link_receive(emu);
772 
773 	fc = tifiles_content_create_regular(ch->model);
774 	e = ticalcs_calc_recv_var_ns(ch, MODE_BACKUP, fc, &head_entry);
775 	if (!e) {
776 		for (i = 0; i < fc->num_entries; i++) {
777 			tve = convert_ve(emu, fc->entries[i], FALSE);
778 			list = g_slist_prepend(list, tve);
779 		}
780 	}
781 	if (head_entry)
782 		tifiles_ve_delete(head_entry);
783 	tifiles_content_delete_regular(fc);
784 
785 	end_link(emu, cbl, ch);
786 
787 	dl->list = g_slist_reverse(list);
788 
789 	dl->aborted = emu->task_abort;
790 
791 	if (e && !emu->task_abort)
792 		dl->error_message = get_tilibs_error(e);
793 	return (e == 0);
794 }
795 
796 /* Get TI-81 directory listing */
get_dirlist_ti81(TilemCalcEmulator * emu,struct dirlistinfo * dl)797 static gboolean get_dirlist_ti81(TilemCalcEmulator *emu,
798                                  struct dirlistinfo *dl)
799 {
800 	int i, slot;
801 	TI81ProgInfo info;
802 	GSList *list = NULL;
803 	TilemVarEntry *tve;
804 	int e;
805 
806 	tilem_em_wake_up(emu, TRUE);
807 
808 	for (i = 0; i <= TI81_SLOT_MAX; i++) {
809 		/* put Prgm0 after Prgm9, the way it appears in the menu */
810 		if (i < 9)
811 			slot = i + 1;
812 		else if (i == 9)
813 			slot = 0;
814 		else
815 			slot = i;
816 
817 		if ((e = ti81_get_program_info(emu->calc, slot, &info)))
818 			break;
819 
820 		if (info.size == 0)
821 			continue;
822 
823 		tve = g_slice_new0(TilemVarEntry);
824 		tve->model = TILEM_CALC_TI81;
825 		tve->slot = info.slot;
826 		tve->name_str = ti81_program_name_to_string(info.name);
827 		tve->slot_str = ti81_program_slot_to_string(info.slot);
828 		tve->file_ext = g_strdup("prg");
829 		tve->filetype_desc = g_strdup("TI-81 programs");
830 		tve->size = info.size;
831 		tve->archived = FALSE;
832 		tve->can_group = FALSE;
833 
834 		list = g_slist_prepend(list, tve);
835 	}
836 
837 	dl->list = g_slist_reverse(list);
838 
839 	if (e && !emu->task_abort)
840 		dl->error_message = get_ti81_error(e);
841 	return (e == 0);
842 }
843 
get_dirlist_main(TilemCalcEmulator * emu,gpointer data)844 static gboolean get_dirlist_main(TilemCalcEmulator *emu, gpointer data)
845 {
846 	switch (emu->calc->hw.model_id) {
847 	case TILEM_CALC_TI81:
848 		return get_dirlist_ti81(emu, data);
849 
850 	case TILEM_CALC_TI82:
851 	case TILEM_CALC_TI85:
852 		return get_dirlist_nonsilent(emu, data);
853 
854 	default:
855 		return get_dirlist_silent(emu, data);
856 	}
857 }
858 
get_dirlist_finished(TilemCalcEmulator * emu,gpointer data,gboolean cancelled)859 static void get_dirlist_finished(TilemCalcEmulator *emu, gpointer data,
860                                  gboolean cancelled)
861 {
862 	GSList *l;
863 	struct dirlistinfo *dl = data;
864 
865 	if (dl->error_message && !cancelled)
866 		show_error(emu, "Unable to receive variable list",
867 		           dl->error_message);
868 	else if (!cancelled && !dl->aborted && emu->ewin && !dl->no_gui) {
869 		if (!emu->rcvdlg)
870 			emu->rcvdlg = tilem_receive_dialog_new(emu);
871 		tilem_receive_dialog_update(emu->rcvdlg, dl->list);
872 		dl->list = NULL;
873 	}
874 
875 	if (!dl->no_gui && emu->rcvdlg)
876 		emu->rcvdlg->refresh_pending = FALSE;
877 
878 	for (l = dl->list; l; l = l->next)
879 		tilem_var_entry_free(l->data);
880 
881 	g_slist_free(dl->list);
882 	g_slice_free(struct dirlistinfo, dl);
883 }
884 
tilem_link_get_dirlist(TilemCalcEmulator * emu)885 void tilem_link_get_dirlist(TilemCalcEmulator *emu)
886 {
887 	struct dirlistinfo *dl = g_slice_new0(struct dirlistinfo);
888 	tilem_calc_emulator_begin(emu, &get_dirlist_main,
889 	                          &get_dirlist_finished, dl);
890 }
891 
892 /**************** Receiving files ****************/
893 
write_output(FileContent ** vars,FlashContent ** apps,const char * filename,gboolean output_tig,char ** error_message)894 static gboolean write_output(FileContent **vars, FlashContent **apps,
895                              const char *filename, gboolean output_tig,
896                              char **error_message)
897 {
898 	FileContent *group = NULL;
899 	TigContent *tig = NULL;
900 	int e, nvars, napps;
901 
902 	for (nvars = 0; vars && vars[nvars]; nvars++)
903 		;
904 	for (napps = 0; apps && apps[napps]; napps++)
905 		;
906 
907 	g_return_val_if_fail(nvars > 0 || napps > 0, FALSE);
908 
909 	if (output_tig) {
910 		e = tifiles_tigroup_contents(vars, apps, &tig);
911 		if (!e)
912 			e = tifiles_file_write_tigroup(filename, tig);
913 	}
914 	else if (nvars > 1 && napps == 0) {
915 		e = tifiles_group_contents(vars, &group);
916 		if (!e)
917 			e = tifiles_file_write_regular(filename, group, NULL);
918 	}
919 	else if (nvars == 0 && napps == 1) {
920 		e = tifiles_file_write_flash(filename, apps[0]);
921 	}
922 	else if (nvars == 1 && napps == 0) {
923 		e = tifiles_file_write_regular(filename, vars[0], NULL);
924 	}
925 	else {
926 		*error_message = g_strdup
927 			("Applications cannot be saved in an XXg group"
928 			 " file.  Try using TIG format or saving apps"
929 			 " individually.");
930 		return FALSE;
931 	}
932 
933 	if (e)
934 		*error_message = get_tilibs_error(e);
935 
936 	if (tig)
937 		tifiles_content_delete_tigroup(tig);
938 	if (group)
939 		tifiles_content_delete_regular(group);
940 
941 	return (e == 0);
942 }
943 
receive_files_silent(TilemCalcEmulator * emu,struct TilemReceiveFileInfo * rf)944 static gboolean receive_files_silent(TilemCalcEmulator* emu,
945                                      struct TilemReceiveFileInfo *rf)
946 {
947 	CableHandle *cbl;
948 	CalcHandle *ch;
949 	FileContent **vars, *filec;
950 	FlashContent **apps, *flashc;
951 	GSList *l;
952 	TilemVarEntry *tve;
953 	int e, i, nvars, napps;
954 
955 	g_return_val_if_fail(rf->entries != NULL, FALSE);
956 
957 	i = g_slist_length(rf->entries);
958 
959 	vars = g_new0(FileContent *, i + 1);
960 	apps = g_new0(FlashContent *, i + 1);
961 	nvars = napps = 0;
962 
963 	begin_link(emu, &cbl, &ch, "Receiving variables");
964 
965 	for (l = rf->entries; l; l = l->next) {
966 		tve = l->data;
967 
968 		if (tve->ve->type == tifiles_flash_type(ch->model)) {
969 			flashc = tifiles_content_create_flash(ch->model);
970 			e = ticalcs_calc_recv_app(ch, flashc, tve->ve);
971 			apps[napps++] = flashc;
972 		}
973 		else {
974 			filec = tifiles_content_create_regular(ch->model);
975 			e = ticalcs_calc_recv_var(ch, MODE_NORMAL,
976 			                          filec, tve->ve);
977 			vars[nvars++] = filec;
978 		}
979 		if (e)
980 			break;
981 	}
982 
983 	if (e && !emu->task_abort)
984 		rf->error_message = get_tilibs_error(e);
985 
986 	end_link(emu, cbl, ch);
987 
988 	if (!e)
989 		e = !write_output(vars, apps, rf->destination,
990 		                  rf->output_tig, &rf->error_message);
991 
992 	for (i = 0; i < nvars; i++)
993 		tifiles_content_delete_regular(vars[i]);
994 	for (i = 0; i < napps; i++)
995 		tifiles_content_delete_flash(apps[i]);
996 
997 	return (e == 0);
998 }
999 
receive_files_ti81(TilemCalcEmulator * emu,struct TilemReceiveFileInfo * rf)1000 static gboolean receive_files_ti81(TilemCalcEmulator* emu,
1001                                    struct TilemReceiveFileInfo *rf)
1002 {
1003 	TilemVarEntry *tve;
1004 	TI81Program *prgm = NULL;
1005 	int e;
1006 	FILE *f;
1007 	char *dname;
1008 
1009 	g_return_val_if_fail(rf->entries != NULL, FALSE);
1010 
1011 	if (rf->entries->next) {
1012 		rf->error_message = g_strdup
1013 			("TI-81 programs cannot be saved in a group file."
1014 			 " Try saving programs individually.");
1015 		return FALSE;
1016 	}
1017 
1018 	tve = rf->entries->data;
1019 	e = ti81_get_program(emu->calc, tve->slot, &prgm);
1020 	if (e) {
1021 		rf->error_message = get_ti81_error(e);
1022 		return FALSE;
1023 	}
1024 
1025 	f = g_fopen(rf->destination, "wb");
1026 	if (!f) {
1027 		e = errno;
1028 		dname = g_filename_display_basename(rf->destination);
1029 		rf->error_message = g_strdup_printf
1030 			("Failed to open %s for writing: %s",
1031 			 dname, g_strerror(e));
1032 		g_free(dname);
1033 		ti81_program_free(prgm);
1034 		return FALSE;
1035 	}
1036 
1037 	e = ti81_write_prg_file(f, prgm);
1038 	if (fclose(f) || e) {
1039 		e = errno;
1040 		dname = g_filename_display_basename(rf->destination);
1041 		rf->error_message = g_strdup_printf
1042 			("Error writing %s: %s",
1043 			 dname, g_strerror(e));
1044 		g_free(dname);
1045 		ti81_program_free(prgm);
1046 		return FALSE;
1047 	}
1048 
1049 	ti81_program_free(prgm);
1050 	return TRUE;
1051 }
1052 
receive_files_nonsilent(TilemCalcEmulator * emu,struct TilemReceiveFileInfo * rf)1053 static gboolean receive_files_nonsilent(TilemCalcEmulator *emu,
1054                                         struct TilemReceiveFileInfo *rf)
1055 {
1056 	const TilemVarEntry *tve;
1057 	FileContent **vars, *fc;
1058 	int i, nvars;
1059 	GSList *l;
1060 	gboolean status;
1061 
1062 	nvars = g_slist_length(rf->entries);
1063 
1064 	vars = g_new0(FileContent *, nvars + 1);
1065 	i = 0;
1066 	for (l = rf->entries; l; l = l->next) {
1067 		tve = l->data;
1068 		g_return_val_if_fail(tve->ve != NULL, FALSE);
1069 		g_return_val_if_fail(tve->ve->data != NULL, FALSE);
1070 
1071 		/* avoid copying variable data */
1072 		fc = tifiles_content_create_regular(get_calc_model(emu->calc));
1073 		fc->entries = g_new(VarEntry *, 1);
1074 		fc->num_entries = 1;
1075 		fc->entries[0] = tve->ve;
1076 		vars[i++] = fc;
1077 	}
1078 
1079 	status = write_output(vars, NULL, rf->destination, rf->output_tig,
1080 	                      &rf->error_message);
1081 
1082 	for (i = 0; i < nvars; i++) {
1083 		if (vars[i]) {
1084 			vars[i]->num_entries = 0;
1085 			g_free(vars[i]->entries);
1086 			vars[i]->entries = NULL;
1087 			tifiles_content_delete_regular(vars[i]);
1088 		}
1089 	}
1090 	g_free(vars);
1091 
1092 	return status;
1093 }
1094 
receive_files_main(TilemCalcEmulator * emu,gpointer data)1095 static gboolean receive_files_main(TilemCalcEmulator *emu, gpointer data)
1096 {
1097 	struct TilemReceiveFileInfo *rf = data;
1098 	TilemVarEntry *tve;
1099 
1100 	g_return_val_if_fail(rf->entries != NULL, FALSE);
1101 
1102 	tve = rf->entries->data;
1103 
1104 	if (emu->calc->hw.model_id == TILEM_CALC_TI81)
1105 		return receive_files_ti81(emu, rf);
1106 	else if (tve->ve && tve->ve->data)
1107 		return receive_files_nonsilent(emu, rf);
1108 	else
1109 		return receive_files_silent(emu, rf);
1110 }
1111 
receive_files_finished(TilemCalcEmulator * emu,gpointer data,gboolean cancelled)1112 static void receive_files_finished(TilemCalcEmulator *emu, gpointer data,
1113                                    gboolean cancelled)
1114 {
1115 	struct TilemReceiveFileInfo *rf = data;
1116 	GSList *l;
1117 
1118 	if (rf->error_message && !cancelled)
1119 		show_error(emu, "Unable to save file", rf->error_message);
1120 
1121 	g_free(rf->destination);
1122 	g_free(rf->error_message);
1123 	for (l = rf->entries; l; l = l->next)
1124 		tilem_var_entry_free(l->data);
1125 	g_slist_free(rf->entries);
1126 	g_slice_free(struct TilemReceiveFileInfo, rf);
1127 }
1128 
tilem_link_receive_group(TilemCalcEmulator * emu,GSList * entries,const char * destination)1129 void tilem_link_receive_group(TilemCalcEmulator *emu,
1130                               GSList *entries,
1131                               const char *destination)
1132 {
1133 	struct TilemReceiveFileInfo *rf;
1134 	GSList *l;
1135 	TilemVarEntry *tve;
1136 	const char *p;
1137 	gboolean output_tig = FALSE;
1138 
1139 	g_return_if_fail(emu != NULL);
1140 	g_return_if_fail(emu->calc != NULL);
1141 	g_return_if_fail(entries != NULL);
1142 	g_return_if_fail(destination != NULL);
1143 
1144 	for (l = entries; l; l = l->next) {
1145 		tve = l->data;
1146 		g_return_if_fail(emu->calc->hw.model_id == tve->model);
1147 	}
1148 
1149 	p = strrchr(destination, '.');
1150 	if (p && (!g_ascii_strcasecmp(p, ".tig")
1151 	          || !g_ascii_strcasecmp(p, ".zip")))
1152 		output_tig = TRUE;
1153 
1154 	rf = g_slice_new0(struct TilemReceiveFileInfo);
1155 	rf->destination = g_strdup(destination);
1156 	rf->output_tig = output_tig;
1157 
1158 	tve = entries->data;
1159 	if (tve->ve && tve->ve->data) {
1160 		/* non-silent: we already have the data; save the file
1161 		   right now */
1162 		rf->entries = entries;
1163 		receive_files_nonsilent(emu, rf);
1164 		rf->entries = NULL;
1165 		receive_files_finished(emu, rf, FALSE);
1166 	}
1167 	else {
1168 		/* silent: retrieve and save data in background */
1169 		for (l = entries; l; l = l->next) {
1170 			tve = tilem_var_entry_copy(l->data);
1171 			rf->entries = g_slist_prepend(rf->entries, tve);
1172 		}
1173 		rf->entries = g_slist_reverse(rf->entries);
1174 
1175 		tilem_calc_emulator_begin(emu, &receive_files_main,
1176 		                          &receive_files_finished, rf);
1177 	}
1178 }
1179 
tilem_link_receive_file(TilemCalcEmulator * emu,const TilemVarEntry * varentry,const char * destination)1180 void tilem_link_receive_file(TilemCalcEmulator *emu,
1181                              const TilemVarEntry *varentry,
1182                              const char* destination)
1183 {
1184 	GSList *l;
1185 	l = g_slist_prepend(NULL, (gpointer) varentry);
1186 	tilem_link_receive_group(emu, l, destination);
1187 	g_slist_free(l);
1188 }
1189 
1190 /**************** Receive matching files ****************/
1191 
1192 struct recmatchinfo {
1193 	char *pattern;
1194 	char *destdir;
1195 	struct dirlistinfo *dl;
1196 	struct TilemReceiveFileInfo *rf;
1197 };
1198 
receive_matching_main(TilemCalcEmulator * emu,gpointer data)1199 static gboolean receive_matching_main(TilemCalcEmulator *emu, gpointer data)
1200 {
1201 	struct recmatchinfo *rm = data;
1202 	GSList *l, *selected = NULL;
1203 	TilemVarEntry *tve;
1204 	char *defname, *defname_r, *defname_l;
1205 	GPatternSpec *pat;
1206 	gboolean status = TRUE;
1207 
1208 	if (!get_dirlist_main(emu, rm->dl))
1209 		return FALSE;
1210 
1211 	/* Find variables that match the pattern */
1212 
1213 	pat = g_pattern_spec_new(rm->pattern);
1214 
1215 	for (l = rm->dl->list; l; l = l->next) {
1216 		tve = l->data;
1217 
1218 		defname = get_default_filename(tve);
1219 		defname_r = g_utf8_normalize(defname, -1, G_NORMALIZE_NFKD);
1220 
1221 		if (g_pattern_match(pat, strlen(defname_r), defname_r, NULL))
1222 			selected = g_slist_prepend(selected, tve);
1223 
1224 		g_free(defname);
1225 		g_free(defname_r);
1226 	}
1227 
1228 	g_pattern_spec_free(pat);
1229 
1230 	if (!selected) {
1231 		rm->rf->error_message = g_strdup_printf
1232 			("Variable %s not found", rm->pattern);
1233 		return FALSE;
1234 	}
1235 
1236 	/* Receive variables */
1237 
1238 	selected = g_slist_reverse(selected);
1239 
1240 	for (l = selected; l; l = l->next) {
1241 		tve = l->data;
1242 
1243 		g_free(rm->rf->destination);
1244 
1245 		defname = get_default_filename(tve);
1246 		defname_l = utf8_to_filename(defname);
1247 		rm->rf->destination = g_build_filename(rm->destdir,
1248 		                                       defname_l, NULL);
1249 		g_free(defname);
1250 		g_free(defname_l);
1251 
1252 		rm->rf->entries = g_slist_prepend(NULL, tve);
1253 		status = receive_files_main(emu, rm->rf);
1254 		g_slist_free(rm->rf->entries);
1255 		rm->rf->entries = NULL;
1256 
1257 		if (!status)
1258 			break;
1259 	}
1260 
1261 	g_slist_free(selected);
1262 
1263 	return status;
1264 }
1265 
receive_matching_finished(TilemCalcEmulator * emu,gpointer data,gboolean cancelled)1266 static void receive_matching_finished(TilemCalcEmulator *emu, gpointer data,
1267                                       gboolean cancelled)
1268 {
1269 	struct recmatchinfo *rm = data;
1270 
1271 	get_dirlist_finished(emu, rm->dl, cancelled);
1272 	receive_files_finished(emu, rm->rf, cancelled);
1273 	g_free(rm->pattern);
1274 	g_free(rm->destdir);
1275 	g_slice_free(struct recmatchinfo, rm);
1276 }
1277 
1278 /* Receive variables with names matching a pattern.  PATTERN is a
1279    glob-like pattern in UTF-8.  Files will be written out to
1280    DESTDIR. */
tilem_link_receive_matching(TilemCalcEmulator * emu,const char * pattern,const char * destdir)1281 void tilem_link_receive_matching(TilemCalcEmulator *emu,
1282                                  const char *pattern,
1283                                  const char *destdir)
1284 {
1285 	struct recmatchinfo *rm;
1286 
1287 	g_return_if_fail(emu != NULL);
1288 	g_return_if_fail(pattern != NULL);
1289 	g_return_if_fail(destdir != NULL);
1290 
1291 	rm = g_slice_new0(struct recmatchinfo);
1292 	rm->pattern = g_utf8_normalize(pattern, -1, G_NORMALIZE_NFKD);
1293 	rm->destdir = g_strdup(destdir);
1294 
1295 	rm->dl = g_slice_new0(struct dirlistinfo);
1296 	rm->dl->no_gui = TRUE;
1297 	rm->rf = g_slice_new0(struct TilemReceiveFileInfo);
1298 
1299 	tilem_calc_emulator_begin(emu, &receive_matching_main,
1300 	                          &receive_matching_finished, rm);
1301 }
1302