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(¤t_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(¤t_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