Source file: /~heha/ewa/Motor/cdesk.zip/manedit.cpp

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include <commctrl.h>
#include <malloc.h>
#include <stdio.h>
#include <tchar.h>
#include <richedit.h>
#pragma comment(linker,"/NOD /OPT:NOWIN98")
#pragma intrinsic(memset)
#define elemof(x) (sizeof(x)/sizeof(*(x)))
enum{
  MB_SOUND	=0x80000000,
};

static HINSTANCE hInstance;
static HWND hMainWnd,hDialog;
static HWND hToolbar,hEdit,hStatus;
static HWND hFind;
static UINT msgFind;
static FINDREPLACE findreplace;
static TCHAR findstr[80],replacestr[80];
static HMENU hMainMenu;
static TCHAR MBoxTitle[64];
static TCHAR ExeName[MAX_PATH];
static TCHAR BakName[MAX_PATH];
static TCHAR CustFilter[64];
static int filetype;	// 1 = Exe, 2 = UTF8-Text

static int vMBox(HWND parent,ULONG_PTR id, UINT flags, va_list va) {
  TCHAR s[256],tbuf[256],*t=tbuf;
  if (IS_INTRESOURCE(id)) LoadString(hInstance,(UINT)id,tbuf,elemof(tbuf));
  else t=(TCHAR*)id;
  s[_vsntprintf(s,elemof(s)-1,t,va)]=0;
  if (flags&MB_SOUND) MessageBeep(flags&MB_ICONMASK);
  return MessageBox(parent,s,MBoxTitle,flags&~MB_SOUND);
}

static int _cdecl MBox(HWND parent,ULONG_PTR id,UINT flags,...) {
  va_list va;
  va_start(va,flags);
  int r=vMBox(parent,id,flags,va);
  va_end(va);
  return r;
}

template<class T> int StrReplace(T*s,int slen,int buflen,int idel, int ldel, const T*sins,int lins);

static int StrReplace<char>(char*s,int slen,int buflen,int idel, int ldel, const char*sins,int lins) {
  if (buflen<4) return 0;
  if (slen<0) slen=lstrlenA(s)+1;
  if ((unsigned)idel>=(unsigned)slen) return 0;
  if (lins<0) lins=lstrlenA(sins);
  int move=lins-ldel;	// amount of grow
  if ((unsigned)(slen+move)>(unsigned)buflen) return 0;
  MoveMemory(s+idel+lins,s+idel+ldel,slen-idel-ldel);
  CopyMemory(s+idel,sins,lins);
  return slen+move;
}

static int StrReplace<WCHAR>(WCHAR*s,int slen,int buflen,int idel, int ldel, const WCHAR*sins,int lins) {
  if (buflen<4) return 0;
  if (slen<0) slen=lstrlenW(s)+1;
  if ((unsigned)idel>=(unsigned)slen) return 0;
  if (lins<0) lins=lstrlenW(sins);
  int move=lins-ldel;	// amount of grow
  if ((unsigned)(slen+move)>(unsigned)buflen) return 0;
  MoveMemory(s+idel+lins,s+idel+ldel,(slen-idel-ldel)*2);
  CopyMemory(s+idel,sins,lins*2);
  return slen+move;
}

struct Node{
  static HANDLE hHeap;
  enum type_t{		//scope		sub			name	value
    root,		//0..len	PI,Element,Comment	0	0
    Element,		//<tag..>	Attr;Element,Text,Comm.	tag	0 if collapsed, between > and </ when split
    Attr,		//tag="value"	0			tag	value
    Text,		//text		Entity			0	text without surrounding whitespace
    CDATASection,	//<![CDATA[var]]> 0			0	var
    EntityReference,	//?
    Entity,		//&tag;		0			tag	0
    ProcessingInstruction,//<?tag ?>	Attr			tag	between tag and ?>
    Comment,		//<!-- -->	0			0	between <!-- and -->
    Document,		//HTML only
    DocumentType,	//<!DOCTYPE >
    DocumentFragment,	//HTML only
    Notation		//?
  }type;
  int depth;
  CHARRANGE scope,name,value;
  Node*next,*prev,*parent,*sub;
  static void*operator new(size_t sz) {return HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sz);}
  static void operator delete(void*) {}	// simply don't delete
  Node(type_t t=root):type(t),depth(0) {next=prev=this;}
//  Node(type_t t,int a, int e):type(t),depth(0) {next=prev=this; scope.cpMin=a; scope.cpMax=e;}
//  int build(int);
//  Node*&lastsub() {if (sub) {Node**p=&sub; while ((*p)->next) p=&((*p)->next); return *p;} return sub;}
  void add_child_back(Node*n) {if (sub) sub->add_sibling_back(n); else{sub=n;n->parent=this;n->depth=depth+1;}}
//  Node*last() {Node*p=this; while (p->next) p=p->next; return p;}
  void add_sibling_back(Node*n) {n->parent=parent; n->next=this; prev->next=n; n->prev=prev; prev=n;n->depth=depth;}
  void add_sibling_front(Node*n) {add_sibling_back(n); n->parent->sub=n;}
  bool isChild(Node*n) const {return n->parent==this;}
};

