Source file: /~heha/ewa/PIC16F145x-Urlader/pubio.zip/src/pubio.cpp

/* PUBIO = PIC16F145x USB Bootloader Input/Output

 Dieser Mikrocontroller bietet USB Full-Speed mit gerade mal
 zwei extern anzuschließenden Kondensatoren!
 Und es gibt ihn im bastelfreundlichen Durchsteckgehäuse.
 Er hat viele Möglichkeiten, u.a. einen 10-bit-A/D-Wandler
 und ist ansonsten mit ATtiny44 oder ATtiny2313 vergleichbar.

 Die erweiterte Version des 512 Programmworte großen Urladers
 bietet zwei neue Befehle:

 * Byte lesen (nach Senden einer 2-Byte-Adresse, LSB zuerst)
 * Byte schreiben (nach Senden einer 2-Byte-Adresse, LSB zuerst, und dem Datenbyte)

 Damit kann ein PIC16F145x als einfaches Laborautomatisierungssystem
 verwendet werden, ohne ein einziges Byte Firmware schreiben zu müssen.
 Für alles was wenig zeitkritisch ist reicht das vollkommen aus.

 Die Namen und Adressen der „Special Function Register“ der (größten)
 PIC16F1459  sind ins Programm eingebaut, für die notwendigen Daten
 muss man im Datenblatt nachgucken.
 Wenn sinnvoll werden Bits und Bitnamen angezeigt,
 diese können auch einzeln manipuliert werden:
 Mausklick oder mit TAB fokussieren und Leertaste.

 Dieses Programm dient zum Herumexperimentieren mit dieser Schnittstelle.
 Der Einsatz erfolgt dann in einem problemangepassten Programm,
 bspw. in LabVIEW.

 Für ambitioniertere Aufgaben ist es jedoch sinnvoll,
 echte Firmware zu entwickeln und ggf. über dasselbe Interface zu brennen.
 Da sowohl Urlader als auch Firmware die gleiche USB-Schnittstelle
 und das gleiche COM-Port verwenden können,
 kann dieser Vorgang auch „in system“ erfolgen.
 Die Firmware muss ab Programm-Adresse 0x200 beginnen,
 und der Anwender-Interrupt startet entsprechend auf 0x204.

 Bei Verwendung von SDCC als C-Compiler muss ein eigens angepasstes
 Linker-Skript (*.lkr) zum Einsatz kommen, damit die Startadresse stimmt.
 Sowie (leider) Zwischen-Assembler-Dateien gepatcht werden.
 Siehe http://www.tu-chemnitz.de/~heha/ewa/PIC16F145x-Urlader/.

 Den Urlader als COM-Port-Treiber weiter zu verwenden ist optional.
 Dazu gibt es „magische Programm-Adressen“ bei 0x001, 0x002, 0x003
 sowie 0x202. Siehe Quelltext „bootCDC.asm“.

 Der Stil meiner klitzekleinen Windows-Programme ist immer gleich.
 Hier nachlesen: http://www.tu-chemnitz.de/~heha/hs/mein_msvc.htm

 Henrik Haftmann, November 2018

+210515	High-DPI-Manifest
!210516	MSVC2008: /O1 generiert Murks bei 32 Bit: Crash bei bootCDC-Firmware
	ohne ID-Bit-Unterstützung; /Os verwenden (ungelöst, ich nehme MSVC6)
-210517	Symboltabelle geändert, Little/Big Endian getrennt
-210519	Flash-Dateiname mit .hex wenn nicht angegeben
-210606	Register-Namensanzeige, Anzeige der linearen und Bank-Adresse bei RAM
+210608	Buffer-Descriptor-Table-Anzeige
+210609	Optionen beim Flash-Speichern
+210627	Optionen beim Programmieren und Vergleichen
*/
#define _WIN32_WINNT 0x0600	// OpenFileDialog mit PlacesBar; SplitButton
#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include <setupapi.h>
#include <devguid.h>

#define T(x) TEXT(x)		// Schreibfaulheit…
#define elemof(x) (sizeof(x)/sizeof(*(x)))	// Elemente eines Arrays
#define nobreak			// expliziter Durchlauf bei switch/case
#if _MSC_VER >= 1400
# include <intrin.h>
# define memset(a,b,c) __stosb((PBYTE)(a),b,c)
# define memcpy(a,b,c) __movsb((PBYTE)(a),(PBYTE)(b),c)
#else
# ifndef SetClassLongPtr
#  define SetClassLongPtr SetClassLong
#  define GCLP_HICON GCL_HICON
# endif
# define INT_PTR INT
# define LONG_PTR LONG
# pragma intrinsic(memset,memcpy)
__forceinline void __stosw(WORD*d, WORD w, size_t l) {
 _asm	mov	edi,d
 _asm	mov	ax,w
 _asm	mov	ecx,l
 _asm	rep stosw
}
#endif

#ifdef _DEBUG
static void _cdecl debugout(const TCHAR*fmt,...) {
 va_list va;
 va_start(va,fmt);
 TCHAR s[256];
 if (IS_INTRESOURCE(fmt)) {
  TCHAR t[256];
  LoadString(0,(UINT)fmt,t,elemof(t));
  fmt=t;
 }
 wvnsprintf(s,elemof(s),fmt,va);
 va_end(va);
 OutputDebugString(s);
}
# define debug(x) debugout x
# define assert(x) if (!(x)) debugout(T("%hs(%d): Assert failed: %hs\n"),__FILE__,__LINE__,#x)
#else
# define debug(x)
# define assert(x)
#endif

extern char const pic16f1459sym[];
extern char const pic16f1459bits[];

static struct Config{
 short posX,posY;
 BYTE ComNr;	// Nullbasierte COM-Portnummer
 BYTE flags;	// Bit 0: <addr> als SFR bei PIC16F1454 vorhanden
		// Bit 1: <addr> als SFR bei PIC16F1455 vorhanden
		// Bit 2: <addr> als SFR bei PIC16F1459 vorhanden
		// Bit 3: <addr> beim Lesen veränderlich; nicht automatisch lesen, Suffix „v“
		// Bit 4: <addr> bezeichnet 16-Bit-Wert Big Endian, Suffix „W“
		// Bit 5: <addr> bezeichnet 16-Bit-Wert Little Endian, Suffix „w“
		// Bit 7: <addr> als Zahl gegeben, sonst als SFR
 WORD addr;	// Zuletzt benutzte Adresse
 WORD data;	// Zuletzt geschriebenes Datenbyte oder Wort
 BYTE saveflags;// Infobits für Hex-Datei-Schreiben (in der Reihenfolge der Button-IDs)
 BYTE progflags;// Infobits für Programmierfunktion
 BYTE veriflags;// Infobits, was zu vergleichen ist
 BYTE read_time;// Timer-Wert fürs automatische Lesen in 10 ms)
 WORD datamax() const {return flags&0x30?0xFFFF:0xFF;}
 WORD linaddr() const;
 WORD bdt() const {return linaddr()-0x2000;}
}config;

// Die Angabe auf Seite 302 (USB RAM) ist falsch!
// Richtig ist, der Single-Port-RAM beginnt bei 0x21F0 und endet bei 0x23EF,
// liegt auf krummen Adressen.
// Der linear adressierbare Dual-Port-RAM liegt bei 0x2000..0x21EF, die letzten 16 Bytes
// befinden sich (nur) im bank-adressierbaren Bereich 0x70..0x7F, damit's 512 Bytes werden.
WORD Config::linaddr() const {
 if (addr>=0x1000) return addr;	// nicht umrechnen
 BYTE b=addr>>7, ba=addr&0x7F;	// aufteilen: Bank (0..31), Adresse (0..127)
 if (ba>=0x70) return 0;	// Letzte 16 Bytes des Dual-Port-RAM (via BDT nutzbar oder nicht??)
 if (ba<0x20) return 0;		// SFR-Bereich nicht umrechenbar
 if (b==31) return 0;		// Bank 31 nicht umrechenbar
 return 0x2000+b*80+ba-0x20;	// nur bis 0x23EF mit RAM belegt
}

