Free Hero Mesh

Check-in [3d7617dc1f]
Login
This is a mirror of the main repository for Free Hero Mesh. New tickets and changes will not be accepted at this mirror.
Overview
Comment:Implement the "portable app" mode.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 3d7617dc1f0ac620e99ccf344e0d16a1c83f86e6
User & Date: user on 2021-05-23 20:24:40
Other Links: manifest | tags
Context
2021-05-23
23:23
Implement portable compilation mode, to compile it for "portable app" use. check-in: ec088682c3 user: user tags: trunk
20:24
Implement the "portable app" mode. check-in: 3d7617dc1f user: user tags: trunk
02:22
Implement a key binding command to record the solution even if auto recording solutions is not enabled. check-in: 1a2fee8a44 user: user tags: trunk
Changes

Modified TODO from [f6f521f828] to [a0fc23867b].

28
29
30
31
32
33
34
35
36
37
38
39
40
41
* Bookmarks
* Message trace menu to enable/disable
* Bugs
  * Figure out why the $SeekerCloser class doesn't seem to work properly
  * Level 232 of SUPERHRO puzzle set (the Lava shouldn't expand? why?)
* Display solution comments/timestamp
* VCR mode
* Portable mode, not needing installing files in home directory
  * Maybe not needed? You can override the HOME environment variable
  * An alternative is to check argv[0]; if it contains / then don't use HOME
* Command-line switch for batch import/export levels
* SQL
  * Implement the GROUP column in the CLASSES table
  * Allow multiple SQL statements in one binding







<
<
<




28
29
30
31
32
33
34



35
36
37
38
* Bookmarks
* Message trace menu to enable/disable
* Bugs
  * Figure out why the $SeekerCloser class doesn't seem to work properly
  * Level 232 of SUPERHRO puzzle set (the Lava shouldn't expand? why?)
* Display solution comments/timestamp
* VCR mode



* Command-line switch for batch import/export levels
* SQL
  * Implement the GROUP column in the CLASSES table
  * Allow multiple SQL statements in one binding

Modified commandline.doc from [64526b83d0] to [f6413bcb62].

36
37
38
39
40
41
42




43
44
45
46
47
48
49

-e
  Start in the editor instead of game.

-f
  Only flush the user cache and then terminate.





-n
  Create a new puzzle set. The .xclass and .class files should already
  exist; the .level and .solution files should not already exist, and will
  be created with minimal data.

-p
  Start the picture editor.







>
>
>
>







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

-e
  Start in the editor instead of game.

-f
  Only flush the user cache and then terminate.

-h
  Disable portable mode; always use the HOME environment variable to find
  the configuration and database files.

-n
  Create a new puzzle set. The .xclass and .class files should already
  exist; the .level and .solution files should not already exist, and will
  be created with minimal data.

-p
  Start the picture editor.
85
86
87
88
89
90
91




















.x0
  Disable loading SQLite extensions.

.x1
  Enable loading SQLite extensions.



























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

.x0
  Disable loading SQLite extensions.

.x1
  Enable loading SQLite extensions.


=== Portable mode ===

Free Hero Mesh may run either in "home mode" or in "portable mode". The
following rules are used to determine which mode to use:

1. If the -h switch is present, use home mode.

2. If the HEROMESH_PREFIX environment variable is present, use portable
mode; its value is the file name prefix (the ".heromeshsession" and
".heromeshrc" names are appended directly to this value, so if it does
not end with a slash, it will be a prefix to the file name too).

3. If argv[0] contains a forward slash, use portable mode; it will use
files "current.heromeshrc" and "current.heromeshsession" in the directory
where the executable file is allegedly found.

4. Otherwise, home mode is used.

Modified main.c from [6c4bf4088e] to [00e076bcbf].

63
64
65
66
67
68
69

70
71
72
73
74
75
76

static const char*globalclassname;
static SDL_Cursor*cursor[77];
static FILE*levelfp;
static FILE*solutionfp;
static sqlite3_int64 leveluc,solutionuc;
static sqlite3_stmt*readusercachest;


static sqlite3_int64 reset_usercache(FILE*fp,const char*nam,struct stat*stats,const char*suffix) {
  sqlite3_stmt*st;
  sqlite3_int64 t,id;
  char buf[128];
  int i,z;
  if(z=sqlite3_prepare_v2(userdb,"DELETE FROM `USERCACHEDATA` WHERE `FILE` = (SELECT `ID` FROM `USERCACHEINDEX` WHERE `NAME` = ?1);",-1,&st,0)) {







>







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

static const char*globalclassname;
static SDL_Cursor*cursor[77];
static FILE*levelfp;
static FILE*solutionfp;
static sqlite3_int64 leveluc,solutionuc;
static sqlite3_stmt*readusercachest;
static char*hpath;

static sqlite3_int64 reset_usercache(FILE*fp,const char*nam,struct stat*stats,const char*suffix) {
  sqlite3_stmt*st;
  sqlite3_int64 t,id;
  char buf[128];
  int i,z;
  if(z=sqlite3_prepare_v2(userdb,"DELETE FROM `USERCACHEDATA` WHERE `FILE` = (SELECT `ID` FROM `USERCACHEINDEX` WHERE `NAME` = ?1);",-1,&st,0)) {
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
  sqlite3_config(SQLITE_CONFIG_SMALL_MALLOC,(int)boolxrm(v,0));
  optionquery[1]=Q_sqlCoveringIndexScan;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"";
  sqlite3_config(SQLITE_CONFIG_COVERING_INDEX_SCAN,(int)boolxrm(v,1));
  if(sqlite3_initialize()) fatal("Failure to initialize SQLite.\n");
  optionquery[1]=Q_sqlFile;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2);
  if(v && *v) {
    s=sqlite3_mprintf("%s",v);
  } else {
    v=getenv("HOME")?:".";
    s=sqlite3_mprintf("%s%s.heromeshsession",v,v[strlen(v)-1]=='/'?"":"/");
  }
  if(!s) fatal("Allocation failed\n");
  if(z=sqlite3_open(s,&userdb)) fatal("Failed to open user database %s (%s)\n",s,userdb?sqlite3_errmsg(userdb):sqlite3_errstr(z));
  sqlite3_free(s);
  optionquery[1]=Q_sqlExtensions;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"";
  sqlite3_db_config(userdb,SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,*v?1:0,&z);
  if(*v) {
    p=s=strdup(v);
    if(!s) fatal("Allocation failed\n");
    while(*v) {







|
<
<
<
<
<
<
|
<







542
543
544
545
546
547
548
549






550

551
552
553
554
555
556
557
  sqlite3_config(SQLITE_CONFIG_SMALL_MALLOC,(int)boolxrm(v,0));
  optionquery[1]=Q_sqlCoveringIndexScan;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"";
  sqlite3_config(SQLITE_CONFIG_COVERING_INDEX_SCAN,(int)boolxrm(v,1));
  if(sqlite3_initialize()) fatal("Failure to initialize SQLite.\n");
  optionquery[1]=Q_sqlFile;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2);
  if(!v || !*v) strcpy(hpath+strlen(hpath)-2,"session"),v=hpath;






  if(z=sqlite3_open(v,&userdb)) fatal("Failed to open user database %s (%s)\n",v,userdb?sqlite3_errmsg(userdb):sqlite3_errstr(z));

  optionquery[1]=Q_sqlExtensions;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"";
  sqlite3_db_config(userdb,SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,*v?1:0,&z);
  if(*v) {
    p=s=strdup(v);
    if(!s) fatal("Allocation failed\n");
    while(*v) {
586
587
588
589
590
591
592
593
594















595
596





597
598
599
600
601
602
603
604
605
606
607
608
}

void set_cursor(int id) {
  id>>=1;
  if(!cursor[id]) cursor[id]=SDL_CreateCursor((void*)cursorimg+(id<<6),(void*)cursorimg+(id<<6)+32,16,16,cursorhot[id]>>4,cursorhot[id]&15);
  SDL_SetCursor(cursor[id]);
}

static void load_options(void) {















  const char*home=getenv("HOME")?:".";
  char*nam=malloc(strlen(home)+16);





  FILE*fp;
  sprintf(nam,"%s%s.heromeshrc",home,home[strlen(home)-1]=='/'?"":"/");
  fp=fopen(nam,"r");
  if(!fp) fatal("Failed to open %s (%m)\n",nam);
  free(nam);
  if(xrm_load(resourcedb,fp,1)) fatal("Error while loading .heromeshrc\n");
  fclose(fp);
}

static void read_options(int argc,char**argv) {
  xrm_db*db=xrm_sub(resourcedb,0,xrm_make_quark(globalclassname,0)?:xrm_anyq);
  while(argc--) xrm_load_line(db,*argv++,1);








|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>
>

<
|
|
<







580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611

612
613

614
615
616
617
618
619
620
}

void set_cursor(int id) {
  id>>=1;
  if(!cursor[id]) cursor[id]=SDL_CreateCursor((void*)cursorimg+(id<<6),(void*)cursorimg+(id<<6)+32,16,16,cursorhot[id]>>4,cursorhot[id]&15);
  SDL_SetCursor(cursor[id]);
}

static void set_path(const char*arg) {
  const char*s;
  if(main_options['h']) goto home;
  if((s=getenv("HEROMESH_PREFIX")) && *s) {
    hpath=malloc(strlen(s)+32);
    if(!hpath) fatal("Allocation failed\n");
    sprintf(hpath,"%s.heromeshrc",s);
    return;
  }
  if(s=strrchr(arg,'/')) {
    hpath=malloc(s+64-arg);
    if(!hpath) fatal("Allocation failed\n");
    sprintf(hpath,"%.*s/current.heromeshrc",(int)(s-arg),arg);
    return;
  }
  home:
  s=getenv("HOME")?:".";
  hpath=malloc(strlen(s)+32);
  if(!hpath) fatal("Allocation failed\n");
  sprintf(hpath,"%s%s.heromeshrc",s,s[strlen(s)-1]=='/'?"":"/");
}

static void load_options(void) {
  FILE*fp;

  fp=fopen(hpath,"r");
  if(!fp) fatal("Failed to open %s (%m)\n",hpath);

  if(xrm_load(resourcedb,fp,1)) fatal("Error while loading .heromeshrc\n");
  fclose(fp);
}

static void read_options(int argc,char**argv) {
  xrm_db*db=xrm_sub(resourcedb,0,xrm_make_quark(globalclassname,0)?:xrm_anyq);
  while(argc--) xrm_load_line(db,*argv++,1);
887
888
889
890
891
892
893
894



895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913

914
915
916
917
918
919
920
  if(main_options['n']) {
    if(main_options['r']) fatal("Switches -r and -n are conflicting\n");
    main_options['x']=1;
  }
  if(main_options['a']) main_options['r']=main_options['x']=1;
  if(main_options['p']) main_options['r']=1;
  if(main_options['f']) main_options['x']=1;
  if(!main_options['c']) load_options();



  if(argc>optind) read_options(argc-optind,argv+optind);
  *optionquery=xrm_make_quark(globalclassname,0)?:xrm_anyq;
#ifdef __GNUC__
  stack_protect_mark=__builtin_frame_address(0);
  set_stack_protection();
#endif
  if(main_options['c']) {
    load_classes();
    return 0;
  }
  init_sql();
  load_key_bindings();
  init_screen();
  if(main_options['p']) {
    run_picture_editor();
    return 0;
  }
  load_pictures();
  if(main_options['T']) {

    test_mode();
    return 0;
  }
  init_usercache();
  if(main_options['n']) return 0;
  load_classes();
  load_level_index();







|
>
>
>



















>







899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
  if(main_options['n']) {
    if(main_options['r']) fatal("Switches -r and -n are conflicting\n");
    main_options['x']=1;
  }
  if(main_options['a']) main_options['r']=main_options['x']=1;
  if(main_options['p']) main_options['r']=1;
  if(main_options['f']) main_options['x']=1;
  if(!main_options['c']) {
    set_path(argv[0]);
    load_options();
  }
  if(argc>optind) read_options(argc-optind,argv+optind);
  *optionquery=xrm_make_quark(globalclassname,0)?:xrm_anyq;
#ifdef __GNUC__
  stack_protect_mark=__builtin_frame_address(0);
  set_stack_protection();
#endif
  if(main_options['c']) {
    load_classes();
    return 0;
  }
  init_sql();
  load_key_bindings();
  init_screen();
  if(main_options['p']) {
    run_picture_editor();
    return 0;
  }
  load_pictures();
  if(main_options['T']) {
    printf("argv[0] = %s\n",argv[0]);
    test_mode();
    return 0;
  }
  init_usercache();
  if(main_options['n']) return 0;
  load_classes();
  load_level_index();

Modified man6/heromesh.6 from [4391f87609] to [ed5c74f628].

27
28
29
30
31
32
33



34
35
36
37
38
39
40
Only load classes and then terminate.
This can be used to verify that the class definition file does not contain syntax errors.
You can also use it with some other options to display details.
.IP -e
Start the level editor.
.IP -f
Only flush the user cache and then terminate.



.IP -n
Create a new puzzle set.
The class definition file and image set must already exist.
.IP -p
Start the picture editor.
.IP -r
Open in read-only mode.







>
>
>







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Only load classes and then terminate.
This can be used to verify that the class definition file does not contain syntax errors.
You can also use it with some other options to display details.
.IP -e
Start the level editor.
.IP -f
Only flush the user cache and then terminate.
.IP -h
Disable portable mode;
always use the HOME environment variable to find the files.
.IP -n
Create a new puzzle set.
The class definition file and image set must already exist.
.IP -p
Start the picture editor.
.IP -r
Open in read-only mode.
52
53
54
55
56
57
58


59
60





61
62
63
64












The configuration file, in X resource manager format.
.TP
.I ~/.heromeshsession
A SQLite database containing user cache data.
.TP
.I \*[Document_Dir]/*.doc
Documentation.


.SH ENVIRONMENT
.TP





.B HOME
The home directory; used to find .heromeshrc and .heromeshsession files.
.PP
Any of the SDL environment variables can also be used.



















>
>


>
>
>
>
>




>
>
>
>
>
>
>
>
>
>
>
>
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
The configuration file, in X resource manager format.
.TP
.I ~/.heromeshsession
A SQLite database containing user cache data.
.TP
.I \*[Document_Dir]/*.doc
Documentation.
.PP
The files it expects to find in the home directory may be looked for elsewhere if portable mode is used.
.SH ENVIRONMENT
.TP
.B HEROMESH_PREFIX
If defined, force portable mode.
This value is used as the prefix to find the files.
(It may include a file name prefix as well as a directory path.)
.TP
.B HOME
The home directory; used to find .heromeshrc and .heromeshsession files.
.PP
Any of the SDL environment variables can also be used.
.SH "PORTABLE MODE"
There is a "portable mode" available.
The following rules are used to determine if portable mode should be used:
.PP
1. If the -h switch is specified, use home mode.
.PP
2. If the HEROMESH_PREFIX environment variable is nonempty, use portable mode.
.PP
3. If argv[0] contains a forward slash, use portable mode.
In this case, the files are named "current.heromeshrc" and "current.heromeshsession" in the directory where the executable file is found.
.PP
4. Otherwise, use home mode.