struct Xml {
  Node*root;
  char*buf;	// UTF-8
  int len,bufsize;
// Unsauber: Wie bekommt man ein operator new mit hHeap in Xml hin??
  Xml() {Node::hHeap=HeapCreate(HEAP_NO_SERIALIZE,0,0);root=new Node;}
  Xml(HWND,int=0);
  int parse();	// linearisierte Version!
  Node*addAttr(Node*p,const char*tag,int tlen,const char*val,int vlen=-1,Node*before=0);
  Node*addAttr(Node*p,const char*tag,const char*val,Node*before=0) {return addAttr(p,tag,-1,val,-1,before);}
  Node*addElement(Node*p,const char*tag,int tlen=-1,Node*before=0/*,Node::type_t t=Node::Element*/);
  Node*setElement(Node*p,const char*tag,bool* =0);	// Kind von p finden oder anhängen
  Node*addPI(const char*tag,int tlen=-1,Node*before=0);	// always to root node
  Node*addText(Node*p,const char*txt,int tlen=-1,Node*before=0);
  void splitElement(Node*el);	// prepare for text and subelement insertion
  void unsplitElement(Node*el);	// after text and subelement deletion
  bool deleteNode(Node*n);
  void show(HWND) const;
  bool shift(int from,int by);	// Text ab Index um Delta (einfügen, löschen) sowie Nodes anpassen
  bool parseAttr(Node*el,int&i);
  ~Xml() {HeapDestroy(Node::hHeap);}
  int compareText(CHARRANGE&t1,CHARRANGE&t2) const {return compareText(t1,t2.cpMin,t2.cpMax);}
  int compareText(CHARRANGE&t1,int a,int e) const {return compareText(t1,buf+a,e-a);}
  int compareText(CHARRANGE&t1,const char*t2) const {return compareText(t1,t2,strlen(t2));}
  int compareText(CHARRANGE&t1,const char*t2,int len) const {return compareText(buf+t1.cpMin,t1.cpMax-t1.cpMin,t2,len);}
  static int compareText(const char*t1,int l1,const char*t2,int l2);
  Node*findElement(Node*r,const char*tag) const {return findNode(r,Node::Element,tag);}
  Node*findNode(Node*r,Node::type_t t,const char*tag=0) const;
  bool enumNodes(Node*r,Node::type_t t,const char*tag,bool(*)(const Xml&,Node*,void*),void*) const;
  Node*findChildNode(Node*p,Node::type_t t,const char*tag,int tlen=-1) const;
private:
  static bool stop_and_save(const Xml&,Node*n,void*param) {*(Node**)param=n; return false;}
  struct shift_t{
    int from,by;
    operator()(CHARRANGE&r)	{if (r.cpMin>=from) r.cpMin+=by; if (r.cpMax>from) r.cpMax+=by;}
  };
  static bool onShift(const Xml&,Node*n,void*param);
};

int Xml::compareText(const char*t1,int l1,const char*t2,int l2){
  if (l1!=l2) return -1;
  return memcmp(t1,t2,l1);
}


bool Xml::onShift(const Xml&,Node*n,void*param) {
  shift_t&sh=*(shift_t*)param;
  sh(n->scope);
  sh(n->name);
  sh(n->value);
  return true;
}

bool Xml::shift(int from,int by) {
  if ((unsigned)from>(unsigned)len) return false;	// start index too large
  if (!by) return true;			// do nothing
  if (len+by>bufsize) return false;	// expansion too large
  if (len+by<from) return false;	// shrink too much: New <len> would be less than <from>
  if (by<0) memmove(buf+from,buf+from-by,len-from+by);
  else{
    memmove(buf+from+by,buf+from,len-from);
#ifdef _DEBUG
    memset(buf+from,'X',by);		// fill gap
#endif
  }
  len+=by;
  shift_t sh={from,by};
  enumNodes(root,Node::type_t(-1),0,onShift,&sh);
  return true;
}

// el = Element node newly created
// i = position in buf (onto whitespace or ">" or "/>" or "?>")
// On return: i onto ">" or "/>" or "?>" or erraneous character
// attribute nodes added to el->sub
bool Xml::parseAttr(Node*el,int&i) {
  while (i<len) {
    if (buf[i]<=' ') {++i; continue;}	// skip whitespace
    switch (buf[i]) {
      case '>': return true;
      case '?':
      case '/': return i<len-1 && buf[i+1]=='>';
    }
    Node*k=new Node(Node::Attr);
    k->scope.cpMin=k->name.cpMin=i;
    while (i<len) {
      char c=buf[i];
      if (c>' ' && c!='=' && c!='>' && c!='?') {++i; continue;}
      k->name.cpMax=i;
      if (c=='=') {
	char q=buf[++i];
	if (q!='"' && q!='\'') return false;	// must be in single or double quotes for XML, not for HTML
	k->value.cpMin=++i;
	for(;i<len && buf[i]!=q;i++);
	if (i==len) return false;	// end of input without closing quote
	k->value.cpMax=i++;
      }
      k->scope.cpMax=i;
      break;
    }
    el->add_child_back(k);
  }
  return false;				// end of input without ">"
}