static TCHAR HexFileName[260];

static HWND MainWnd;
static TCHAR MBoxTitle[64];

static int vMBox(UINT id, UINT type, va_list va) {
 TCHAR s[1000],t[1000];
 LoadString(0,id,t,elemof(t));
 wvnsprintf(s,elemof(s),t,va);
 return MessageBox(MainWnd,s,MBoxTitle,type);
}

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

static void LoadSettings() {
 HKEY hKey;
 if (RegOpenKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\pubio"),0,KEY_QUERY_VALUE,&hKey)) return;
 DWORD len=sizeof Config;
 RegQueryValueEx(hKey,T("Config"),0,0,(BYTE*)&config,&len);
 len=sizeof HexFileName;
 RegQueryValueEx(hKey,T("HexFileName"),0,0,(BYTE*)HexFileName,&len);
 RegCloseKey(hKey);
}

static void SaveSettings() {
 HKEY hKey;
// Schlüssel-Zweig öffnen
 if (RegCreateKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\pubio"),0,
   0,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,0,&hKey,0)) return;
// eine Beschreibung schreiben (damit der Anwender weiß, worum es geht)
 RegSetValue(hKey,0,REG_SZ,MBoxTitle,(lstrlen(MBoxTitle)+1)*sizeof(TCHAR));
 RegSetValueEx(hKey,T("Config"),0,REG_BINARY,(PBYTE)&config,sizeof config);
 RegSetValueEx(hKey,T("HexFileName"),0,REG_SZ,(PBYTE)HexFileName,(lstrlen(HexFileName)+1)*sizeof(TCHAR));
 RegCloseKey(hKey);
}

BOOL EnableDlgItem(HWND Wnd,UINT id,BOOL e) {
 return EnableWindow(GetDlgItem(Wnd,id),e);
}

static HANDLE hCom;
static BYTE detectedChip;	// 0, 4, 5 oder 9

static bool OpenComm() {
 if (hCom) return true;
 TCHAR s[12];
 wnsprintf(s,elemof(s),T("\\\\.\\COM%u"),config.ComNr+1);
 HANDLE h=CreateFile(s,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
 if (h==INVALID_HANDLE_VALUE) {
  debug((T("Could not open %s, error %d (0x%X)\n"),s,GetLastError(),GetLastError()));
  return false;
 }
 hCom=h;
 return true;
}

static void CloseComm() {
 if (!hCom) return;
 if (CloseHandle(hCom)) hCom=0;
 SetDlgItemText(MainWnd,106,0);
 EnableDlgItem(MainWnd,4,false);	// Knopf "Lesen"
 EnableDlgItem(MainWnd,5,false);	// Knopf "Schreiben"
 for (UINT i=8; i<12; i++) EnableDlgItem(MainWnd,i,false);
 detectedChip=0;
}

// Weil von den anderen COM-Ports nichts zurückkommt, mit asyncronen Transfers arbeiten.
// COMM-Timeouts würden auf ähnlich schlampig implementierter USB-Mikrocontroller-Firmware
// nicht so zuverlässig funktionieren, denn da muss die USB-CDC Firmware (sicherlich) mitarbeiten.
static DWORD WriteComm(const void*b, DWORD len, DWORD to=100) {
 OVERLAPPED o;
 ZeroMemory(&o,sizeof o);
 DWORD bw=0;
 HANDLE h=hCom;
 if (!WriteFile(h,b,len,&bw,&o)
  && GetLastError()==ERROR_IO_PENDING)
  if (!WaitForSingleObject(h,to)) GetOverlappedResult(h,&o,&bw,FALSE);
  else CancelIo(h);
 return bw;
}

static DWORD ReadComm(void*b, DWORD len, DWORD to=100) {
 OVERLAPPED o;
 ZeroMemory(&o,sizeof o);
 DWORD br=0;
 HANDLE h=hCom;
 if (!ReadFile(h,b,len,&br,&o)
  && GetLastError()==ERROR_IO_PENDING)
  if (!WaitForSingleObject(h,to)) GetOverlappedResult(h,&o,&br,FALSE);
  else CancelIo(h);
 return br;
}

namespace pubio{

static int W(const void*buf,DWORD len) {
 DWORD b=WriteComm(buf,len);
 if (b!=len) {CloseComm(); return -3;}
 BYTE r;
 b=ReadComm(&r,1);
 if (b!=1) {CloseComm(); return -2;}
 return r;
}
// wie oben aber mit Fehlerkode-Umwandlung
static int We(const void*buf,DWORD len) {
 int r=W(buf,len);	// es muss 1 für Okay von der Firmware zurückkommen
 if (r!=1) r+=10;	// 2,3,4 in String-ID umwandeln (Murks!)
 return r;
}

// Byte aus PIC-Adressraum lesen
static int Read(WORD a) {
 if (!OpenComm()) return -7;
 return W(&a,2);
}
// Byte in PIC-Adressraum schreiben
static int Write(WORD a,BYTE b) {
 if (!OpenComm()) return -7;
 struct{
  WORD a;
  BYTE b;
 }block={a,b};
 return We(&block,3);	// es muss 1 für Okay von der Firmware zurückkommen
}
// Äpp anspringen
static int JumpToApp() {
 BYTE b='R';
 int r=We(&b,1);	// Reset ausgeben, Firmware starten, muss 1 liefern für Erfolg
 if (r!=1) return r;
 CloseComm();
 return r;
}
}//namespace pubio

static BYTE HexData[0x4000];	// 16 Kilobyte (8K×14bit) Flash-Daten für den Controller
#define HexDataW ((WORD*)HexData)
static BYTE IdSpace[64];
/*Wort-Adressen:	0x8000: IDLOC0
			0x8001:	IDLOC1
			0x8002:	IDLOC2
			0x8003:	IDLOC3
			0x8004:	frei, 0x3FFF
			0x8005:	Revision-ID
			0x8006:	Chip-ID
			0x8007:	CONFIG1
			0x8008:	CONFIG2
			0x8009..0x801F: frei, 0x0000
*/
#define IdSpaceW ((WORD*)IdSpace)

/*******************
 * Hex-Datei lesen *
 *******************/

struct get{			// Speicherlese-Objekt (für die Hex-Daten)
 const char*p;
 const char*const e;
 static int line;
 get(const void*q,DWORD len):p((const char*)q),e((const char*)q+len) {line=1;};
 int chr() {if (p==e) return -9; byte r=*p++; if (r==10) ++line; return r;}
 int nib();
 int byt();
};

int get::nib() {		// ohne Kleinbuchstaben
 int c=chr();
 if (c<0) return c;		// EOF
 if (c<'0' || c>'9' && c<'A' || c>'F') return -8;	// Falsches Zeichen
 c-='0'; if (c>9) c-=7;
 return c;			// 0..15
}
int get::byt() {
 int c1=nib();
 if (c1<0) return c1;
 int c2=nib();
 if (c2<0) return c2;
 return c1<<4|c2;		// 0..255
}

const WORD DefFlash=0x3FFF;
static bool haveID;	// Hex-Datei enthielt ID-Bits (sonst unverändert lassen)
int get::line;

// Intel-Hex-Datei einlesen
// TODO: Einige Fehler ignorierbar machen
// checkChpCfg:	Bit 0: Urlader-Bits zulassen
//		Bit 5: Chip-Vergleich aktivieren
//		Bit 6: Config-Vergleich aktivieren
static int ParseHexFile(get gp,BYTE checkChpCfg) {
 __stosw(HexDataW,DefFlash,sizeof HexData>>1);
 if (!detectedChip) checkChpCfg&=~0x60;	// kann ID-Space nicht vergleichen (alter Urlader)
 bool havedata=false;
 haveID=false;
 DWORD ha=0;			// High-Adresse
 int c;
 for (;;) {
  do{
   c=gp.chr();
   if (c<0) return c;		// Endekennung fehlt
  }while(c!=':');		// Anfang von Hexdaten suchen
  BYTE line[32+5];
  c=gp.byt();
  if (c<0) return c;		// Keine Hexziffer
  if (c>32) return -5;		// Zeile zu lang
  BYTE cs=line[0]=(BYTE)c;
  for (int i=1;i<line[0]+5;i++) {	// Bytezeile einlesen
   c=gp.byt();
   if (c<0) return c;
   line[i]=(BYTE)c;
   cs+=c;
  }
  if (cs) return -4;		// Prüfsummen-Fehler
  DWORD a=ha+MAKEWORD(line[2],line[1]);	// Startadresse
  switch (line[3]) {
   case 0: {			// Datenzeile
    for (int i=0; i<line[0]; i++) {
     DWORD aa=a+i;		// Byteadresse
     BYTE b=line[i+4];		// Zu programmierendes Byte
     if (aa&1) b&=HIBYTE(DefFlash);	// 2 MSB entfernen, falls irrtümlich gesetzt
     if (aa>=0x10000) {		// Microchips ID-Adressbereich × 2
      aa-=0x10000;
      DWORD wa=aa>>1;		// Wortadresse
      switch (wa) {
       case 0: case 1: case 2: case 3: IdSpace[aa]=b; haveID=true; break;
       case 5: break;		// Revision-ID ignorieren
       case 6: if (checkChpCfg&0x20 && IdSpace[aa]!=b) return -23; break;	// Unveränderlich
       case 7:
       case 8: if (checkChpCfg&0x40 && IdSpace[aa]!=b) return -24; break;	// Passt nicht
       default: return -20;	// reserviertes Word (4) bzw. Überschüssige Konfigurationsdaten
      }
     }else{
      if (!(checkChpCfg&1) && aa<1024) return -17;	// Hex-Datei überschreibt Urlader
      if (aa>=sizeof HexData) return -21;	// Überschüssige Daten 
      HexData[aa]=b;		// einzelbyteweise übertragen
      havedata=true;
     }
    }
   }break;
   case 1: if (!havedata) return c; return 0;	// Ende-Record
   case 2: ha=DWORD(MAKEWORD(line[5],line[4]))<<4; break;	// Segmentvorgabe
   case 4: ha=DWORD(MAKEWORD(line[5],line[4]))<<16; break;	// High-Adressvorgabe
  }
 }
}
static int readHexFile(BYTE checkChpCfg) {
 HANDLE h=CreateFile(HexFileName,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);
 if (h==INVALID_HANDLE_VALUE) return -6;
 DWORD size=GetFileSize(h,0);
 HANDLE hm=CreateFileMapping(h,0,PAGE_READONLY,0,0,0);
 if (!hm) return -6;
 const void*p=MapViewOfFile(hm,FILE_MAP_READ,0,0,0);
 get gp(p,size);		// Get-Pointer (2 Zeiger) basteln
 int r=ParseHexFile(gp,checkChpCfg);
 UnmapViewOfFile(p);
 CloseHandle(hm);
 CloseHandle(h);
 return r;
}

/***********************
 * Flash programmieren *
 ***********************/
namespace pubio{

static int ProgSector(WORD flashaddr, const BYTE*mem) {
 WORD ff=DefFlash;
 BYTE cs=0,e=flashaddr==0x8000?8:64;
 for (BYTE b=0; b<e; b++) {
  cs+=mem[b];	// Neu 210618: Prüfsumme nur über IDLOC
  ((BYTE*)&ff)[b&1]&=mem[b];
 }
 struct{
  WORD addr;	// Programmspeicher-Wortadresse (Little-Endian)
  BYTE cs;	// Datenblock muss nach Summierung mit cs 0 ergeben
  char erase;	// Lösch-Flag (auch bei IDLOC erforderlich?)
 }block={flashaddr,256-cs,/*flashaddr==0x8000 ? 'P' :*/'E'};	// HACK!!
 if (flashaddr<512) return 17;
 int r=We(&block,4); if (r!=1) return r;	// sonstiges r = Erase-Fehler
 if (ff==DefFlash) return 1;	// nichts zu tun (leerer Flash)
 return We(mem,64);	// sonstiges r = Verify-Fehler
}

static int Prog() {
// Teil 1: Hex-Datei einlesen (TODO: ELF-Datei einlesen; Makefile ausführen)
 if (config.progflags&1) {
  int r=readHexFile(config.progflags<<4);
  if (r<0) return r;
 }
// Teil 2: Daten zum Flash übertragen
 if (!OpenComm()) return -7;
 for (int a=1024; a<sizeof HexData; a+=64) {
  int r=ProgSector(a>>1,HexData+a);
  if (r!=1) return r;
 }
 if (haveID) {
  int r=ProgSector(0x8000,IdSpace);
  if (r!=1) return r;
 }
 if (config.progflags&0x80) return JumpToApp();
 return 1;
}
}//namespace pubio

/*****************************
 * Intel-Hex-Datei schreiben *
 *****************************/
const BYTE maxline=32;		// max. 16 Bytes pro Zeile

static void writeHexLine(HANDLE h, const BYTE*data, BYTE len) {
 char line[1+(4+maxline+1)*2+3],*p=line;
 *p++=':';
 BYTE cs=0;
 do{
  BYTE b=*data++;
  cs-=b;
  p+=wsprintfA(p,"%02X",b);
 }while(--len);
 p+=wsprintfA(p,"%02X\r\n",cs);
 DWORD bw;
 WriteFile(h,line,DWORD(p-line),&bw,0);
}
// len darf nicht größer als maxline sein!!
static void writeHexLine(HANDLE h, BYTE type, BYTE len=0, const BYTE*data=0, WORD addr=0) {
 BYTE dat2[4+maxline];
 dat2[0]=len;
 dat2[1]=HIBYTE(addr);
 dat2[2]=LOBYTE(addr);
 dat2[3]=type;
 memcpy(dat2+4,data,len);
 writeHexLine(h,dat2,len+4);
}
// Letzte genutzte Flash-Adresse eines Speicherbereiches ermitteln; len muss gerade sein!
// Liefert Byte-Index der ersten ungenutzten Flash-Adresse, immer gerade
static unsigned lastusedflash(const BYTE*data,unsigned len) {
 unsigned r=len>>1;
 while (r && (((WORD*)data)[r-1]&DefFlash)==DefFlash) --r;
 return r<<1;	// gerade Byteadresse, 0 wenn alles gelöscht ist
}
// Der PIC16F145x liest die nichtvorhandenen Flash-Bits als 0.
// Für bessere Übersichtlichkeit der HEX-Datei mag es besser sein,
// diese Bits auf 1 zu setzen:
// Leerer Flash erscheint dann nicht als 0x03FF sondern als 0xFFFF.
// Leider reicht das nicht zum Vergleich mit der HEX-Datei,
// die von picasm(?) / sdcc(?) generiert wird:
// Dort werden diese beiden Bits nach derzeit unbekanntem Muster belegt,
// vermutlich opcode-abhängig.
static void SetFlashHiBits(BYTE*data,unsigned len) {
 for (;len;len-=2,data+=2) *(WORD*)data|=~DefFlash;
}
// Eine Zeile Hex-Daten (ID=0) mit Adresse <addr> ausgeben.
// Wenn Bit 5 in config.saveflags gelöscht:
//  Zeile auf tatsächlich genutzte Flash-Words (von hinten) kürzen.
//  Enthält diese Zeile keine genutzen Flash-Words, nichts tun, Zeile weglassen.
static void writeHexData(HANDLE h, WORD addr, BYTE len, const BYTE*data) {
// Erforderliche Länge feststellen; Freispeicher nicht ausspucken
 if (!(config.saveflags&32)) len=lastusedflash(data,len);
 if (!len) return;
 writeHexLine(h,0,len,data,addr);
}
static void writeHexData(HANDLE h, BYTE linelen, const BYTE*data, unsigned a, unsigned e) {
 for (unsigned i=a; i<e; i+=linelen) {
  if (linelen>e-i) linelen=e-i;
  writeHexData(h,(WORD)i,linelen,data+i);
 }
}
static void writeHexFile(HANDLE h) {
 if (config.saveflags&0x40) {		// so wie mpasm(?) das liefert
  SetFlashHiBits(HexData,sizeof HexData);	// Gelesene Flash-Daten vorverarbeiten: verändern
  SetFlashHiBits(IdSpace,sizeof IdSpace);
 }// (war wohl bei älteren PICs so, dass 0xFFFF statt 0x3FFF (14 bit) bzw. 0xFFF (12 bit) gelesen wurde.)
 unsigned a=0,e=sizeof HexData;
 if (!(config.saveflags&1)) a=0x400;	// Ohne Urlader
 if (!(config.saveflags&2)) e=lastusedflash(HexData,e);	// Nicht der ganze Flash
 writeHexData(h,config.saveflags&0x80?32:16,HexData,a,e);
 if (config.saveflags&0x1C) {
  const BYTE extendedaddress[2]={0,1};
  writeHexLine(h,4,2,extendedaddress);
  if (config.saveflags&0x04) writeHexData(h,0,8,IdSpace);	// User-ID
  if (config.saveflags&0x08) writeHexData(h,10,4,IdSpace+10);	// Chip-ID sowie Revision
  if (config.saveflags&0x10) writeHexData(h,14,4,IdSpace+14);	// Konfiguration
 }
 writeHexLine(h,1);	// EOF
}

/***************
 * Flash lesen *
 ***************/

// Zum Beschreiben von IDLOC muss ein Sektor gelesen werden,
// sonst klappt das Verify in der (knappen) Firmware nicht.
// Aber da lauert noch ein Firmware-Fehler (2021):
// Das Programmieren der IDLOC-Worte geht nicht!
static int pubioReadSector(WORD flashaddr, BYTE*mem) {
 if (!OpenComm()) return -7;
 struct{
  WORD addr;
  BYTE cs;
  char erase;
 }block={flashaddr,0,'R'};
 if (WriteComm(&block,4)!=4) {CloseComm(); return -3;}
 if (ReadComm(mem,64)!=64) {CloseComm(); return -18;}
 return 1;
}

static int pubioReadAll() {
 for (int a=0; a<sizeof HexData; a+=64) {	// Lesen mit Urlader (kann man nachher leicht herauslöschen)
  int r=pubioReadSector(a>>1,HexData+a);
  if (r!=1) return r;
 }
 return pubioReadSector(0x8000,IdSpace);	// sollte überflüssig sein, ist bereits gelesen
}


namespace pubio{
static int ReadFlash() {
// Teil 1: Flash lesen (der ID-Sektor wurde bereits gelesen)
 if (!OpenComm()) return -7;
 int r=pubioReadAll();
 if (r!=1) return r;
// Teil 2: Hex-Datei schreiben (TODO: ELF-Datei)
 TCHAR f[64];	// Filter-String mit Nullen aus Ressource
 f[LoadString(0,33,f,elemof(f)-1)+1]=0;	// mit Doppel-Null terminieren
 TCHAR n[260];
 lstrcpyn(n,HexFileName,elemof(n));
 OPENFILENAME ofn;
 ZeroMemory(&ofn,sizeof ofn);
 ofn.lStructSize=sizeof ofn;
 ofn.hwndOwner=MainWnd;
 ofn.lpstrFile=n;
 ofn.nMaxFile=elemof(n);
 ofn.lpstrFilter=f;
 ofn.nFilterIndex=1;
 ofn.Flags=OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY;
 ofn.lpstrDefExt=f+lstrlen(f)+3;	// übergehe "\0*." und zeige auf "hex\0"
 if (!GetSaveFileName(&ofn)) return 1;	// Keinen weiteren Fehler anzeigen lassen
 HANDLE h=CreateFile(n,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0);
 if (h==INVALID_HANDLE_VALUE) return -10;
 writeHexFile(h);
 CloseHandle(h);
 return 1;
}

static int CompSector(WORD flashaddr, const BYTE*mem) {
 if (!OpenComm()) return -7;
 if (flashaddr==0x8000) {
  if (!(config.veriflags&0x70)) return 64;	// Kein Extra-Vergleich
 }else{
  if (!(config.veriflags&0x04)) return 64;	// Kein Flash-Vergleich
 }
 struct{
  WORD addr;
  BYTE cs;
  char erase;
 }block={flashaddr,0,'R'};
 if (WriteComm(&block,4)!=4) {CloseComm(); return -3;}
 BYTE sec[64];
 if (ReadComm(sec,64)!=64) {CloseComm(); return -18;}
 const WORD*s=reinterpret_cast<const WORD*>(sec),
           *m=reinterpret_cast<const WORD*>(mem);
 for (unsigned i=0; i<32; i++,s++,m++) {
  if (flashaddr==0x8000) {
// Beim Vergleich die IdSpace-Wortadressen 0x8004 (undef.) und 0x8005 (Revisions-ID) nicht mit einbeziehen!
// Hingegen User-IDs (0x8000..0x8003), Chip-ID (0x8006) und Konfigurationswörter (0x8007 + 0x8008) werden verglichen.
   if (        i<4 && !(config.veriflags&0x10)) continue;
   if (4<=i && i<6) continue;
   if (i==6        && !(config.veriflags&0x20)) continue;
   if (7<=i && i<9 && !(config.veriflags&0x40)) continue;
   if (9<=i) continue;
  }else{
   if (!(config.veriflags&8) && !(~*m&DefFlash)) continue;
  }
  if (*m!=*s) return i<<1;
 }
 return 64;
}
// Beim Vergleich werden die beiden Hi-Bits ausgenommen.
// picasm / sdcc scheinen diese Bits wahlfrei zu benutzen (je nach Opcode?)
// werden nicht auf einen festen Wert gelegt.
static int CompAll() {
 int r,a=config.veriflags&2?0:1024;	// Vergleich mit/ohne Urlader
 if (config.veriflags&4) for (; a<sizeof HexData; a+=64) {
  r=CompSector(a>>1,HexData+a);
  if (r!=64) return r<0?r:a+r;
 }
 a=0x10000;
 r=CompSector(a>>1,IdSpace);
 if (r!=64) return r<0?r:a+r;
 return -1;	// hier: OK!!
}
static int Verify() {
 if (config.veriflags&1) {
  int r=readHexFile(config.veriflags);
  if (r<0) return r;	// hier kommt kein -1
 }
 return CompAll();	// hier ist -1 = okay, ab 0 = fehlerhafte Flash-Adresse
}
}//namespace pubio

/*******
 * GUI *
 *******/

static void _cdecl showError(int r, ...) {
 if (r==1) return;
 if (r<0) r=-r;
 UINT k=MB_OK;
 if (r) k|=MB_ICONEXCLAMATION;
 va_list va;
 va_start(va,r);
 vMBox(r,k,va);
 va_end(va);
}

static void FillComboComPorts() {
 HWND hCombo=GetDlgItem(MainWnd,101);
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
 ComboBox_ResetContent(hCombo);
 HANDLE devs=SetupDiGetClassDevs((LPGUID)&GUID_DEVCLASS_PORTS,0,0,DIGCF_PRESENT);
 if (devs!=INVALID_HANDLE_VALUE) {
  SP_DEVINFO_DATA devInfo;
  devInfo.cbSize=sizeof devInfo;
  for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
   HKEY hKey;
   TCHAR s[16];	// trotzdem ein Unicode-String
   *s=0;
   if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
     ==INVALID_HANDLE_VALUE) continue;
   DWORD len=sizeof(s);
   RegQueryValueEx(hKey,T("PortName"),0,0,(LPBYTE)s,&len);
   RegCloseKey(hKey);
   if (*s=='C') {		// Fehlschläge und LPTx ausfiltern
    int idx=ComboBox_AddString(hCombo,PTSTR(s));
    DWORD num=StrToInt(s+3)-1;	// nullbasierte Nummer
    ComboBox_SetItemData(hCombo,idx,num);
    if (num==config.ComNr) ComboBox_SetCurSel(hCombo,idx);
   }
  }
  SetupDiDestroyDeviceInfoList(devs);
 }
}

static void NotifyParent(HWND hWnd,WORD cbn) {
 SendMessage(GetParent(hWnd),WM_COMMAND,MAKELONG(GetDlgCtrlID(hWnd),cbn),(LPARAM)hWnd);
}

static void ShowAddress(HWND hCombo) {
 TCHAR s[12];
 TCHAR*sp=s+wnsprintf(s,elemof(s)-3,T("0x%03X"),config.addr);
 BYTE f=config.flags;
 if (f&0x08) *sp++='v';	// veränderlich beim Lesen
 if (f&0x10) *sp++='W';	// Big Endian Word
 else if (f&0x20) *sp++='w';	// Little Endian Word
 *sp=0;
 SetWindowText(hCombo,s);
 NotifyParent(hCombo,CBN_EDITCHANGE);
}

static TCHAR*append(TCHAR*d,TCHAR const*e,char c) {
 if (d!=e) *d++=c;
 return d;
}

static TCHAR*append(TCHAR*d,TCHAR const*e,char const*s,int l=-1) {
 if (s) {
#ifdef UNICODE
  d+=MultiByteToWideChar(CP_ACP,0,s,l,d,int(e-d));
#else
  if (l<0) l=lstrlenA(s);
  if (l>e-d) l=(e-d);
  memcpy(d,s,l); d+=l;
#endif
 }
 return d;
}

static void FillComboSymbols() {
 HWND hCombo=GetDlgItem(MainWnd,102);
 ComboBox_ResetContent(hCombo);
 const char*p=pic16f1459sym;
 while (*p) {
  const char*q=p;
  while (*q>=0x20) ++q;	// Ende des Namens finden
  TCHAR s[32];
  *append(s,s+elemof(s)-1,p,int(q-p))=0;
  if (!detectedChip
    || detectedChip==4 && q[2]&0x01
    || detectedChip==5 && q[2]&0x02
    || detectedChip==9 && q[2]&0x04) {	// Symbol-Liste passend zum aktuellen Chip
   int idx=ComboBox_AddString(hCombo,s);
   DWORD a=MAKELONG(MAKEWORD(q[1],q[0]),q[2]);
   ComboBox_SetItemData(hCombo,idx,a);
   if (LOWORD(a)==config.addr		// Adresse gleich?
   && !((q[2]^config.flags)&0xB0)) {	// Byte/Word gleich? Adresse nicht als Edit?
    ComboBox_SetCurSel(hCombo,idx);
    NotifyParent(hCombo,CBN_SELCHANGE);
   }
  }
  p=q+3;
 }
 if (ComboBox_GetCurSel(hCombo)<0) ShowAddress(hCombo);
}

static void DuplicateCheckboxes() {
 int i=23;
 HWND cb=GetDlgItem(MainWnd,i);	// Erste Checkbox aus der Ressource, gibt Maße vor
 HFONT f=GetWindowFont(MainWnd);
 WINDOWINFO wi;
 wi.cbSize=sizeof wi;
 GetWindowInfo(cb,&wi);
 wi.dwStyle&=~WS_GROUP;
 wi.dwStyle|= WS_VISIBLE;
 wi.dwExStyle&=0x0000FFFF;		// Windows 10: Manchmal kommt 0xC0000804 statt 0x00000804
					// und dann geht CreateWindow() schief!
 wi.rcWindow.right-=wi.rcWindow.left;	// Breite
 wi.rcWindow.bottom-=wi.rcWindow.top;	// Höhe
 ScreenToClient(MainWnd,(PPOINT)&wi.rcWindow);
 while (--i>=16) {
  wi.rcWindow.left+=wi.rcWindow.right;	// rechts daneben erzeugen
  HWND w=CreateWindowEx(wi.dwExStyle,MAKEINTRESOURCE(wi.atomWindowType),0,wi.dwStyle,
    wi.rcWindow.left,wi.rcWindow.top,wi.rcWindow.right,wi.rcWindow.bottom,
    MainWnd,(HMENU)i,0,0);
  SetWindowFont(w,f,TRUE);
  SetWindowPos(w,cb,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);	// In Tab-Reihenfolge setzen
  cb=w;
 }
}

static char const*endofbitname(char const*n) {
 while ((BYTE)*n>=8) n++;
 return n;
}

static void SetBitName(HWND w,char const*p,char const*e,char bit=-1) {
 TCHAR S[16],*s;
 s=append(S,S+elemof(S)-1,p,int(e-p));
 if (bit>=0) s=append(s,S+elemof(S)-1,bit+'0');	// Ziffer
 *s=0;
 SetWindowText(w,S);
 EnableWindow(w,p!=e);		// p==e bedeutet: Disabled
}

static void SetBitNames(BYTE mask,char const*names=0) {
 char const*p=names,*e=names-1;
 byte bitcount=p?0:8,bit=0,n=0;
 for (HWND w=GetDlgItem(MainWnd,16);n<8;w=GetPrevSibling(w),--bitcount,++bit,mask>>=1,++n) {
  if (bitcount) SetBitName(w,p,mask&1?e:p,e!=p?bit:n);
  else{
   p=e+1;
   e=endofbitname(p);
   bit=0;
   bitcount=*e; if (!bitcount) bitcount=1;	// Anzahl Bits
   SetBitName(w,p,mask&1?e:p,p!=e?bitcount<2?-1:bit:n);
  }
 }
 assert(!bitcount);
}

static void SetBitNames() {
 SendDlgItemMessage(MainWnd,84,UDM_SETRANGE32,0,config.datamax());
 for (int i=16; i<24; i++) ShowWindow(GetDlgItem(MainWnd,i),config.flags&0x30?SW_HIDE:SW_SHOWNA);
 if (config.flags&0x30) return;	// Keine Bits bei 16-Bit-Registern anzeigen
// BDT-Adressen: Nur BDnSTAT, n=0..31
// 210620: Bitnamen für BDnSTAT haben sich als verwirrend herausgestellt, doch wieder weg damit
// if (!(config.bdt()&0xFF83)) {	// 0,4,8..124
//  if (false) SetBitNames(0xBF,"BC8\1BC9\1PID\4\1UOWN");		//UOWN=1: SIE kontrolliert BDnSTAT
//  else SetBitNames(0xCF,"BC8\1BC9\1BSTALL\1DTSEN\1\2DTS\1UOWN");//UOWN=0: CPU kontrolliert BDnSTAT
//  return;
// }
 char const*p=pic16f1459bits;
 WORD a=MAKEWORD(p[1],p[0]); p+=2;
 for(;;){
  if (a==config.addr) {
   if (*p==-1) SetBitNames(p[1]);
   else SetBitNames(0xFF,p);
   return;
  }else{
   if (*p==-1) p+=2;	// Maske
   else{
    int i=8;		// 8 Bitdefinitionen, jeweils mit \0 ..\7 abgeschlossen
    do{
     p=endofbitname(p);
     byte c=*p++;
     if (!c) c=1;
     assert(c<=i);
     i-=c;
    }while(i);
   }
  }
  a=MAKEWORD(p[1],p[0]); p+=2;
  if (!a) {
   SetBitNames(0xFF,0);
   return;
  }
 }
}

// Adresse von config.Addr und config.flags (16 bit) interpretieren
// Bit 7 von config.flags: Benutzer hat Adresse manuell eingegeben: SFR dekodieren
static void OutAddressInfo() {
 TCHAR S[64],*s=S,*e=S+elemof(S)-1;
 WORD a=config.addr;
 BYTE f=config.flags;
 if (f&0x80) {
// Symbolanzeige zur Adresse realisieren, dabei Hi- und Lo-Bytes auflösen
  WORD m=a&0xF07F;
  if (m<0x0C || 0x70<=m && m<0x80) a&=0x7F; // Gemeinsame Register auffinden lassen
  f&=0x30;
  for (const char*q,*p=pic16f1459sym; *(q=p); p=q+3) {
   while (*q>=0x20) ++q;	// Ende des Namens finden
   WORD A=MAKEWORD(q[1],q[0]);
   BYTE F=q[2];
   if (!detectedChip
     || detectedChip==4 && F&0x01
     || detectedChip==5 && F&0x02
     || detectedChip==9 && F&0x04) {	// passend zum aktuellen Chip
    char lh=-1;		// Ergebnis der Symbolsuche: "", "L" oder "H" wenn Treffer
    F&=0x30;		// Nur 8/16 bit
    if (!f && F) {	// Byte-Adresse gegeben und 16-Bit-Symbol?
     if (A==a)   lh=F&0x10?'H':'L';	// Big-Endian: Hi auf niederer Adresse
     if (A==a-1) lh=F&0x20?'H':'L';	// Little-Endian: Hi auf höherer Adresse
    }else if (f==F && A==a) lh=0;	// Gleiche Adresse
    if (lh>=0) {
     s=append(s,e,p,int(q-p));
     if (lh) s=append(s,e,lh);
     s=append(s,e,'\n');
     break;
    }
   }
  }
 }
 if (a>=0x8000) {
  TCHAR t[64];
  LoadString(0,42,t,elemof(t));	// "Flash ROM"
  s+=wnsprintf(s,int(e-s),T("%s 0x%04X\n"),t,a&0x1FFF); // Lineare Adresse
 }else{
  BYTE b,ba;	// Bank (0..31 oder 255=any) und Bank-Adresse (0..7F)
  if (a<0x1000) {	// Bank-Adresse in Lineare Adresse umrechnen (wenn möglich)
   b=a>>7; ba=a&0x7F;
   if (ba<0x0C || ba>=0x70) b=255; // In allen Bänken
   a=config.linaddr();		// 0 = keine lineare Adresse
  }else{	// Lineare Adresse in Bank-Adresse umrechnen (wenn möglich)
   WORD aa=a-0x2000;
   b=aa/80; ba=aa%80+0x20;	// PIC-interne Umrechnungsformel
   if (b>=31) ba=0xFF;	// jenseits Bank 30 nicht für PIC adressierbar: 31×80+16=2496=0x9C0
  }
  if (a) {
   if (!(a-0x2000&0xFF80)) {	// Buffer Descriptor Table (Länge sicherlich kürzer)
    const char*t=0,*lh="";
    switch (a&3) {
     case 0: if (!f) t="STAT"; break;
     case 1: if (!f) t="BC"; break;
     case 2: if (!(f&~0x20)) t="ADR"; if (!f) lh="L"; break;
     case 3: if (!f) t="ADR"; lh="H"; break;
    }
    if (t) s+=wnsprintf(s,int(e-s),T("BD%d%hs%hs\n"),a>>2&0x1F,t,lh);
   }	// "%hs" gibt 8-Bit-String aus, egal ob wnsprintfA oder wnsprintfW
   TCHAR t[64];
   BYTE id=38;		// "Undefined";
   if (ba!=0xFF) {
    if (a>=0x23F0) id=41;	// "Unimplemented"
    else if (a>=0x21F0) id=40;	// "Single-Port RAM"
    else if (a>=0x2000) id=39;	// "Dual-Port RAM"
   }
   LoadString(0,id,t,elemof(t));
   s+=wnsprintf(s,int(e-s),T("%s 0x%04X\n"),t,a);	// Lineare Adresse
  }
  if (ba!=255) {		// Bank-Adresse vorhanden? (Lineare Adresse zwischen 0x2000 und 0x29BF)
   TCHAR t[64];
   LoadString(0,36+(b==255),t,elemof(t));	// "Bank %d Addr. %X"
   if (b==255) s+=wnsprintf(s,int(e-s),t,ba);
   else s+=wnsprintf(s,int(e-s),t,b,ba);
  }
 }
 SetDlgItemText(MainWnd,107,S);
}

static void EnableRead() {
 bool f=!!(config.flags&0x08);
 EnableWindow(GetDlgItem(MainWnd,4),f);
 if (f) KillTimer(MainWnd,3);
 else SetTimer(MainWnd,3,config.read_time*10,0);
 SetDlgItemText(MainWnd,103,0);		// Gelesenes Byte nicht mehr anzeigen
}

static INT_PTR CALLBACK TimeDlgProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 switch (msg) {
  case WM_INITDIALOG: {
   SetDlgItemInt(Wnd,11,config.read_time*10,FALSE);
  }return TRUE;
  case WM_COMMAND: switch (wParam) {
   case IDOK: {
    UINT v=GetDlgItemInt(Wnd,11,NULL,FALSE);
    if (v && v<=1000) config.read_time=(v+9)/10;
    else{
     HWND w=GetDlgItem(Wnd,11);
     Edit_SetSel(w,0,-1);
     SetFocus(w);
     MessageBeep(MB_ICONHAND);
     break;
    }
   }/*nobreak*/
   case IDCANCEL: EndDialog(Wnd,wParam); break;
  }break;
 }
 return FALSE;
}

static void HandleMenu(int menupos) {
 if (menupos<0) menupos=3;	// Extrawurst für Lesen-Knopf
 DWORD pos=GetMessagePos();
 HMENU hm=LoadMenu(0,MAKEINTRESOURCE(2));
 HMENU sm=GetSubMenu(hm,menupos);
 BYTE&b=(&config.saveflags)[menupos];
 if (menupos==3) {
  CheckMenuItem(sm,0,config.flags&8?MF_UNCHECKED|MF_BYPOSITION:MF_CHECKED|MF_BYPOSITION);
  EnableMenuItem(sm,1,config.flags&8?MF_DISABLED|MF_BYPOSITION:MF_ENABLED|MF_BYPOSITION);
 }else{
  for (BYTE m=1,i=0;m;m<<=1,i++) CheckMenuItem(sm,i,
    b&m?MF_CHECKED|MF_BYPOSITION:MF_UNCHECKED|MF_BYPOSITION);
 }
 int e=TrackPopupMenu(sm,	// alle IDs sind hier fortlaufend 16..23
   TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RETURNCMD,
   GET_X_LPARAM(pos),GET_Y_LPARAM(pos),0,MainWnd,0);
 if (e>=16) {
  if (menupos==3) switch (e){
   case 0: config.flags^=8; break;	// TODO: Aussehen anpassen!
   case 1: DialogBox(0,MAKEINTRESOURCE(2),MainWnd,TimeDlgProc); break;	// Mini-Dialog
  }else b^=1<<(e-16);	// Bit toggeln
 }
 DestroyMenu(hm);
}

static WNDPROC DefButtonProc;

static LRESULT CALLBACK MyButtonWndProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 switch (msg) {
  case WM_CONTEXTMENU: HandleMenu(GetWindowID(Wnd)-8); break;
 }
 return CallWindowProc(DefButtonProc,Wnd,msg,wParam,lParam);
}

static void MakeSplitButton(HWND w) {
 if (LOBYTE(GetVersion())>=6) {	// Knopf zum SplitButton machen
// Unter Windows XP würde das zum Verschwinden des Knopfes führen.
// Fraglich was passiert wenn comctlXX.dll nicht geladen wird.
  SetWindowLong(w,GWL_STYLE,GetWindowStyle(w)|12);//BS_SPLITBUTTON
 }	//WM_PARENTNOTIFY ist zu umständlich
 DefButtonProc=SubclassWindow(w,MyButtonWndProc);
}

static INT_PTR CALLBACK MainDlgProc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
 switch (msg) {
  case WM_INITDIALOG: {
   MainWnd=Wnd;	// für CloseComm (bei Fehler u.ä.)
   config.saveflags=0x3C;	// standardmßig alle IDs und ohne Flash-Lücken bei 0x3FFF
   config.progflags=0xFF;	// standardmäßig alle Aktionen
   config.veriflags=0x7D;	// standardmäßig alles außer Urlader vergleichen
   config.read_time=10;		// 100 ms
   MakeSplitButton(GetDlgItem(Wnd,8));	// auslesen (Submenü der zu speichernden Items usw.)
   MakeSplitButton(GetDlgItem(Wnd,9));	// programmieren (Submenü der Aktionen)
   MakeSplitButton(GetDlgItem(Wnd,10));	// vergleichen (Submenü was zu vergleichen ist)
//   MakeSplitButton(GetDlgItem(Wnd,4));	// lesen (Millisekunden)
   LoadSettings();
   WINDOWPLACEMENT wp;
   wp.length=sizeof wp;
   GetWindowPlacement(Wnd,&wp);
   OffsetRect(&wp.rcNormalPosition,
     config.posX-wp.rcNormalPosition.left,
     config.posY-wp.rcNormalPosition.top);
   SetWindowPlacement(Wnd,&wp);
   SetClassLongPtr(Wnd,GCLP_HICON,(LONG_PTR)LoadIcon(GetModuleHandle(0),MAKEINTRESOURCE(1)));
   GetWindowText(Wnd,MBoxTitle,elemof(MBoxTitle));
   DuplicateCheckboxes();
   FillComboComPorts();
   HWND hCombo=GetDlgItem(Wnd,102);
   HWND hUpDn=CreateUpDownControl(WS_VISIBLE|WS_CHILD
    |UDS_WRAP|/*UDS_SETBUDDYINT|*/UDS_ALIGNRIGHT/*|UDS_NOTHOUSANDS*/,
    0,0,0,0,Wnd,82,0,hCombo,0xFFF,0,config.addr);
   FillComboSymbols();		// Symbole für Adresse
   SendMessage(hUpDn,UDM_SETBASE,16,0);
   HWND hEdit=GetDlgItem(Wnd,104);		// UpDown für Datenbyte anhängen
   hUpDn=CreateUpDownControl(WS_VISIBLE|WS_CHILD
    |UDS_WRAP|UDS_SETBUDDYINT|UDS_ALIGNRIGHT|UDS_ARROWKEYS|UDS_NOTHOUSANDS,
    0,0,0,0,Wnd,84,0,hEdit,config.datamax(),0,config.data);
//   SetWindowPos(hUpDn,hEdit,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_DEFERERASE);
   SendMessage(hUpDn,UDM_SETBASE,16,0);
#ifdef UNICODE
   int argc;
   PTSTR*argv=CommandLineToArgvW(GetCommandLine(),&argc);
   if (argc>=2) {
    lstrcpyn(HexFileName,argv[1],elemof(HexFileName));
    PostMessage(Wnd,WM_COMMAND,9,0);		// Knopf "Programmieren" drücken
    PostMessage(Wnd,WM_COMMAND,IDCANCEL,0);	// Knopf "Ende" drücken
   }
   GlobalFree(argv);
#endif
   SetDlgItemText(Wnd,105,HexFileName);
   SetTimer(Wnd,2,500,0);
  }return TRUE;
  case WM_DEVICECHANGE: CloseComm(); SetTimer(Wnd,1,2000,0); break;
  case WM_TIMER: switch (wParam) {
   case 1: {	// Wartezeit nach WM_DEVICECHANGE abgelaufen: COM-Schnittstellen neu listen
    KillTimer(Wnd,wParam);
    FillComboComPorts();	// Erneut füllen
    SetTimer(Wnd,2,200,0);
   }break;
   case 2: {	// Wartezeit nach Schnittstellen-Auswahl abgelaufen: PIC suchen
    KillTimer(Wnd,wParam);
    __stosw(IdSpaceW,DefFlash,sizeof IdSpace>>1);
    int r=pubio::Read(0x8200);
    if (r>=0) {
     EnableRead();
     EnableDlgItem(Wnd,5,true);
     for (UINT i=8; i<11; i++) EnableDlgItem(Wnd,i,true);
     if (r!=0xFF) EnableDlgItem(Wnd,11,true);
    }
    if (pubioReadSector(0x8000,IdSpace)==1) {
     WORD id=IdSpaceW[6];
     TCHAR s[64],t[64],p[16];
     byte z=0, lowpower=id&4;
     if (HIBYTE(id)==0x30) switch (LOBYTE(id)&0xFB) {
      case 0x20: z=4; break;	// PIC16F1454
      case 0x21: z=5; break;	// PIC16F1455
      case 0x22: z=8; break;	// PIC16F1458 (obsolet)
      case 0x23: z=9; break;	// PIC16F1459
     }
     if (z) {
      detectedChip=z;
      wnsprintf(p,elemof(p),T("PIC16%sF145%u"),lowpower?T("L"):T(""),z);
      FillComboSymbols();	// Liste ändern/einschränken
     }else{
      LoadString(0,35,s,elemof(s));	// "unbekannt, ID=%X"
      wnsprintf(p,elemof(p),s,id);
     }
     LoadString(0,34,t,elemof(t));
     wnsprintf(s,elemof(s),t,p);
     SetDlgItemText(Wnd,106,s);
    }
   }break;
   case 3: {	// Automatisches Lesen (alle 100 ms, einstellbar)
    if (hCom) SendMessage(Wnd,WM_COMMAND,4,0);	// Knopf „Lesen“ drücken
    else KillTimer(Wnd,wParam);
   }break;
  }break;

  case WM_COMMAND: switch (wParam) {
   case MAKELONG(101,CBN_SELCHANGE): {
    CloseComm();
    config.ComNr=(BYTE)ComboBox_GetItemData((HWND)lParam,ComboBox_GetCurSel((HWND)lParam));
    SetTimer(Wnd,2,200,0);	// Erkennung starten
   }break;
   case MAKELONG(102,CBN_EDITCHANGE): {
    KillTimer(Wnd,3);
    TCHAR s[64];
    s[0]='0';
    s[1]='x';
    BYTE f=0;	// Flags, Bit 3 = veränderlich beim Lesen, Bit 4 = 2 Byte Big Endian, Bit 5 = 2 Byte Little Endian
    TCHAR*e=s+2+GetWindowText((HWND)lParam,s+2,elemof(s)-2);
    while (e!=s) {
     TCHAR c=*--e;
     if (c=='w' && !(f&0x30)) {f|=0x20; *e=0; continue;}	// Little-Endian-Suffix
     if (c=='W' && !(f&0x30)) {f|=0x10; *e=0; continue;}	// Big-Endian-Suffix
     if (c=='v' && !(f&0x08)) {f|=0x08; *e=0; continue;}	// Veränderlich-Suffix
     break;
    }
    TCHAR*p=s;
    if (s[2]=='0' && s[3]=='x') p+=2;
    int a;
    if (!StrToIntEx(p,STIF_SUPPORT_HEX,&a)) {
     MessageBeep(0);	// Bereichsüberlauf, falsche Zeichen u.ä.
     s[0]=0;
    }else{
     config.addr=(WORD)a;
     config.flags=f|0x80;	// Bit 7 = 1: Als Zahl gegeben
     OutAddressInfo();
     SetBitNames();
     EnableRead();
    }
   }break;
   case MAKELONG(102,CBN_SELCHANGE): {
    KillTimer(Wnd,3);
    DWORD a=DWORD(ComboBox_GetItemData((HWND)lParam,ComboBox_GetCurSel((HWND)lParam)));
    config.addr=LOWORD(a);
    config.flags=LOBYTE(HIWORD(a));	// Bit 7 = 0: Als SFR gegeben
    OutAddressInfo();
    SetBitNames();
    EnableRead();	// Knopf "Lesen" aktivieren wenn veränderlich beim Lesen
   }break;
   case MAKELONG(104,EN_CHANGE): {
    TCHAR s[8];			// Weil das UpDownControl immer die Form „0x04AF“ ausgibt
    GetWindowText((HWND)lParam,s,elemof(s));
    if (s[0]=='0' && s[1]=='x') SetWindowText((HWND)lParam,s+(config.flags&0x30?2:4));
    BOOL err;
    WORD d=(WORD)SendDlgItemMessage(Wnd,84,UDM_GETPOS32,0,(LPARAM)&err);
    if (err) MessageBeep(0);	// Bereichsüberlauf, falsche Zeichen u.ä.
    else config.data=d;
   }break;
   case 4: {	// Lesen (automatisch wenn nicht beim Lesen veränderlich oder den Urlader beeinflussend)
    int r=pubio::Read(config.addr);
    if (r<0) {if (lParam) showError(r); break;}
    PCTSTR t=T("%02X");
    if (config.flags&0x30) {	// 16 Bit?
     t=T("%04X");
     int rr=pubio::Read(config.addr+1);
     if (rr<0) {if (lParam) showError(rr); break;}
     r=config.flags&0x20 ? MAKEWORD(r,rr) : MAKEWORD(rr,r);	// Big Endian gibt's nur bei 2 Registern: UFRM und TOS
    }else{
     for (int i=16,rr=r; i<24; i++,rr>>=1) CheckDlgButton(Wnd,i,rr&1);	// Bits anzeigen
    }
    TCHAR s[6];
    wnsprintf(s,elemof(s),t,r);
    SetDlgItemText(Wnd,103,s);		// Byte/Wort anzeigen
   }break;
   case MAKELONG(105,EN_CHANGE): {
    GetWindowText((HWND)lParam,HexFileName,elemof(HexFileName));
   }break;
   case 5: {	// Schreiben
    int r=pubio::Write(config.addr,config.flags&0x10?HIBYTE(config.data):LOBYTE(config.data));
    if (r==1 && config.flags&0x30)
      r=pubio::Write(config.addr+1,config.flags&0x20?HIBYTE(config.data):LOBYTE(config.data));
    showError(r);
   }break;
   case 6: {
    MBox(32,MB_OK|MB_ICONINFORMATION);
   }break;
   case 7: {	// Datei-Dialog
    TCHAR f[64];	// Filter-String mit Nullen aus Ressource
    f[LoadString(0,33,f,elemof(f)-1)+1]=0;	// mit Doppel-Null terminieren
    OPENFILENAME ofn;
    ZeroMemory(&ofn,sizeof ofn);
    ofn.lStructSize=sizeof ofn;
    ofn.hwndOwner=Wnd;
    ofn.lpstrFile=HexFileName;
    ofn.nMaxFile=elemof(HexFileName);
    ofn.lpstrFilter=f;
    ofn.nFilterIndex=1;
    ofn.Flags=OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
    ofn.lpstrDefExt=f+lstrlen(f)+3;	// übergehe "\0*." und zeige auf "hex\0"
    if (GetOpenFileName(&ofn)) {
     SetDlgItemText(Wnd,105,HexFileName);
    }
   }break;
   case 9: {	// Programmieren
    showError(pubio::Prog(),get::line);
   }break;
   case 8: {	// Lesen
    showError(pubio::ReadFlash());
   }break;
   case 10: {	// Vergleichen
    int e=0,r=pubio::Verify();
    if (r>=0) {e=25; r>>=1;}	// Wortadresse
    else if (r<-1) {e=-r; r=get::line;}
    showError(e,r);
   }break;
   case 11: {	// Äpp anspringen
    pubio::JumpToApp();
   }break;
   case IDCANCEL: {
    CloseComm();
    WINDOWPLACEMENT wp;
    wp.length=sizeof wp;
    if (GetWindowPlacement(Wnd,&wp)) {
     config.posX=(short)wp.rcNormalPosition.left;
     config.posY=(short)wp.rcNormalPosition.top;
    }
    SaveSettings();
    EndDialog(Wnd,wParam);
   }break;
   default: if (16<=wParam && wParam<24) {	// Klick auf ein Bit
    if (SendMessage((HWND)lParam,BM_GETCHECK,0,0)==BST_INDETERMINATE) {
     byte mask=0x80;
     for (HWND w=GetDlgItem(Wnd,23);mask;w=GetNextSibling(w),mask>>=1) {
      SendMessage(w,BM_SETCHECK,0,0);
      SetWindowLong(w,GWL_STYLE,GetWindowLong(w,GWL_STYLE)&~BS_3STATE|BS_AUTOCHECKBOX);	// Ab nun wieder nur 2 Zustände
      SendMessage(w,BM_SETCHECK,config.data&mask?1:0,0);
     }
    }else{
     byte b;
     for (int i=24;--i>=16;) b=b<<1|IsDlgButtonChecked(Wnd,i);
     config.data=b;
     SendDlgItemMessage(Wnd,84,UDM_SETPOS32,0,config.data);
     SendMessage(Wnd,WM_COMMAND,5,0);	// Schreiben auslösen
    }
   }
  }break;

  case WM_NOTIFY: {
   NMHDR&hdr=*(NMHDR*)lParam;
   switch (hdr.idFrom) {
    case 82: switch (hdr.code) {
     case UDN_DELTAPOS: {
      NMUPDOWN&ud=*(NMUPDOWN*)lParam;
      config.addr+=ud.iDelta;
      ShowAddress(GetDlgItem(Wnd,102));
     }break;
    }break;
    case 8:	// Knopf „Lesen&Speichern“
    case 9:	// Knopf „Programmieren“
    case 10: switch (hdr.code) {
     case BCN_FIRST+2: HandleMenu(int(hdr.idFrom)-8); break;
    }break;
   }
  }break;

 }
 return FALSE;
}

EXTERN_C void WinMainCRTStartup() {
 InitCommonControls();
 __stosw(HexDataW,DefFlash,sizeof HexData>>1);
 __stosw(IdSpaceW,DefFlash,sizeof IdSpace>>1);
 ExitProcess((UINT)DialogBox(0,MAKEINTRESOURCE(1),0,MainDlgProc));
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded