/******************************************************************************
*
* project name:    TIGCC Tools Suite
* file name:       ttstart.c
* initial date:    14/08/2000
* author:          thomas.nussbaumer@gmx.net
* description:     start utility for exepacked and/or normal executables
*
* =============================================================================
* IMPORTANT NOTE
* =============================================================================
*
* This program uses many really dirty tricks to overcome some restrictions of
* the AMS. Don't take it as example how to code (dirty tricks are NOT smart!)
*
* The special RETURN_VALUE treatment exit is, of course, the most dirty part.
* It's correctnes can ONLY be guaranteed for TIGCC 0.93 with optimization
* setting -Os !!!!
*
* And: Don't modify the source when you haven't completely understood it!
*
*
* =============================================================================
* Usage info - normal build (tigcc -Os ttstart.c)
* =============================================================================
*
* When ttstart is build "normally" it can be used to start either exepacked or
* normal executables on TI-89/TI-92plus running AMS versions up to 2.05.
* To start a program (for example: myprog.ppg or myprog.ASM) just type:
* ttstart("myprog")
* (NOTE: the quotes around the program name!)
* If you want to hand-over some commandline parameters to the started program
* just specify them after the programname like this: ttstart("myprog",10,20)
* The program name itself is removed from the estack by ttstart. The started
* program will only see the additionally parameters of ttstart.
*
* Additionally:
*
* ttstart is the first available launcher which handles return values >>from the
* started program to the AMS correctly.
*
*
* =============================================================================
* customizing ttstart (generating own, special purpose launchers)
* =============================================================================
*
* -----------------------------------------------------------------------------
* (1) using a predefined filename
* -----------------------------------------------------------------------------
*
* if you want to build a launcher which automatically starts a predefined
* program you can use the TIGCC commandline option -DCUSTOM_NAME=name_of_file
* For example: if you want to build a launcher with name mylaunch which should
* automatically start myprog.ppg compile ttstart like this:
*
* tigcc -Os -DCUSTOM_NAME=myprog -o mylaunch
*
* After uploading mylaunch and myprog to your calc you can simply start myprog
* by just typing: mylaunch()
* You can also hand-over any parameter to myprog. For example: mylaunch(10,20)
* starts myprog with parameter 10 and 20 on the estack.
*
* -----------------------------------------------------------------------------
* (2) customizing the type of program (normal or exepacked)
* -----------------------------------------------------------------------------
*
* It doesn't make much sense to build a launcher with a predefined filename,
* but without customizing the type of the program, too.
* If you build a launcher for your program and the program is NOT exepacked,
* the launcher will still contain the unpacking code (around 1200 bytes) which
* is not necessary. Therefore you can use commandline option -DONLY_PPG
* for exepacked programs or -DONLY_ASM for normal program.
*
* -------- Example 1 - launcher for myprog.ppg --------
*
* tigcc -Os -DCUSTOM_NAME=myprog -DONLY_PPG -o mylaunch
*
* -------- Example 2 - launcher for myprog.asm --------
*
* tigcc -Os -DCUSTOM_NAME=myprog -DONLY_ASM -o mylaunch
*
* -----------------------------------------------------------------------------
* (3) memory leak detection support (-DLEAKWATCH)
* -----------------------------------------------------------------------------
*
* When you compile ttstart with option -DLEAKWATCH it will check if the
* available memory gets changed by the started program. If the memory is changed
* it will output the difference in the status line (negative number == less
* memory available after program run / positive number == more memory available
* after program run).
*
* Not each report is a real memory leak of the started program. If a program
* adds or deletes variables, or if it manipulates the home screen by adding
* or removing items from it, a change of available memory  will be, of course,
* reported by ttstart, too. There are several other possibilities for "legal"
* changes of the available memory, but if your program ONLY uses malloc()'s and
* free()'s it's very likely that you have a real memory leak.
*
*
* =============================================================================
* TIGCC -pack option (how to make a new pstarter.o file)
* =============================================================================
*
* Since version 0.90 TIGCC features option -pack which generates automatically
* exepacked programs including a custom launcher program.
* The object code of the custom launcher (pstarter.o) used by TIGCC can be
* found in directory include/c of your TIGCC installation.
* pstarter.o can be easily generated from ttstart.c:
*
* [since TIGCC 0.93 no longer an assembly file but an object file is used]
*
* (1) compile ttstart.c this way:
*     tigcc -c -Os -DCUSTOM_NAME=tempprog -DONLY_PPG ttstart.c
* (2) rename generated ASM file ttstart.o into pstarter.o
* (3) backup old pstarter.o from directory include/c of your TIGCC
*     installation
* (4) copy the newly generated file pstarter.o over the old one
*     in directory include/c of your TIGCC installation
*
*
* =============================================================================
* How to generate exepacked programs
* =============================================================================
*
* The simplest way to generate an exepack program is, of course, the -pack
* option of TIGCC (examine the TIGCC docs).
* The second way to generate an exepack program from ANY executable (even from
* kernel-dependent programs) is to use tool ttppggen from the TIGCC Tools
* Suite.
*
*
* =============================================================================
* CREDITS
* =============================================================================
*
* (1) Zeljko Juric, for the "normal launcher" code from the TIGCC-FAQ and the
*     code which overcomes the RETURN_VALUE problem
* (2) Kevin Kofler, for the idea how to handle RETURN_VALUE in the context of a
*     launcher correctly, for the new version of own_enter_ghost_space and
*     other modifications related to TIGCC 0.93
* (3) Greg Dietsche, for various suggestions (normal program handling etc.)
* (4) Sebastian Reichelt, for fixing the comments
* (5) Olivier Armand (ExtendeD) for the necessary modifications to support
*     kernel-dependent exepacked prgs and for the EX_patch replacement
* (6) Me (Thomas Nussbaumer), for anything else
*
* $Id: ttstart.c,v 1.17 2002/05/27 07:54:09 tnussb Exp $
*
******************************************************************************/

//-----------------------------------------------------------------------------
// uncomment the following line to use an own relocation routine instead
// of EX_patch call. The own relocation routine is added just for the case
// EX_patch would break again in followup AMS versions.
//-----------------------------------------------------------------------------

//#define USE_OWN_EXPATCH

#define USE_TI89       // compile for TI89
#define USE_TI92P      // compile for TI92p
#define USE_V200       // compile for V200

#define MIN_AMS 100         // works on all AMS versions
#define NO_EXIT_SUPPORT     // the launcher doesn't need exit support
#define NO_CALC_DETECT      // it doesn't need calc detection, either
#define OPTIMIZE_ROM_CALLS  // generates smaller code by reserving a5

//-----------------------------------------------------------------------------
// DIRTY: This is needed to get everything to work with "complex main"s
//        (Kevin Kofler)
//-----------------------------------------------------------------------------
asm("/* Include Patch: save_the_sp */");
extern void *__save__sp__;

#include <statline.h>
#include <alloc.h>
#include <vat.h>
#include <string.h>
#include <system.h>
#include <error.h>

#ifndef CUSTOM_NAME
#include <args.h>
#endif

//-----------------------------------------------------------------------------
// the following variable is used to protect a launcher from launching itself
// NOTE, that this is only enabled for the commandline version and NOT for
// customized launchers, because I think that a programmer KNOWS what he is
// doing and building a customized launcher to launch itself doesn't make
// ANY sense!
//-----------------------------------------------------------------------------
#ifndef CUSTOM_NAME
short its_me_checking = 0;
#endif

#ifndef CUSTOM_NAME
#define fatal(s)         {its_me_checking=0; ST_showHelp(s); return;}
#define fatal_without(s) {ST_showHelp(s); return;}
#else
#define fatal(s) {ST_showHelp(s); return;}
#endif

//-----------------------------------------------------------------------------
// if -DONLY_ASM and -DONLY_PPG is given undefine both (trivial)
//-----------------------------------------------------------------------------
#ifdef ONLY_ASM
#ifdef ONLY_PPG
#undef ONLY_ASM
#undef ONLY_PPG
#endif
#endif

#ifndef ONLY_ASM
#include "ttunpack.h"    // errorcodes definition
#endif

//-----------------------------------------------------------------------------
// we use an own implementation of enter_ghost_space, because due to the
// arrangements within TIGCCLIB (comes as real compile-time library since 2.4)
// otherwise we cannot generate a compatible pstarter.o anymore
// (the launcher generated with TIGCC option -pack will not be linked against
// tigcc.a)
//-----------------------------------------------------------------------------
void own_enter_ghost_space(void);
asm("own_enter_ghost_space:\n"
"	bsr.w 0f\n"
"0:\n"
"	move.l (%sp)+,%d0\n"
"	cmpi.l #0x40000,%d0\n"
"	bcc.s 2f\n"
"	movea.l 0xC8,%a0\n"
"	cmpi.l #1000,(%a0,-4)\n"
"	bcs.s 2f\n"
"	movem.l %a2-%a6/%d3-%d7,-(%sp)\n"
"	lea (%sp,-20),%sp\n"
"	move.l #0x3E000,%a3\n"
"	move.l %a0,%d0\n"
"	andi.l #0x600000,%d0\n"
"	addi.l #0x20000,%d0\n"
"	move.l %d0,(%sp,12)\n"
"	move.l %d0,(%sp,16)\n"
"	trap #0xC\n"
"	move.w #0x2700,%sr\n"
"	move.l #0xF,%d3\n"
"	pea 1f(%pc)\n"
"	bset.b #2,(%sp,1)\n"
"	clr.w  -(%sp)\n"
"	move.l 0xAC,%a0\n"
"	jmp (%a0)\n"
"1:\n"
"	lea (%sp,20),%sp\n"
"	movem.l (%sp)+,%a2-%a6/%d3-%d7\n"
"2:\n"
"	move.l (%sp)+,%d0\n"
"	bset.l #18,%d0\n"
"	movea.l %d0,%a0\n"
"	jmp (%a0)");


//-----------------------------------------------------------------------------
// The following macro calls function fptr and treats a possible RETURN_VALUE
// correctly. If a return value is available mode is set to 0, otherwise 1
//
// NOTE: the NOPs are very important due to a dirty hack in the TIGCCLIB with
//       define RETURN_VALUE (they must be exactly after the call!!)
//-----------------------------------------------------------------------------

#define ASM_CALL_WITH_RETURN_VALUE_HANDLING(fptr,mode) ({\
        asm volatile ("movem.l %%d1-%%d7/%%a0-%%a6,-(%%sp);"\
            "jsr (%1);"\
            "bra.s 0f;"\
            "nop;nop;"\
            "clr.b %%d0;"\
            "bra.s 1f;"\
            "0: st.b %%d0;"\
            "1: movem.l (%%sp)+,%%d1-%%d7/%%a0-%%a6;"\
            "move.b %%d0,%0"\
            : "=d" (mode) : "a" ((void *)(fptr)) : "d0","cc","memory");})


#if defined(USE_OWN_EXPATCH)

//-----------------------------------------------------------------------------
// Replacement of EX_patch call implemented as smart ASM macro
// (original code by Extended, modified to become a smart ASM macro by tnussb)
//-----------------------------------------------------------------------------
#define EX_PATCH(base_addr,tag_ptr) ({\
    asm volatile (\
        "0: move.b -(%1),-1(%%a7)\n"\
        "   move.b -(%1),-(%%a7)\n"\
        "   moveq  #0,%%d0\n"\
        "   move.w (%%a7)+,%%d0\n"\
        "   beq.s  1f\n"\
        "   move.b -(%1),-1(%%a7)\n"\
        "   move.b -(%1),-(%%a7)\n"\
        "   moveq  #0,%%d1\n"\
        "   move.w (%%a7)+,%%d1\n"\
        "   add.l  %0,%%d1\n"\
        "   move.l %%d1,0(%0,%%d0)\n"\
        "   bra.s  0b\n1:" \
        :: "a" ((void*)(base_addr)),"a" ((void*)(tag_ptr)) : "d0","d1");})

#endif

//-----------------------------------------------------------------------------
// embedded version string -- EDITED BY HAND!
//
// NOTE: this version string is embedded in ALL variants and its should !!NOT!!
//       be removed by anyone
//
// to force the compiler to embed the string in all case this variable is
// volatile by intention!
//-----------------------------------------------------------------------------
volatile const char embedded_ttstart_version[]="[tts117]";


//=============================================================================
// nothing special, just a huge main routine ...
//=============================================================================
void _main(void) {
    unsigned char*         src;
    unsigned char*         dest;
    unsigned short         length;
    char                   tmpstr[50];
    const char*            fname;
    SYM_ENTRY*             symptr;
    HANDLE                 h,uncomp_h=0;
    volatile unsigned char mode;
#ifndef ONLY_PPG
    unsigned short         asm_prg  = 0;
    unsigned short         do_alloc = 1;
#endif

#ifndef CUSTOM_NAME
    // check if "we" are already active (the same copy of this executable) and
    // if yes exit immediately !!
    if (its_me_checking) {fatal_without("can't start myself");}
    else                 its_me_checking = 1;  // now we are active ...
#endif



#ifdef LEAKWATCH
    long freemem = (long)HeapAvail();
#endif

#ifndef CUSTOM_NAME
    //-------------------------------------------------------------------------
    // Here comes the first "DIRTY, DIRTY, DONT-DO-IT-NORMALLY" hack:
    //
    // We are not using a copy of the top_estack pointer, but we operate
    // directly on the top_estack pointer itself. This way the called program
    // will not "see" its own program name on the estack !!!!
    //
    // NOTE: due to the "nature" of the estack it is not necessary to restore
    //       the top_estack pointer to its original value
    //-------------------------------------------------------------------------
    if (GetArgType(top_estack) != STR_TAG) {
        fatal("use: ttstart(\"name\")");
    }
    fname = GetStrnArg(top_estack);

    // suggestion from Greg Dietsche: if given filename contains a '(' truncate
    // the string there. This becomes useful if someone uses a auto-completion
    // TSR
    if ((src = strchr(fname,'('))) *src = 0;
#else
    //-------------------------------------------------------------------------
    // if you can figure out why the next lines are that strange, then you are
    // really good in C programming ...
    //-------------------------------------------------------------------------
    #define TOSTR(x) #x
    #define XTOSTR(x) TOSTR(x)

    fname = XTOSTR(CUSTOM_NAME);
#endif

    //-------------------------------------------------------------------------
    // setup TIOS name for variable and try to find it in the current folder ..
    //-------------------------------------------------------------------------
    tmpstr[0] = 0;
    strcpy(tmpstr+1,fname);
    if (!(symptr = DerefSym(SymFind(tmpstr+1+strlen(fname))))) {
        sprintf(tmpstr,"%s not found",fname);
        fatal(tmpstr);
    }

    //-------------------------------------------------------------------------
    // IMPORTANT: only use handle (symptr may become invalid !!!)
    //-------------------------------------------------------------------------
    h = symptr->handle;

    //-------------------------------------------------------------------------
    // if file is already locked, don't lock it again ...
    //-------------------------------------------------------------------------
    if (HeapGetLock(h)) {
        src = HeapDeref(h);
        h   = 0;
    }
    else {
        src = HLock(h);
    }

    if (!src) fatal("locking failed"); // will return!

    length = *(unsigned short*)src+3;
    dest   = src+length-2;

#ifndef ONLY_PPG
    //-------------------------------------------------------------------------
    // check if we want to start a normal ASM program
    //-------------------------------------------------------------------------
    if (*dest == 0xF3) {
        asm_prg = 1;
        dest    = src;
        length--;
        if (!symptr->flags.bits.archived && !symptr->flags.bits.locked) do_alloc = 0;
    }
#endif

#ifdef ONLY_ASM
    else {
        if (h) HeapUnlock(h);
        fatal("no ASM prg"); // will return!
    }

#else

    //-------------------------------------------------------------------------
    // check if given program is not even a packed file
    //-------------------------------------------------------------------------
#ifndef ONLY_PPG
    else {
#endif
        //---------------------------------------------------------------------
        // skip "length of variable" bytes
        //---------------------------------------------------------------------
        src += 2;
        if (*dest     != 0xF8 ||
            *(dest-4) != 'p'  ||
            *(dest-3) != 'p'  ||
            *(dest-2) != 'g'  ||
            !ttunpack_valid(src))
        {
            if (h) HeapUnlock(h);
#ifndef ONLY_PPG
            fatal("neither ASM nor PPG prg");
#else
            fatal("no PPG prg");
#endif
        }
        //---------------------------------------------------------------------
        // get the original size and allocate memory for it
        //---------------------------------------------------------------------
        length = ttunpack_size(src);
#ifndef ONLY_PPG
    }
#endif

#endif


#ifndef ONLY_PPG
    if (do_alloc) {
#endif

        // NOTE: we don't use malloc, but the low level routines
        //       otherwise compressed kernel-dependent progs will crash
        if (!(uncomp_h=HeapAllocHigh(length))) {
            if (h) HeapUnlock(h);
            fatal("out of memory");
        }

        dest = HeapDeref(uncomp_h);

#ifndef ONLY_PPG
        if (asm_prg) {
            memcpy(dest,src,length);
        }
#endif

#ifndef ONLY_ASM

    #ifndef ONLY_PPG
        else {
    #endif
            ST_showHelp("decompressing ...");

            if (ttunpack_decompress(src,dest)) {
                //-------------------------------------------------------------
                // "houston we got problems ..."
                //-------------------------------------------------------------
                HeapFree(uncomp_h);
                if (h) HeapUnlock(h);
                fatal("failed");
            }
            ST_eraseHelp();
    #ifndef ONLY_PPG
        }
    #endif
    #ifndef ONLY_PPG
    }
    #endif

#else
    }
#endif

    //-------------------------------------------------------------------------
    // relocate the executable, enter the ghostspace and start it ...
    //-------------------------------------------------------------------------
    // FIXED EX_patch call for upcoming AMS 2.06 (Thanx, Kevin for the fix)

#if defined(USE_OWN_EXPATCH)
    EX_PATCH(dest+0x40002,dest+length+0x3FFFF);
#else
    EX_patch(dest+0x40002,dest+length+0x3FFFF);
#endif
    own_enter_ghost_space();
    {(*(unsigned long *)__save__sp__)|=0x40000;}
    TRY
        ASM_CALL_WITH_RETURN_VALUE_HANDLING(dest+0x40002,mode);
    ONERR
        ERD_dialog(errCode,FALSE);
        mode = 1;
    ENDTRY

#ifndef ONLY_PPG
    if (src!=dest)
#endif
        HeapFree(uncomp_h);

    if (h) HeapUnlock(h);

#ifdef LEAKWATCH
    if ((freemem=((long)HeapAvail())-freemem) != 0) {
        sprintf(tmpstr, "mem changed by %ld", freemem);
        ST_showHelp(tmpstr);
    }
#endif

#ifndef CUSTOM_NAME
    its_me_checking = 0; // we are not active anymore ...
#endif

    if (!mode) {
        //---------------------------------------------------------------------
        // launched program uses RETURN_VALUE - handle it correctly
        // (VERY dirty trick implemented Kevin Kofler
        //---------------------------------------------------------------------
        {
            if (*(((unsigned short **)__save__sp__)[2])==0x21ee) ((unsigned short **)__save__sp__)[2]+=3;
            else                                                 ((unsigned short **)__save__sp__)[2]+=2;
        }
    }
}

//#############################################################################
//###################### NO MORE FAKES BEYOND THIS LINE #######################
//#############################################################################
//
//=============================================================================
// Revision History
//=============================================================================
//
// $Log: ttstart.c,v $
// Revision 1.17  2002/05/27 07:54:09  tnussb
// exception handling added (thanx to Sebastian Reichelt)
//
// Revision 1.16  2002/05/22 09:01:38  tnussb
// embedded version string set to tts116 to reflect the "stepping back to old
// unpacking function" modification
//
// Revision 1.15  2002/04/17 10:35:10  tnussb
// documentation extended
//
// Revision 1.14  2002/03/13 15:01:42  tnussb
// (1) some messages shortened
// (2) using now new and minor size optimized unpacking function
// (3) some comments added
//
// Revision 1.13  2002/03/04 08:51:03  tnussb
// Extended's relocation routine as smart ASM macro added (just for the case
// that EX_patch() will "break" again)
//
// Revision 1.12  2002/02/28 08:53:18  tnussb
// EX_patch called fixed to be prepared for new EX_patch version coming with
// AMS 2.06
//
// Revision 1.11  2002/02/07 09:57:58  tnussb
// many changes like new bootstrapping, new enter_ghost_space routine and,
// of course, kernel-dependend PPG support
//
// Revision 1.9  2001/07/31 22:56:59  Thomas Nussbaumer
// generic commit for Tools Suite version 0.99.10
//
// Revision 1.8  2001/02/04 16:45:40  Thomas Nussbaumer
// using new ttunpack.h version
//
// Revision 1.7  2000/11/18 16:00:18  Thomas Nussbaumer
// enter_ghost_space() added
//
// Revision 1.6  2000/10/01 14:56:52  Thomas Nussbaumer
// support for custom start utility build added
//
// Revision 1.5  2000/08/30 20:01:55  Thomas Nussbaumer
// using now assembler optimized version of unpack routine
//
// Revision 1.4  2000/08/30 19:44:24  Thomas Nussbaumer
// TABs replaced by spaces and trailing spaces removed
//
// Revision 1.3  2000/08/20 15:28:22  Thomas Nussbaumer
// including of unpack.c done now before including ttunpack.h
//
// Revision 1.2  2000/08/16 22:29:05  Thomas Nussbaumer
// (1) based now on shared file unpack.c
// (2) argument can be specified on commandline
// (3) define and code for time measurement integrated
//
// Revision 1.1  2000/08/14 22:48:38  Thomas Nussbaumer
// initial version
//
//