int Xml::parse() {
  root->scope.cpMax=len;
  Node*cur=root;
  for (int i=0; i<len;) {
    if (buf[i]<=' ') {++i; continue;}
    if (buf[i]=='<') switch (buf[++i]) {
      case '/': {
        if (cur->type!=Node::Element) return i;
	int k=++i;	// save start of string
	while (i<len && buf[i]>' ' && buf[i]!='>') ++i;	// locate end of tag
	if (k!=i	// Not only "</>"
	&& compareText(cur->name,k,i)) return k;	// Error! Tag must be the same
	cur->value.cpMax=k-2;	// where “innerHTML” ends
	while (i<len && buf[i]!='>') ++i;	// locate end marker (Bug: skipping false attributes)
	if (i==len) return i;	// No end marker
	cur->scope.cpMax=++i;	// now end of scope available
	cur=cur->parent;	// ascend
      }break;
      case '?': {
        Node*n=new Node(Node::ProcessingInstruction);
	n->scope.cpMin=i-1;
	n->name.cpMin=++i;
	for(;i<len;i++) {
	  char c=buf[i];
	  if (c<=' ' || c=='>' || c=='/' || c=='?') break;
	}
	n->name.cpMax=i;
	if (!parseAttr(n,i)) return i;
	if (buf[i]!='?') return i;	// wrong termination
	n->scope.cpMax=(i+=2);
	cur->add_child_back(n);
      }break;
      case '!': {
        Node*n=new Node(Node::Comment);
	n->scope.cpMin=i-1;
	if (i>len-6) return i;		// no space for "-- -->"
	if (buf[++i]!='-') return i;
	if (buf[++i]!='-') return i;
	n->value.cpMin=++i;
	for (; i<len-2; i++) {
	  if (buf[i]=='-' && buf[i+1]=='-' && buf[i+2]=='>') break;
	}
	if (i==len-2) return i;		// missing termination
	n->value.cpMax=i;
	n->scope.cpMax=(i+=3);
	cur->add_child_back(n);
      }break;
      default: {
        Node*n=new Node(Node::Element);
	n->scope.cpMin=i-1;
	n->name.cpMin=i;
	for(;i<len;i++) {
	  char c=buf[i];
	  if (c<=' ' || c=='>' || c=='/' || c=='?') break;
	}
	n->name.cpMax=i;
	if (!parseAttr(n,i)) return i;
	char c=buf[i];
	if (c=='?') return i;		// not allowed end marker
	cur->add_child_back(n);
	if (c=='/') {
	  n->scope.cpMax=(i+=2);
	  break;
	}
	n->value.cpMin=++i;	// where “innerHTML” starts
	cur=n;	// descend
      }
    }else{
      if (cur->type!=Node::Element) return i;	// root-level text not allowed
      Node*n=new Node(Node::Text);
      n->scope.cpMin=i;
      while (i<len && (buf[i]<=' ' || buf[i]=='<')) ++i;
      n->value.cpMin=i;
      while (i<len && buf[i]!='<') ++i;
      n->scope.cpMax=i;
      int j=i;
      while (buf[--j]<=' ');
      n->value.cpMax=max(j+1,n->value.cpMin);
      cur->add_child_back(n);
    }
  }
  if (cur->type!=Node::root) return i;	// Not at root at end of input
  return 0;	// okay
}

Node*Xml::findNode(Node*r,Node::type_t t,const char*tag) const{
  Node*ret=0;
  enumNodes(r,t,tag,stop_and_save,&ret);
  return ret;
}

bool Xml::enumNodes(Node*r,Node::type_t t,const char*tag,bool(*cb)(const Xml&,Node*,void*),void*param) const{
  if (!r) return true;
  Node*n=r;
  do{
    if ((t==-1 || n->type==t) && (!tag || !compareText(n->name,tag)) && !cb(*this,n,param)) return false;
    if (!enumNodes(n->sub,t,tag,cb,param)) return false;	// recurse through sub-tree
    n=n->next;
  }while (n!=r);	// stop when all siblings are checked
  return true;
}

Node*Xml::findChildNode(Node*p,Node::type_t t,const char*tag,int tlen) const{
  if (tlen<0) tlen=strlen(tag);
  Node*n0=p->sub,*n=n0;
  if (!n0) return 0;
  do{
    if ((t==-1 || n->type==t) && (!compareText(n->name,tag,tlen))) return n;
    n=n->next;
  }while (n!=n0);	// stop when all siblings are checked
  return 0;
}

HANDLE Node::hHeap;

Xml::Xml(HWND hEdit,int bs) {
  Node::hHeap=HeapCreate(HEAP_NO_SERIALIZE,0,0);
  root=new Node;
  int lw=SendMessage(hEdit,WM_GETTEXTLENGTH,0,0);
  TCHAR*sw=(TCHAR*)_alloca((lw+1)*sizeof(TCHAR));
  GetWindowText(hEdit,sw,lw+1);
  len=WideCharToMultiByte(CP_UTF8,0,sw,lw,0,0,0,0);
  if (bs<len) bs=len;
  bufsize=bs;
  buf=(char*)Node::operator new(bs);	// nicht nullterminiert!
  WideCharToMultiByte(CP_UTF8,0,sw,lw,const_cast<char*>(buf),len,0,0);
//  if (bs>len) buf[len]=0;		// nullterminiert wenn Platz
}

void Xml::show(HWND hEdit) const{
  int lw=MultiByteToWideChar(CP_UTF8,0,buf,len,0,0);
  TCHAR*sw=(TCHAR*)_alloca((lw+1)*sizeof(TCHAR));
  MultiByteToWideChar(CP_UTF8,0,buf,len,sw,lw);
  sw[lw]=0;
  Edit_SetModify(hEdit,true);
  SetWindowText(hEdit,sw);	// EN_CHANGE enabled dann den Apply-Menüpunkt - denkste!! Nicht bei RichEdit!
  Edit_SetModify(hEdit,true);
}

bool Xml::deleteNode(Node*n) {
  if (n->type==Node::root) return false;
  int a=n->scope.cpMin;
  int e=n->scope.cpMax;
  if (buf[e]==' ') ++e;
  if (buf[e]=='\r') ++e;
  if (buf[e]=='\n') ++e;
  Node*prv=n->prev,*nxt=n->next;
  prv->next=nxt;	// may write to same node when there is only one sibling
  nxt->prev=prv;
  if (n->parent->sub==n) n->parent->sub=nxt!=n?nxt:0;
  delete n;
  return shift(a,a-e);
}

//Leeres Elternelement der Form <tag/>\n oder <tag attr="value"/>\n aufteilen in <tag>\n</tag>\n bzw. <tag attr="value">\n</tag>\n.
//Dann erst kann das Kindelement (außer Attr) eingefügt werden.
void Xml::splitElement(Node*el) {
  if (el->type!=Node::Element) return;
  if (el->value.cpMin) return;	// already split (value is “innerHTML”)
  int a=el->scope.cpMax-2;	// onto "/>"
  int b=el->name.cpMax-el->name.cpMin;
  if (!shift(a,b+3)) return;	// make gap, modifying el->scope.cpMax
  _snprintf(buf+a,b+5,"> </%.*s>",b,buf+el->name.cpMin);
  el->value.cpMin=a+1;		// onto " "
  el->value.cpMax=a+2;		// onto "</tag>"
}

void Xml::unsplitElement(Node*el) {
  if (el->type!=Node::Element) return;
  if (!el->value.cpMin) return;	// already unsplit
  Node*q,*p=el->sub;
  if (q=p) do{
    if (q->type!=Node::Attr) return;	// has non-attribute child
  }while((q=q->next)!=p);		// until end of list
  int a=el->value.cpMin+1;	// one character after ">": Let space for "/>"
  int b=el->scope.cpMax-a;
  shift(a,-b);
  _snprintf(buf+a-2,2,"/>");
  el->value.cpMax=el->value.cpMin=0;	// mark as unsplit form
}

Node*Xml::addElement(Node*p,const char*tag,int tlen,Node*before) {
  if (!p) p=root;
  if (!(p->type==Node::Element || p->type==Node::root)) return 0;	// parent must be element or root
  if (before) {
    if (before->parent!=p) return 0;	// <before> must be direct child of <p>
    if (before->type==Node::Attr) return 0;	// cannot be an attribute
  }
  if (tlen<0) tlen=strlen(tag);
  splitElement(p);
  int a=before?before->scope.cpMin:p->sub&&p->sub->prev->type!=Node::Attr?p->sub->prev->scope.cpMax:p->value.cpMax;
  if (!a) return 0;
  int b=tlen+5;
  if (!shift(a,b)) return 0;
  _snprintf(buf+a,b,"\n<%.*s/>\n",tlen,tag);	// create an unsplit element in a new line
  Node*n=new Node(Node::Element);
  n->scope.cpMin=a+1;
  n->scope.cpMax=a+b-2;
  n->name.cpMin=a+2;
  n->name.cpMax=a+2+tlen;
  if (before) before->add_sibling_back(n);
  else p->add_child_back(n);
  return n;  
}

Node*Xml::setElement(Node*p,const char*tag,bool*created) {
  if (created) *created=false;
  Node*n=findChildNode(p,Node::Element,tag);	// wenn's da ist, liefern
  if (n) return n;
  if (p==root) {
    Node*pi=addPI("xml",3,p->sub);	// am Anfang
    addAttr(pi,"version","1.0");
    addAttr(pi,"encoding","UTF-8");
    addAttr(pi,"standalone","yes");
  }
  if (created) *created=true;
  return addElement(p,tag);			// wenn nicht, erzeugen und (innen) anhängen
}

Node*Xml::addPI(const char*tag,int tlen,Node*before) {
  if (before && before->parent!=root) return 0;	// <before> must be direct child of root
  if (tlen<0) tlen=strlen(tag);
  int a=before?before->scope.cpMin:0;
  int b=tlen+6;
  if (!shift(a,b)) return 0;
  _snprintf(buf+a,b,"<?%.*s ?>\n",tlen,tag);	// create a processing instruction in a new line
  Node*n=new Node(Node::ProcessingInstruction);
  n->scope.cpMin=a;
  n->scope.cpMax=a+b-1;
  n->name.cpMin=a+2;
  n->name.cpMax=a+2+tlen;
  if (before) before->add_sibling_back(n);
  else root->add_child_back(n);
  return n;  
}

Node*Xml::addText(Node*p,const char*txt,int tlen,Node*before) {
  if (!p) return 0;
  if (p->type!=Node::Element) return 0;
  if (before) {
    if (before->parent!=p) return 0;	// <before> must be direct child of <p>
    if (before->type==Node::Attr) return 0;	// cannot be an attribute
  }
// TODO: Check whether insertion point is not surrounded by text nodes, then modify text node instead
  splitElement(p);
  int a=before?before->scope.cpMin:p->sub?p->sub->prev->scope.cpMax:p->value.cpMin;
  if (!a) return 0;	// something wrong
  if (!shift(a,tlen)) return 0;
  _snprintf(buf+a,tlen,"%.*s",tlen,txt);	// dasselbe wie memcpy(buf+a,txt,tlen)
  Node*n=new Node(Node::Text);
  n->scope.cpMin=n->value.cpMin=a;
  n->scope.cpMax=n->value.cpMax=a+tlen;
  if (before) before->add_sibling_back(n);
  else p->add_child_back(n);
  return n;
}

Node*Xml::addAttr(Node*el,const char*tag,int tlen,const char*val,int vlen,Node*before) {
  if (!el) return 0;
  if (el->type!=Node::Element) return 0;
  if (before) {
    if (before->parent!=el) return 0;
    if (before->type!=Node::Attr) return 0;
  }
  Node*lastattr=0;
  if (!before) {
    Node*q,*p=el->sub;
    if (q=p) do{
      if (q->type!=Node::Attr) break;
      lastattr=q;
    }while((q=q->next)!=p);
  }
  int a=before?before->scope.cpMin-1:lastattr?lastattr->scope.cpMax:el->name.cpMax;
  if (!a) return 0;
  if (tlen<0) tlen=strlen(tag);
  if (vlen<0) vlen=strlen(val);
  int b=tlen+vlen+4;
  if (!shift(a,b)) return 0;
  _snprintf(buf+a,b," %.*s=\"%.*s\"",tlen,tag,vlen,val);
  Node*n=new Node(Node::Attr);
  n->scope.cpMin=n->name.cpMin=a+1;
  n->name.cpMax=a+1+tlen;
  n->value.cpMin=a+1+tlen+2;
  n->value.cpMax=a+b-1;
  n->scope.cpMax=a+b;
  if (before) before->add_sibling_back(n);
  else if (lastattr) lastattr->add_sibling_front(n);
  else el->add_child_back(n);
  return n;
}

static bool addDependency(Xml&xml, const char*token, const char*name, const char*version) {
  bool created;
  Node*a=xml.setElement(xml.root,"assembly",&created);
  if (created) {
    xml.addAttr(a,"xmlns","urn:schemas-microsoft-com:asm.v1");
    xml.addAttr(a,"manifestVersion","1.0");
  }
  Node*b=xml.setElement(a,"dependency");
  Node*c=xml.addElement(b,"dependentAssembly");	// immer neu
  Node*d=xml.addElement(c,"assemblyIdentity");
  xml.addAttr(d,"type","win32");
  xml.addAttr(d,"name",name);
  xml.addAttr(d,"version",version);
  xml.addAttr(d,"processorArchitecture","*");
  xml.addAttr(d,"publicKeyToken",token);
  xml.addAttr(d,"language","*");
  return true;
}

static bool removeDependency(Xml&xml, const char*token) {
  Node*a=xml.findChildNode(xml.root,Node::Element,"assembly");
  if (!a) return false;
  Node*b=xml.findChildNode(a,Node::Element,"dependency");
  if (!b) return false;
  Node*d=xml.findNode(b,Node::Attr,"publicKeyToken");	// TODO!!
  return true;
}

static bool onNode(const Xml&xml,Node*n,void*) {
  Node*a=xml.findNode(n,Node::Attr,"publicKeyToken");
  if (a) {
    unsigned __int64 tok;
    sscanf(xml.buf+a->value.cpMin,"%I64x",&tok);
    switch (tok) {
      case 0x6595b64144ccf1df: CheckMenuItem(hMainMenu,112,MF_CHECKED); break;	// comctl32.dll
      case 0x1fc8b3b9a1e18e3b: break;	// Microsoft.VC80.CRT
    }
  }
  return true;
}

static void classify() {
  Xml xml(hEdit);	// construct XML parser object: Get string as UTF-8
  xml.parse();		// parse to tree
  Node*n=xml.findElement(xml.root,"dpiAware");
  unsigned id=309;
  if (n) {
    Node*t=xml.findNode(n,Node::Text);
    if (!xml.compareText(t->value,"true/PM",4)) id=310;
    else if (!xml.compareText(t->value,"true/PM",7)) id=311;
  }
  CheckMenuRadioItem(hMainMenu,309,311,id,0);
  n=xml.findElement(xml.root,"activeCodePage");
  CheckMenuItem(hMainMenu,115,n?MF_CHECKED:MF_UNCHECKED);
  xml.enumNodes(xml.root,Node::Element,"assemblyIdentity",onNode,0);
}

static void setDpiAware(char sel) {
  Xml xml(hEdit,4096);	// construct XML parser object: Get string as UTF-16
  xml.parse();		// parse to tree
  if (sel) {
    Node*n=xml.findElement(xml.root,"dpiAware");
    if (n) {
      xml.deleteNode(n->sub);
    }else{
      Node*a=xml.findElement(xml.root,"assembly");
      Node*b=xml.addElement(a,"asmv3:application");
      xml.addAttr(b,"xmlns:asmv3","urn:schemas-microsoft-com:asm.v3");
      Node*c=xml.addElement(b,"asmv3:windowsSettings");
      xml.addAttr(c,"xmlns","http://schemas.microsoft.com/SMI/2005/WindowsSettings");
      n=xml.addElement(c,"dpiAware");
    }
    xml.addText(n,"true/PM",sel==2?7:4);
  }else{
    xml.deleteNode(xml.findElement(xml.root,"asmv3:application"));
  }
  xml.show(hEdit);
}

static void indent() {
}

static bool setWindowTextU(HWND hEdit,const char*s,int slen=-1) {
  if (slen<0) slen=strlen(s);
  int wl=MultiByteToWideChar(CP_UTF8,0,s,slen,0,0);
  WCHAR*ws=(WCHAR*)_alloca((wl+1)*2);
  ws[MultiByteToWideChar(CP_UTF8,0,s,slen,ws,wl)]=0;
  return !!SetWindowText(hEdit,ws);
}

static bool getmanifest(HMODULE hExe) {
//  if (!hExe) {
//    MBox(hMainWnd,2,MB_OK,ExeName);
//    return -2;
//  }
  HRSRC hRes=FindResource(hExe,MAKEINTRESOURCE(1),RT_MANIFEST);
  if (!hRes) return false;
  HGLOBAL h1=LoadResource(hExe,hRes);
  if (!h1) return false;
  const char*m=(const char*)LockResource(h1);
  if (!m) return false;
  int slen=SizeofResource(hExe,hRes);
  const char*z=reinterpret_cast<const char*>(memchr(m,0,slen));
  if (z) MBox(hMainWnd,10,MB_OK,z-m);
  Edit_SetModify(hEdit,false);
  setWindowTextU(hEdit,m,slen);
  UnlockResource(h1);
  classify();
  return true;
}

static bool readmanifest() {
  HANDLE f=CreateFile(ExeName,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);
  if (f==INVALID_HANDLE_VALUE) return false;
  struct Filesize{
    DWORD lo,hi;
    operator unsigned __int64() const {return *reinterpret_cast<const unsigned __int64*>(this);}
    void operator()(HANDLE f) {lo=GetFileSize(f,&hi);}
  }size;
  size(f);
  if (size>4096) {CloseHandle(f); return false;}	// zu groß
  filetype=2;
  char*t=(char*)_alloca(size.lo);
  ReadFile(f,t,size.lo,&size.hi,0);
  CloseHandle(f);
  if (size.hi!=size.lo) return false;
  Edit_SetModify(hEdit,false);
  return setWindowTextU(hEdit,t,size.lo);
}

static bool getmanifest() {
  HINSTANCE hTest=LoadLibraryEx(ExeName,0,LOAD_LIBRARY_AS_DATAFILE);
  if (!hTest) return readmanifest();
  filetype=1;
  bool b=getmanifest(hTest);
  FreeLibrary(hTest);
  return b;
}

static bool makebakname() {
  lstrcpyn(BakName,ExeName,elemof(BakName));
  int i=PathFindExtension(BakName)-BakName;
  return StrReplace(BakName,-1,elemof(BakName),i,0,TEXT("~"),-1)>0;
}

static bool revert(HWND parent) {
  if (MoveFileEx(BakName,ExeName,MOVEFILE_REPLACE_EXISTING)) {
    EnableMenuItem(hMainMenu,104,MF_GRAYED);
    getmanifest();
    MBox(parent,5,MB_OK|MB_ICONINFORMATION,ExeName);
    return true;
  }
  MBox(parent,6,MB_OK|MB_ICONEXCLAMATION|MB_SOUND,ExeName);
  return false;
}

// apply changes
static bool apply(HWND parent) {
// If no backup file exists, create one. Existence is marked in MainMenu id 104.
  if (GetMenuState(hMainMenu,104,0)&MF_GRAYED) {
    if (CopyFile(ExeName,BakName,TRUE)) {
      EnableMenuItem(hMainMenu,104,MF_ENABLED);
    }else if (MBox(parent,3,MB_YESNO|MB_ICONQUESTION|MB_SOUND)!=IDYES) return false;
  }
// Now use XxxUpdateResource functions to modify the executable file
  HANDLE hUpdate=BeginUpdateResource(ExeName,FALSE);
  if (!hUpdate) {
    MBox(parent,4,MB_OK|MB_ICONEXCLAMATION|MB_SOUND);
    return false;
  }
  int lw=GetWindowTextLength(hEdit)+1;
  WCHAR*sw=(WCHAR*)_alloca(lw*2);
  GetWindowText(hEdit,sw,lw);
  int lu=WideCharToMultiByte(CP_UTF8,0,sw,lw,0,0,0,0);
  char*su=(char*)_alloca(lu);
  WideCharToMultiByte(CP_UTF8,0,sw,lw,su,lu,0,0);
  if (!UpdateResource(hUpdate,RT_MANIFEST,MAKEINTRESOURCE(2),0,su,lu)) {
    MBox(parent,4,MB_OK|MB_ICONEXCLAMATION|MB_SOUND);
    return false;
  }
  if (!EndUpdateResource(hUpdate,FALSE)) {
    MBox(parent,4,MB_OK|MB_ICONEXCLAMATION|MB_SOUND);
    return false;
  }
  MBox(parent,7,MB_OK|MB_ICONINFORMATION);
  EnableMenuItem(hMainMenu,102,MF_GRAYED);
  return true;
}

static void handleDependency(UINT id,unsigned __int64 token,const char*name,const char*version) {
  char buf[17];
  _snprintf(buf,sizeof buf,"%I64x",token);
  Xml xml(hEdit,4096);
  xml.parse();
  UINT check=0;
  if (GetMenuState(hMainMenu,id,0)&MF_CHECKED) {
    if (!removeDependency(xml,buf)) return;
  }else{
    if (!addDependency(xml,buf,name,version)) return;
    check=MF_CHECKED;
  }
  CheckMenuItem(hMainMenu,id,check);
  xml.show(hEdit);
}

static void handleOS(UINT id, unsigned __int64 guidH, unsigned __int64 guidL) {
  char buf[39];
  _snprintf(buf,sizeof buf,"{%08x-%04x-%04x-%04x-%012x}",
   guidH>>32, guidH>>16&0xFFFF, guidH&0xFFFF,
   guidL>>48, guidL&0xFFFFFFFFFFFF);
}

static bool openfile(HWND parent) {
  OPENFILENAME ofn;
  ZeroMemory(&ofn,sizeof ofn);
  ofn.lStructSize=sizeof ofn;
  ofn.hwndOwner=parent;
  ofn.lpstrFile=ExeName;
  ofn.nMaxFile=elemof(ExeName);
  ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST;
  ofn.nFilterIndex=filetype;
  ofn.lpstrCustomFilter=CustFilter;
  ofn.nMaxCustFilter=elemof(CustFilter);
  TCHAR f[64];
  f[LoadString(hInstance,8,f,elemof(f)-1)+1]=0;
  ofn.lpstrFilter=f;
  if (!GetOpenFileName(&ofn)) return false;
  getmanifest();
  makebakname();
  BOOL bTest=PathFileExists(BakName);
  EnableMenuItem(hMainMenu,103,filetype==1?MF_ENABLED:MF_GRAYED);
  EnableMenuItem(hMainMenu,2,MF_ENABLED|MF_BYPOSITION);
  EnableMenuItem(hMainMenu,104,bTest?MF_ENABLED:MF_GRAYED);
  DrawMenuBar(hMainWnd);
  TCHAR s[64];
  _sntprintf(s,elemof(s),TEXT("%s - %s"),ExeName+ofn.nFileOffset,MBoxTitle);
  SetWindowText(hMainWnd,s);
  return true;
}

static LONG_PTR CALLBACK MainWndProc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
  switch (msg) {
    case WM_CREATE: {
      hMainWnd=Wnd;
      hMainMenu=GetMenu(Wnd);
      hStatus=CreateStatusWindow(WS_VISIBLE|WS_CHILD,TEXT("Status"),Wnd,13);
      hEdit=CreateWindowEx(0,RICHEDIT_CLASS,0,WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_WANTRETURN,
       0,0,0,0,Wnd,(HMENU)12,0,0);
      msgFind=RegisterWindowMessage(FINDMSGSTRING);
      findreplace.lStructSize=sizeof findreplace;
      findreplace.hwndOwner=Wnd;
      findreplace.Flags=FR_SHOWHELP|FR_DOWN;
      findreplace.lpstrFindWhat=findstr;
      findreplace.wFindWhatLen=elemof(findstr);
      findreplace.lpstrReplaceWith=replacestr;
      findreplace.wReplaceWithLen=elemof(replacestr);
    }break;
    case WM_SIZE: {
      SendMessage(hStatus,msg,wParam,lParam);
      RECT rc;
      static const int info[]={1,1,1,13,0};
      GetEffectiveClientRect(Wnd,&rc,const_cast<int*>(info));
      SetWindowPos(hEdit,0,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,SWP_NOZORDER);
    }break;
    case WM_INITMENU: {
      EnableMenuItem((HMENU)wParam,65,SendMessage(hEdit,EM_CANUNDO,0,0)?MF_ENABLED:MF_GRAYED);
      EnableMenuItem((HMENU)wParam,66,SendMessage(hEdit,EM_CANREDO,0,0)?MF_ENABLED:MF_GRAYED);
    }break;
    case WM_COMMAND: switch (wParam) {
      case 65: SendMessage(hEdit,EM_UNDO,0,0); break;
      case 66: SendMessage(hEdit,EM_REDO,0,0); break;
      case 81: if (hFind) SetActiveWindow(hFind);
      else hFind=FindText(&findreplace);
      break;
      case 84: if (hFind) SetActiveWindow(hFind);
      else hFind=ReplaceText(&findreplace);
      break;
      case 101: openfile(Wnd); break;
      case 102: apply(Wnd); break;
      case 103: ShellExecute(Wnd,0,ExeName,0,0,SW_SHOWDEFAULT); break;
      case 104: revert(Wnd); break;
      case 112: handleDependency(wParam,0x6595b64144ccf1df,"Microsoft.Windows.Common-Controls","6.0.0.0"); break;
      case 319: handleDependency(wParam,0x1fc8b3b9a1e18e3b,"Microsoft.VC80.CRT","8.0.50608.0"); break;
      case 320: handleDependency(wParam,0x1fc8b3b9a1e18e3b,"Microsoft.VC90.DebugCRT","9.0.21022.8"); break;
      case 301: handleOS(wParam,0xe2011457154643c5,0xa5fe008deee3d3f0); break;	// Vista
      case 302: handleOS(wParam,0x35138b9a5d964fbd,0x8e2da2440225f93a); break;	// 7
      case 303: handleOS(wParam,0x4a2f28e353b94441,0xba9cd69d4a4a6e38); break;	// 8
      case 304: handleOS(wParam,0x1f676c7680e14239,0x95bb83d0f6d0da78); break;	// 8.1
      case 305: handleOS(wParam,0x8e0f7a12bfb34fe8,0xb9a548fd50a15a9a); break;	// 10
      case 309:
      case 310:
      case 311: setDpiAware(wParam-309); break;
      case 2: SendMessage(Wnd,WM_CLOSE,0,0); break;
      case MAKELONG(12,EN_CHANGE):
      case MAKELONG(12,EN_UPDATE): {	// Nur EN_UPDATE kommt
        EnableMenuItem(hMainMenu,102,Edit_GetModify((HWND)lParam)?MF_ENABLED:MF_GRAYED);
      }break;
    }break;
    case WM_NOTIFY: switch (wParam) {
      case 12: {
        NMHDR*nmhdr=(NMHDR*)lParam;
	switch (nmhdr->code) {
	  case EN_UPDATE:		// kommt nie
	  case EN_CHANGE: EnableMenuItem(hMainMenu,102,Edit_GetModify(nmhdr->hwndFrom)?MF_ENABLED:MF_GRAYED); break;
	}
      }break;
    }break;
    case WM_SETFOCUS: SetFocus(hEdit); break;
    case WM_CLOSE:
    case WM_QUERYENDSESSION: {
      if (!(GetMenuState(hMainMenu,102,0)&MF_GRAYED)) {
        switch (MBox(Wnd,9,MB_YESNOCANCEL|MB_ICONQUESTION|MB_SOUND,ExeName)) {
	  case IDNO: goto def;		// don't save, Windows can terminate
	  case IDCANCEL: return FALSE;	// don't save, don't let Windows terminate
	}
	apply(Wnd);			// save, then let Windows terminate
      }
    }break;
//    case WM_CLOSE: DestroyWindow(Wnd); break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: if (msg==msgFind) {
      FINDREPLACE&fr=*(FINDREPLACE*)lParam;
      if (fr.Flags&FR_DIALOGTERM) hFind=0;
      if (fr.Flags&FR_FINDNEXT) {
        UINT options=fr.Flags&(FR_DOWN|FR_MATCHCASE|FR_WHOLEWORD);
	FINDTEXTEX ft={{0,-1},fr.lpstrFindWhat};
        if (SendMessage(hEdit,EM_FINDTEXTEX,options,(LPARAM)&ft)>=0) {
	  Edit_SetSel(hEdit,ft.chrgText.cpMin,ft.chrgText.cpMax);
	}else MessageBeep(MB_ICONEXCLAMATION);
      }
    }
  }
def:
  return DefWindowProc(Wnd,msg,wParam,lParam);
}

void WinMainCRTStartup() {
  hInstance=GetModuleHandle(0);
  InitCommonControls();
  LoadLibrary(TEXT("Riched20.dll"));
  WNDCLASS wc={
    CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
    MainWndProc,
    0,0,0,
    LoadIcon(0,IDI_APPLICATION),
    0,0,
    MAKEINTRESOURCE(100),
    MAKEINTATOM(100)
  };
  RegisterClass(&wc);
//  const TCHAR*cmdline=GetCommandLine();
//  const TCHAR*args=PathGetArgs(cmdline);
  LoadString(hInstance,1,MBoxTitle,elemof(MBoxTitle));
  HACCEL hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(100));

  hMainWnd=CreateWindowEx(0,MAKEINTATOM(100),MBoxTitle,WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
    0,0,0,0);
  ShowWindow(hMainWnd,SW_SHOWDEFAULT);

  MSG msg;
  while (GetMessage(&msg,0,0,0)) {
    if (hAccel && TranslateAccelerator(hMainWnd,hAccel,&msg)) continue;
    if (hDialog && IsDialogMessage(hDialog,&msg)) continue;
    if (hFind && IsDialogMessage(hFind,&msg)) continue;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  ExitProcess((UINT)msg.wParam);

}

// Normalerweise wäre _alloca(size_t) ein simples "sub esp,eax".
// Aber das Win32-Speicherlayout macht einen Strich durch die Rechnung und verlangt,
// dass der Stack Seite für Seite abwärts belegt wird.
// Sonst kommt eine Schutzverletzung.
// Oder man legt per Linker-Schalter ein reichliches Commit für den Stack fest. (Geht das??)
// Daher generiert der Compiler für _alloca() sowie für Funktionen mit mehr als 4 KByte
// lokale Variablen einen Aufruf dieser Funktion.
// Prinzipiell könnte diese eax/4 push-Befehle ausführen …
// PE: eax = Zu reservierende Bytes
// PA: esp = Anfang des Speicherbereiches
// VR: eax

extern "C" void _declspec(naked) _alloca_probe() {_asm{
	add	eax,3
	and	al,~3		// Teilbarkeit durch 4 sicherstellen
	push	ecx
	 lea	ecx,[esp]+8	// ecx = Endadresse auf Stack
	 jmp	short testpg
onepg:	 sub	ecx,4096
	 sub	eax,4096
	 test	dword ptr [ecx],eax	// seitenweise Speicherlesezyklus anstoßen
testpg:	 cmp	eax,4096
	 jae	short onepg
	 sub	ecx,eax			// ecx = Startadresse auf Stack
	 mov	eax,esp			// eax = Zeiger auf ursprüngliches ecx
	 test	dword ptr [ecx],eax	// nochmal Speicherlesezyklus
	 mov	esp,ecx			// esp = Startadresse (Rückgabewert)
	mov	ecx,[eax]		// ecx restaurieren
	jmp	dword ptr[eax+4]	// Rücksprung zum Aufrufer
}}

extern "C" void _declspec(naked) _aullshr() {_asm{
	test	cl,32
	jnz	l1
	shrd	eax,edx,cl
	shr	edx,cl
	ret
l1:	mov	eax,edx
	xor	edx,edx
	shr	eax,cl
	ret 
}}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded