diff --git a/Code/Visualiser.cpp b/Code/Visualiser.cpp --- a/Code/Visualiser.cpp +++ b/Code/Visualiser.cpp @@ -1,3116 +1,3116 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "iHex.h" #include "lgi/common/List.h" #include "lgi/common/LexCpp.h" #include "lgi/common/ToolBar.h" #include "resdefs.h" #ifndef INT32_MAX #define INT32_MAX 0x7fffffff #endif #define MAX_STR_DISPLAY 64 #define NO_ENUM -999999 // These limits can be undefined to remove them temporarily: // #define MAX_ARRAY 1000 // #define MAX_EXECUTION_TIME 1000 // ms #define MAX_OUTPUT_SIZE (256 << 10) // 256 kb enum BaseType { TypeNull, TypeInteger, TypeFloat, TypeChar, TypeStrZ, TypeNibble, TypeEnum, }; struct Basic { BaseType Type; uint8_t Bytes; int Bits; bool Signed; bool Array; Basic(BaseType type = TypeNull) { Type = type; Bytes = 0; Bits = 0; Signed = false; Array = false; } Basic(const Basic &b) { Type = b.Type; Bytes = b.Bytes; Bits = b.Bits; Signed = b.Signed; Array = b.Array; } }; struct BitReference { union { uint8_t *Ptr; UNativeInt Size; }; int Bit; size_t Len; BitReference() { Ptr = NULL; Bit = 0; Len = 0; } bool IsValid() { return Ptr != NULL && Len > 0; } uint8_t *Aligned() { return (Bit) ? Ptr + 1 : Ptr; } UNativeInt AlignedSize() { return (Bit) ? Size + 1 : Size; } bool SeekBits(int Bits) { while (Bits) { if (Len <= 0) return false; int Avail = 8 - Bit; int Sk = MIN(Bits, Avail); Bits -= Sk; Bit += Sk; if (Bit >= 8) { LAssert(Bit == 8); Bit = 0; Ptr++; Len--; } } return true; } bool SeekBytes(size_t Bytes) { LAssert(Bit == 0); Ptr += Bytes; Len -= Bytes; return true; } bool Seek(Basic *b, ssize_t ArrayLen = -1) { if (ArrayLen < 0) ArrayLen = 1; if (b->Bits) { int WordSize = b->Bytes << 3; Bit += b->Bits * ArrayLen; if (Bit >= WordSize) { Bit -= WordSize; Ptr += b->Bytes; Len -= b->Bytes; } } else { auto Bytes = b->Bytes * ArrayLen; Ptr += Bytes; Len -= Bytes; } return true; } bool AlignForBasic(Basic *b) { if (b->Bits == 0 && Bit) { // Byte align.. if (Len > 0) { Bit = 0; Ptr++; Len--; } else { // No more data return false; } } return true; } template bool ReadBits(T &out, int Bits) { if (Bits == 0) { // Read whole type LAssert(Bit == 0); out = *((T*)Ptr); Ptr += sizeof(T); } else { // Read 'Bits' bits out = 0; int BitSz = sizeof(T) << 3; while (Bits > 0) { int Avail = BitSz - Bit; int Rd = MIN(Avail, Bits); int Mask = (1 << Rd) - 1; int Shift = BitSz - Rd - Bit; LAssert(Shift >= 0); out <<= Rd; out |= ( *((T*)Ptr) >> Shift) & Mask; Bits -= Rd; Bit += Rd; if (Bit >= BitSz) { Bit -= BitSz; Ptr += sizeof(T); Len -= sizeof(T); if (Len <= 0) return Bits > 0; } } } return true; } }; struct ViewContext : public BitReference { LStream &Out; uint8_t *Base, *End; ViewContext(LStream &o) : Out(o) { } ViewContext(const ViewContext &c) : Out(c.Out) { Base = c.Base; End = c.End; } size_t Offset() { LAssert(Ptr != NULL && Ptr >= Base); return Ptr - Base; } void Trace(const char *s) { LgiTrace("%p, %i:%i - %s\n", Ptr, Offset(), Bit, s); } LString ToString() { LString s; s.Printf("%p, %i:%i\n", Ptr, Offset(), Bit); return s; } bool GotoAddress(uint64 Byte, uint8_t BitLoc = 0) { if (!Base) return false; Ptr = Base + Byte; if (Ptr >= End) return false; if (BitLoc >= 8) return false; Bit = BitLoc; Len = End - Ptr; return true; } }; struct AddressRef : public LArray { AddressRef &operator = (const ViewContext &c) { BitReference &r = New(); r = c; return *this; } }; int XCmp(char16 *w, const char *utf, int Len = -1) { int Status = -1; LUtf8Ptr a((char*)utf); if (w && a) { while (Len == -1 || Len-- > 0) { #define Upper(c) ( ((c)>='a' && (c)<='z') ? (c) - 'a' + 'A' : (c) ) uint32_t ca = a; Status = Upper(*w) - Upper(ca); if (Status) break; if (!a || !*w) break; w++; a++; } } return Status; } int16 IfSwap(int16 i, bool Little) { if (!Little) i = ((i & 0xff00) >> 8) | ((i & 0xff) << 8); return i; } uint16 IfSwap(uint16 i, bool Little) { if (!Little) i = ((i & 0xff00) >> 8) | ((i & 0xff) << 8); return i; } int32 IfSwap(int32 i, bool Little) { if (!Little) i = ((i & 0xff000000) >> 24) | ((i & 0xff0000) >> 8) | ((i & 0xff00) << 8) | ((i & 0xff) << 24); return i; } uint32_t IfSwap(uint32_t i, bool Little) { if (!Little) i = ((i & 0xff000000) >> 24) | ((i & 0xff0000) >> 8) | ((i & 0xff00) << 8) | ((i & 0xff) << 24); return i; } int64 IfSwap(int64 i, bool Little) { if (!Little) { uint8_t *p = (uint8_t*)&i; return ((uint64)p[0]) | (((uint64)p[1]) << 8) | (((uint64)p[2]) << 16) | (((uint64)p[3]) << 24) | (((uint64)p[4]) << 32) | (((uint64)p[5]) << 40) | (((uint64)p[6]) << 48) | (((uint64)p[7]) << 56); } return i; } uint64 IfSwap(uint64 i, bool Little) { if (!Little) { uint8_t *p = (uint8_t*)&i; return ((uint64)p[0]) | (((uint64)p[1]) << 8) | (((uint64)p[2]) << 16) | (((uint64)p[3]) << 24) | (((uint64)p[4]) << 32) | (((uint64)p[5]) << 40) | (((uint64)p[6]) << 48) | (((uint64)p[7]) << 56); } return i; } struct ArrayDimension { LArray Expression; ~ArrayDimension() { Expression.DeleteArrays(); } ArrayDimension &operator =(const ArrayDimension &a) { Expression.DeleteArrays(); for (unsigned i=0; i Length; // for arrays ssize_t ResolvedLength; VarDefType() { Init(); } VarDefType(const VarDefType *v) { Init(); if (v) { Base = v->Base; OwnBase = false; Cmplex = v->Cmplex; Length = v->Length; ResolvedLength = v->ResolvedLength; } } ~VarDefType() { if (OwnBase) DeleteObj(Base); } void Sizeof(BitReference &sz); VarDefType &operator =(const VarDefType &v) { Base = v.Base ? new Basic(*v.Base) : NULL; OwnBase = true; Cmplex = v.Cmplex; Length = v.Length; ResolvedLength = v.ResolvedLength; return *this; } }; uint64 DeNibble(uint8_t *ptr, int size) { uint64 n = 0; int bytes = size >> 1; for (int i=0; i Expression; LArray Members; AddressRef Addr; ConditionDef() : Member(MemberCondition) { Little = true; Eval = -1; } ~ConditionDef() { Expression.DeleteArrays(); } VarDef *IsVar() { return NULL; } ConditionDef *IsCondition() { return this; } void Sizeof(BitReference &sz) { if (!Eval) return; for (unsigned i=0; iSizeof(sz); } } bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0); }; struct VarDef : public Member { LDom *SMap; VarDefType *Type; LString::Array Enums; char *Name; LVariant Value; LString TargetType; LString TargetAddr; bool Hidden; bool Debug; VarDef(LDom *smap) : Member(MemberVar) { SMap = smap; Name = 0; Hidden = false; Debug = false; } ~VarDef() { DeleteArray(Name); } VarDef *IsVar() { return this; } ConditionDef *IsCondition() { return NULL; } void Sizeof(BitReference &sz) { if (Type) Type->Sizeof(sz); } bool HasValue(char *&Str) { if (Value.Type != GV_NULL && Type && Type->Base && ((Type->Base->Type == TypeChar) || (Type->Base->Type == TypeStrZ))) { Str = Value.Str(); return true; } return false; } bool HasValue(int &Val) { if (!Value.IsNull() && Type && Type->Base && Type->Base->Type == TypeInteger) { char *v = Value.Str(); if (strnicmp(v, "0x", 2) == 0) { // Hex value Val = htoi(v + 2); } else if (IsAlpha(*v)) { // #define or enum? LVariant vv; if (SMap->GetValue(v, vv)) { Val = vv.CastInt32(); return true; } return false; } else { // Integer? Val = Value.CastInt32(); } return true; } return false; } bool HasValue(float &Val) { if (!Value.IsNull()) { char *v = Value.Str(); if (IsAlpha(*v)) { // #define? LVariant vv; if (!SMap->GetValue(v, vv)) return false; Val = vv.CastDouble(); } else { Val = Value.CastDouble(); } return true; } return false; } bool HasValue(double &Val) { if (!Value.IsNull()) { char *v = Value.Str(); if (IsAlpha(*v)) { // #define? LVariant vv; if (!SMap->GetValue(v, vv)) return false; Val = vv.CastDouble(); } else { Val = Value.CastDouble(); } return true; } return false; } float CastFloat(BitReference &Addr, bool Little) { float f = 0; Basic *b = Type->Base; if (b) { switch (b->Type) { default: LAssert(!"Not impl"); break; case TypeInteger: { int64 Val = CastInt(Addr, Little); f = (float)Val; break; } case TypeFloat: { if (b->Bytes == 4) { f = *((float*)Addr.Aligned()); break; } else if (b->Bytes == 8) { f = *((double*)Addr.Aligned()); } break; } case TypeChar: case TypeStrZ: { f = atof((char*)Addr.Aligned()); break; } case TypeNibble: { uint64 v = DeNibble(Addr.Aligned(), Type->Base->Bytes); f = (float)(int64)v; break; } } } return f; } double CastDouble(BitReference &Addr, bool Little) { double f = 0; Basic *b = Type->Base; if (b) { switch (b->Type) { default: LAssert(!"Not impl"); break; case TypeInteger: { int64 Val = CastInt(Addr, Little); f = (double)Val; break; } case TypeFloat: { if (b->Bytes == 4) { f = *((float*)Addr.Aligned()); break; } else if (b->Bytes == 8) { f = *((double*)Addr.Aligned()); } break; } case TypeChar: case TypeStrZ: { f = atof((char*)Addr.Aligned()); break; } case TypeNibble: { uint64 v = DeNibble(Addr.Aligned(), Type->Base->Bytes); f = (double)(int64)v; break; } } } return f; } int64 CastInt(BitReference Addr, bool Little) { Basic *b = Type->Base; if (!b) return 0; int64 i = 0; switch (b->Type) { default: LAssert(!"Not impl"); break; case TypeInteger: { switch (b->Bytes) { case 1: { uint8_t Val; Addr.ReadBits(Val, b->Bits); if (b->Signed) i = (int8)Val; else i = (uint8_t)Val; break; } case 2: { uint16 Val; Addr.ReadBits(Val, b->Bits); if (b->Signed) i = IfSwap((int16)Val, Little); else i = IfSwap((uint16)Val, Little); break; } case 4: { uint32_t Val; Addr.ReadBits(Val, b->Bits); if (b->Signed) i = IfSwap((int32_t)Val, Little); else i = IfSwap((uint32_t)Val, Little); break; } case 8: { uint64 Val; Addr.ReadBits(Val, b->Bits); if (b->Signed) i = IfSwap((int64)Val, Little); else i = IfSwap((uint64)Val, Little); break; } } break; } case TypeFloat: { LAssert(Addr.Bit == 0); switch (b->Bytes) { case 4: { float *n = (float*)Addr.Aligned(); i = (int64)*n; break; } case 8: { double *n = (double*)Addr.Aligned(); i = (int64)*n; break; } default: { LAssert(!"Unexpected floating pt length"); break; } } break; } case TypeChar: case TypeStrZ: { i = atoi((char*)Addr.Aligned()); break; } case TypeNibble: { i = DeNibble(Addr.Aligned(), b->Bytes); break; } } return i; } }; bool ConditionDef::GetVariant(const char *Name, LVariant &Value, const char *Array) { if (Eval <= 0) { return false; } auto Len = MIN(Members.Length(), Addr.Length()); ConditionDef *c = NULL; for (int i=0; iIsVar(); if (v) { if (v->Name && strcmp(v->Name, Name) == 0) { Value = v->CastInt(Addr[i], Little); return true; } } else if ((c = m->IsCondition())) { if (c->GetVariant(Name, Value, Array)) return true; } } return false; } class StructDef : public LDom { BitReference EvalAddr; bool EvalLittle; public: LString Name; LString Base; LString Address; LArray Members; LArray Children; StructDef() { EvalAddr.Ptr = NULL; EvalLittle = false; } void Sizeof(BitReference &sz) { for (int i=0; iSizeof(sz); } } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) { if (!Name || !EvalAddr.IsValid()) return false; BitReference Addr = EvalAddr; for (auto Mem: Members) { VarDef *c = Mem->IsVar(); if (c && c->Type) { if (c->Name && !stricmp(c->Name, Name)) { switch (c->Type->Base->Type) { case TypeInteger: { if (!c->Type->Base->Array) { Value = c->CastInt(Addr, EvalLittle); return true; } else LAssert(!"Impl me."); break; } case TypeFloat: { if (!c->Type->Base->Array) { Value = c->CastFloat(Addr, EvalLittle); return true; } else LAssert(!"Impl me."); break; } case TypeChar: { if (c->Type->Base->Bytes == 1) { // Utf string... Value.OwnStr(NewStr((char*)Addr.Ptr, c->Type->ResolvedLength)); return true; } else if (c->Type->Base->Bytes == 2) { // Utf16 string... LAssert(!"Impl me."); } else if (c->Type->Base->Bytes == 4) { // Utf32 string... LAssert(!"Impl me."); } else LAssert(!"What?"); break; } default: case TypeStrZ: { break; } } } Addr.Seek(c->Type->Base, c->Type->ResolvedLength); } } return false; } StructDef *GetStruct(const char *n) { if (Name.Equals(n)) return this; for (int i=0; iGetStruct(n); if (s) return s; } return NULL; } StructDef *MatchChild(ViewContext &View, bool Little) { if (Children.Length() == 0) return NULL; // Check to see if any child struct specializations match for (int c=0; cMatch(r, Little)) { return Children[c]; } } return NULL; } LDom *GetReference(BitReference &Addr, bool Little) { LAssert(Addr.Ptr != NULL); EvalAddr = Addr; EvalLittle = Little; return this; } bool Match(BitReference &Addr, bool Little) { if (!Addr.IsValid()) return false; bool Status = false; EvalAddr = Addr; EvalLittle = Little; for (int i=0; iIsVar(); if (d && d->Type) { Basic *b = d->Type->Base; if (b) { if ((b->Type == TypeChar) || (b->Type == TypeStrZ)) { char *Str = 0; if (d->HasValue(Str)) { if (Str && strnicmp((char*)Addr.Aligned(), Str, strlen(Str)) != 0) { return false; } else Status = true; } } else if (d->Type->Base->Type == TypeInteger) { int Val = 0; if (d->HasValue(Val)) { uint64 i = d->CastInt(Addr, Little); if (i != Val) { return false; } else Status = true; } } else if (d->Type->Base->Type == TypeFloat) { float Val = 0; if (d->HasValue(Val)) { float f = d->CastFloat(Addr, Little); if (f != Val) { return false; } else Status = true; } } else LAssert(0); d->Type->ResolvedLength = 1; if (d->Type->Length.Length() > 0) { ArrayDimension &ad = d->Type->Length.First(); if (ad.Expression.Length() == 1) { d->Type->ResolvedLength = AtoiW(ad.Expression[0]); } else { // Use the script engine to evaluate the expression... LString::Array a; for (unsigned i=0; iType->ResolvedLength = Result.CastInt32(); } } if (d->Type->ResolvedLength < 0) { break; } LAssert(b->Bits == 0); // impl bit seeking support? Addr.Ptr += b->Bytes * d->Type->ResolvedLength; Addr.Len -= b->Bytes * d->Type->ResolvedLength; } else if (d->Type->Cmplex) { Status |= d->Type->Cmplex->Match(Addr, Little); } else { LAssert(0); } } } return Status; } }; void VarDefType::Sizeof(BitReference &sz) { if (Base) { if (Base->Bits) sz.SeekBits(Base->Bits); else sz.SeekBytes(Base->Bytes); } else if (Cmplex) { Cmplex->Sizeof(sz); } } struct EnumDef { LString Name; LHashTbl,int64> ToValue; LHashTbl,LString> ToName; EnumDef() : ToValue(0, NO_ENUM) { } }; class StructureMap : public LListItem, public LDom { AppWnd *App; char *File; char *Body; LStringPipe Errs; bool Little; char Tabs[256]; uint64 StartTs; struct ScopeType { int Pos; LArray *Members; AddressRef Addr; }; LArray Stack; LHashTbl, ssize_t> KeywordValues; public: LHashTbl, char16*> Defines; LHashTbl, EnumDef*> Enums; LArray Compiled; StructDef *GetStruct(const char *Name) { if (Name) { for (int i=0; iGetStruct(Name); if (s) return s; } } return 0; } StructureMap(AppWnd *app, char *file = 0) : KeywordValues(16, -1) { App = app; File = 0; Body = 0; SetFile(file); } ~StructureMap() { DeleteArray(File); DeleteArray(Body); Compiled.DeleteObjects(); } bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0) { if (!Name) return false; auto Kv = KeywordValues.Find(Name); if (Kv >= 0) { Value = Kv; return true; } // Walk up the stack of scopes looking for a variable matching 'Name' for (auto Scope = Stack.Length() - 1; Scope >= 0; Scope--) { ScopeType &s = Stack[Scope]; int n = 0; VarDef *Var = NULL; ConditionDef *Cond = NULL; for (; nIsVar(); if (Var) { if (Var->Name && strcmp(Var->Name, Name) == 0) { LAssert(s.Addr.Length() > n); if (Var->Type && Var->Type->Cmplex) { Value = Var->Type->Cmplex->GetReference(s.Addr[n], Little); } else { Value = Var->CastInt(s.Addr[n], Little); } return true; } } else if ((Cond = Mem->IsCondition())) { Cond->Little = Little; if (Cond->GetVariant(Name, Value, Array)) return true; } } } // If it's not in any scope, check the #defines as well char16 *v = Defines.Find(Name); if (v) { LAutoString u(WideToUtf8(v)); LScriptEngine e(App, App, NULL); bool Status = e.EvaluateExpression(&Value, this, u); return Status; } // Check enums? for (auto e : Enums) { int64 v = e.value->ToValue.Find(Name); if (v != NO_ENUM) { Value = v; return true; } } return false; } template T *DisplayString(T *str, size_t chars, bool Signed = true) { LArray out; for (size_t i=0; i= 0x7f) { char hex[16]; int ch = sprintf_s(hex, sizeof(hex), "\\x%x", (uint8_t)str[i]); for (int c=0; c bool Read ( /// The output type (integer only) T &Out, /// The input stream of bytes. A copy is used to avoid /// changing the contents of the source View. Updating /// the position is separate to reading the data. BitReference Ref, /// The number of bits to read int Bits ) { return Ref.ReadBits(Out, Bits); } bool DoInt(VarDef *d, ViewContext &View, ssize_t &ArrayLength) { Basic *b = d->Type->Base; if (!d->Hidden) { View.Out.Print("%s%s", Tabs, d->Name); if (ArrayLength < 1) { View.Out.Print("[0]\n"); return true; } else if (ArrayLength > 1) View.Out.Print("[%i]:\n", ArrayLength); else View.Out.Print(" = "); } for (int n=0; n= b->Bytes; n++) { if (!View.AlignForBasic(b)) return false; if (!d->Hidden) { if (ArrayLength > 1) View.Out.Print(" %s[%i] = ", Tabs, n); char *LeadIn = ArrayLength > 1 ? Tabs : (char*)""; bool Displayed = false; if (d->Enums.Length()) { int64 Val = d->CastInt(View, Little); LString EnumName; for (auto e : d->Enums) { EnumDef *Ed = Enums.Find(e); if (Ed) { EnumName = Ed->ToName.Find(Val); if (EnumName) break; } } if (EnumName) { View.Out.Print("%s%s (" LPrintfInt64 ")\n", LeadIn, EnumName.Get(), Val); Displayed = true; } } if (!Displayed) { switch (b->Bytes) { case 1: { uint8_t Byte; if (!Read(Byte, View, b->Bits)) return false; if (b->Signed) View.Out.Print("%s%i (0x%02.2x)\n", LeadIn, (int8)Byte, (int8)Byte); else View.Out.Print("%s%u (0x%02.2x)\n", LeadIn, (uint8_t)Byte, (uint8_t)Byte); break; } case 2: { uint16 Short; if (!Read(Short, View, b->Bits)) return false; if (b->Signed) { int16 n = IfSwap(((int16)Short), Little); View.Out.Print("%s%i (0x%04.4x)\n", LeadIn, n, n); } else { uint16 n = IfSwap(Short, Little); View.Out.Print("%s%u (0x%04.4x)\n", LeadIn, n, n); } break; } case 4: { uint32_t Int; if (!Read(Int, View, b->Bits)) return false; if (b->Signed) { int32_t n = IfSwap((int32_t)Int, Little); View.Out.Print("%s%i (0x%08.8x)\n", LeadIn, n, n); } else { uint32_t n = IfSwap(Int, Little); View.Out.Print("%s%u (0x%08.8x)\n", LeadIn, n, n); } break; } case 8: { uint64 Long; if (!Read(Long, View, b->Bits)) return false; if (b->Signed) { int64 n = IfSwap((int64)Long, Little); #ifdef WIN32 View.Out.Print("%s%I64i (0x%16.16lI64x)\n", LeadIn, n, n); #else View.Out.Print("%s%li (0x%16.16lx)\n", LeadIn, n, n); #endif } else { uint64 n = IfSwap(Long, Little); #ifdef WIN32 View.Out.Print("%s%I64u (0x%16.16lI64x)\n", LeadIn, n, n); #else View.Out.Print("%s%lu (0x%16.16lx)\n", LeadIn, n, n); #endif } break; } } } } int Val = 0; if (d->HasValue(Val)) { BitReference Ref; Ref = View; auto a = d->CastInt(Ref, Little); if (a != Val) { View.Out.Print("%sValue Mismatch!\n", Tabs); return false; } } if (!View.Seek(b)) return false; } return true; } bool DoFloat(VarDef *d, ViewContext &View, ssize_t &ArrayLength) { Basic *b = d->Type->Base; if (!d->Hidden) { View.Out.Print("%s%s", Tabs, d->Name); if (ArrayLength > 1) View.Out.Print("[%i]:\n", ArrayLength); else View.Out.Print(" = "); } for (int n=0; n= b->Bytes; n++) { if (!d->Hidden) { if (ArrayLength > 1) View.Out.Print("\t%s[%i] = ", Tabs, n); char *LeadIn = ArrayLength > 1 ? Tabs : (char*)""; switch (b->Bytes) { case 4: { LAssert(sizeof(float) == 4); float flt = *((float*)View.Ptr); #define Swap(a, b) { uint8_t t = a; a = b; b = t; } if (!Little) { uint8_t *c = (uint8_t*)&flt; Swap(c[0], c[3]); Swap(c[1], c[2]); } double dbl = flt; View.Out.Print("%s%g\n", LeadIn, dbl); float Val = 0; if (d->HasValue(Val)) { if (d->CastFloat(View, Little) != Val) { View.Out.Print("%sValue Mismatch!\n", Tabs); return false; } } break; } case 8: { LAssert(sizeof(double) == 8); double dbl = *((double*)View.Aligned()); if (!Little) { uint8_t *c = (uint8_t*)&dbl; Swap(c[0], c[7]); Swap(c[1], c[6]); Swap(c[2], c[5]); Swap(c[3], c[4]); } View.Out.Print("%s%g\n", LeadIn, dbl); double Val = 0; if (d->HasValue(Val)) { if (d->CastDouble(View, Little) != Val) { View.Out.Print("%sValue Mismatch!\n", Tabs); return false; } } break; } default: { View.Out.Print("#error (%s:%i)\n", LeadIn, __FILE__, __LINE__); break; } } } View.Seek(b); } return true; } bool DoNibble(VarDef *d, ViewContext &View, ssize_t &ArrayLength) { Basic *b = d->Type->Base; if (!d->Hidden) { View.Out.Print("%s%s", Tabs, d->Name); if (ArrayLength > 1) View.Out.Print("[%i]:\n", ArrayLength); else View.Out.Print(" = "); } for (int n=0; n= b->Bytes; n++) { if (!d->Hidden) { if (ArrayLength > 1) View.Out.Print("\t%s[%i] = ", Tabs, n); char *LeadIn = ArrayLength > 1 ? Tabs : (char*)""; switch (b->Bytes) { case 2: { uint8_t n = DeNibble(View.Aligned(), b->Bytes); View.Out.Print("%s%u (0x%02.2x)\n", LeadIn, n, n); break; } case 4: { uint16 v = DeNibble(View.Aligned(), b->Bytes); uint16 n = IfSwap(v, Little); View.Out.Print("%s%u (0x%04.4x)\n", LeadIn, n, n); break; } case 8: { uint32_t v = (uint32_t)DeNibble(View.Aligned(), b->Bytes); uint32_t n = IfSwap(v, Little); View.Out.Print("%s%i (0x%08.8x)\n", LeadIn, n, n); break; } case 16: { uint64 v = DeNibble(View.Aligned(), b->Bytes); uint64 n = IfSwap(v, Little); View.Out.Print("%s%I64u (0x%016.16I64x)\n", LeadIn, n, n); break; } default: LAssert(!"Not impl."); break; } } int Val = 0; if (d->HasValue(Val)) { if (d->CastInt(View, Little) != Val) { View.Out.Print("%sValue Mismatch!\n", Tabs); return false; } } View.Seek(b); } return true; } bool DoString(VarDef *d, ViewContext &View, ssize_t &ArrayLength) { if (!d->Hidden) { if (d->Type->Base->Bytes == 1) { LAutoString u(DisplayString((char*)View.Aligned(), ArrayLength, d->Type->Base->Signed)); View.Out.Print("%s%s[%i] = '%.*s'%s\n", Tabs, d->Name, ArrayLength, MIN(MAX_STR_DISPLAY, ArrayLength), u.Get(), ArrayLength>MAX_STR_DISPLAY?"...":""); if (u && d->Value.Str()) { if (strnicmp(u, d->Value.Str(), ArrayLength) != 0) { View.Out.Print("%sValue Mismatch!\n", Tabs); return false; } } } else if (d->Type->Base->Bytes == 2) { char16 *w = new char16[ArrayLength+1]; char16 *u = 0; if (w) { char16 *Src = (char16*)View.Aligned(); for (int i=0; iName, u); if (d->Value.WStr()) { if (StrnicmpW(u, d->Value.WStr(), ArrayLength) != 0) { View.Out.Print("%sValue Mismatch!\n", Tabs); DeleteArray(u); return false; } } DeleteArray(u); } } View.SeekBytes(ArrayLength * d->Type->Base->Bytes); return true; } bool DoStrZ(VarDef *d, ViewContext &View, ssize_t &ArrayLength) { if (!d->Hidden && (d->Type->Base->Bytes < 8)) { View.Out.Print("%s%s", Tabs, d->Name); if (ArrayLength > 1) View.Out.Print("[%i]:\n", ArrayLength); else View.Out.Print(" = "); } ssize_t ArrayLen = ArrayLength >= 0 ? ArrayLength : 1; for (int n=0; n= d->Type->Base->Bytes; n++) { if (!d->Hidden && (d->Type->Base->Bytes < 8)) { if (ArrayLength > 1) View.Out.Print("\t%s[%i] = ", Tabs, n); } int zstringLen = 0; if (d->Type->Base->Bytes == 1) { while (*(View.Aligned() + zstringLen)) { zstringLen++; } char *u = (char*) LNewConvertCp("utf-8", View.Aligned(), "iso-8859-1", zstringLen); if (!d->Hidden) { View.Out.Print("'%s'\n", u); if (u && d->Value.Str()) { if (strnicmp(u, d->Value.Str(), zstringLen) != 0) { View.Out.Print("%sValue Mismatch!\n", Tabs); DeleteArray(u); return false; } } } DeleteArray(u); } else if (d->Type->Base->Bytes == 2) { while ((char16)*(View.Aligned() + zstringLen * d->Type->Base->Bytes)) { zstringLen++; } char16 *w = new char16[zstringLen+1]; char *u = 0; if (w) { char16 *Src = (char16*)View.Aligned(); for (int i=0; iHidden) { View.Out.Print("'%s'\n", u); if (d->Value.Str()) { if (strnicmp(u, d->Value.Str(), zstringLen) != 0) { View.Out.Print("%sValue Mismatch!\n", Tabs); DeleteArray(u); return false; } } } DeleteArray(u); } else if (d->Type->Base->Bytes == 8) { // Just skip passed the string while ((uint64)*(View.Aligned() + zstringLen * d->Type->Base->Bytes)) { zstringLen++; } } zstringLen += 1; // Take null terminator into account View.SeekBytes(zstringLen * d->Type->Base->Bytes); } return true; } bool DoMember(Member *Mem, ViewContext *View, ScopeType &Scope, int Depth) { int64 Sz = View->Out.GetSize(); ViewContext LocalView(*View); // uint64 Time = LCurrentTime() - StartTs; if (!Mem || #ifdef MAX_EXECUTION_TIME Time >= MAX_EXECUTION_TIME || #endif #ifdef MAX_OUTPUT_SIZE Sz >= MAX_OUTPUT_SIZE #endif ) return false; ConditionDef *c; VarDef *d = Mem->IsVar(); if (d) { d->Type->ResolvedLength = 1; if (d->Type->Length.Length()) { LArray DimStr; d->Type->ResolvedLength = -1; // Unknown for (unsigned dim = 0; dim < d->Type->Length.Length(); dim++) { LStringPipe p; ArrayDimension &ad = d->Type->Length[dim]; for (unsigned n=0; nType->ResolvedLength = v.CastInt32(); #ifdef MAX_ARRAY if (ArrayLength > MAX_ARRAY) ArrayLength = MAX_ARRAY; #endif if (d->Type->ResolvedLength < 0) d->Type->ResolvedLength = 0; BitReference Sz; Sz.Len = INT32_MAX; d->Sizeof(Sz); if (d->Type->ResolvedLength * Sz.AlignedSize() > View->Len) d->Type->ResolvedLength = View->Len / Sz.AlignedSize(); } else { View->Out.Print("Error: evaluating the expression '%s'\n", DimStr[0]); } } DimStr.DeleteArrays(); } if (d->TargetAddr) { // Need to seek to the target address LScriptEngine e(App, App, NULL); LVariant v; KeywordValues.Empty(); KeywordValues.Add("this", Scope.Addr[0].Ptr - View->Base); KeywordValues.Add("pointer", d->CastInt(*View, Little) ); if (e.EvaluateExpression(&v, this, d->TargetAddr)) { View = &LocalView; LocalView.GotoAddress(v.CastInt32()); } else View->Out.Print("Error: evaluating the expression '%s'\n", d->TargetAddr.Get()); } VarDefType LocalType; VarDefType *OldType = d->Type; if (d->TargetType) { LocalType.Cmplex = GetStruct(d->TargetType); if (LocalType.Cmplex) { LocalType.Base = NULL; d->Type = &LocalType; if (!d->TargetAddr) { // Assume offset from start of data LocalView.GotoAddress(d->CastInt(*View, Little)); View = &LocalView; } } else { LAutoWString w(Utf8ToWide(d->TargetType)); if (!w) View->Out.Print("Error: alloc error\n"); else { LAutoPtr t(ParseDefType(w, false)); if (!t) View->Out.Print("Error: no type '%s'\n", d->TargetType.Get()); else { LocalType = *t; d->Type = &LocalType; } } } } if (d->Type->Base) { Basic *b = d->Type->Base; switch (b->Type) { default: LAssert(!"Not impl"); break; case TypeInteger: { if (!DoInt(d, *View, d->Type->ResolvedLength)) return false; break; } case TypeFloat: { if (!DoFloat(d, *View, d->Type->ResolvedLength)) return false; break; } case TypeNibble: { if (!DoNibble(d, *View, d->Type->ResolvedLength)) return false; break; } case TypeChar: { if (!DoString(d, *View, d->Type->ResolvedLength)) return false; break; } case TypeStrZ: { if (!DoStrZ(d, *View, d->Type->ResolvedLength)) return false; break; } } } else if (d->Type->Cmplex) { StructDef *s = d->Type->Cmplex; StructDef *sub = s->MatchChild(*View, Little); auto Offset = View->Offset(); if (sub && d->Type->ResolvedLength == 1) View->Out.Print("%s%s.%s", Tabs, sub->Name.Get(), d->Name); else View->Out.Print("%s%s", Tabs, d->Name); if (d->Type->ResolvedLength > 1 || d->Type->ResolvedLength == 0) View->Out.Print("[%i]", d->Type->ResolvedLength); else if (d->Type->ResolvedLength < 0) View->Out.Print("[]"); View->Out.Print(" (@ %i/0x%x) =\n", Offset, Offset); if (d->Type->ResolvedLength == 1) View->Out.Print("%s{\n", Tabs); for (int i=0; (d->Type->ResolvedLength < 0 || i < d->Type->ResolvedLength) && View->Len > 0; i++) { s = d->Type->Cmplex; if (i) sub = s->MatchChild(*View, Little); if (sub) s = sub; if (d->Type->ResolvedLength != 1) { Offset = View->Offset(); View->Out.Print("%s [%i] (%s @ %i/0x%x)\n", Tabs, i, s->Name.Get(), Offset, Offset); } if (!DoStruct(s, View, Little, Depth + 1 + (d->Type->ResolvedLength != 1 ? 1 : 0))) { return false; } SetTabs(Depth); } if (d->Type->ResolvedLength == 1) View->Out.Print("%s}\n", Tabs); } d->Type = OldType; } else if ((c = Mem->IsCondition())) { LStringPipe p; for (unsigned n=0; nExpression.Length(); n++) { p.Print(" %S", c->Expression[n]); } LAutoString Exp(p.NewStr()); LScriptEngine e(App, App, NULL); LVariant v; if (!e.EvaluateExpression(&v, this, Exp)) { View->Out.Print("Error: Couldn't evaluate '%s'\n", Exp.Get()); return false; } c->Eval = v.CastInt32() != 0; View->Out.Print("%s// Expression:%s = %i\n", Tabs, Exp.Get(), c->Eval); if (c->Eval) { auto InitLen = Stack.Length(); ScopeType &Sub = Stack.New(); Sub.Members = &c->Members; for (Sub.Pos=0; Sub.PosMembers.Length(); Sub.Pos++) { Member *Mem = c->Members[Sub.Pos]; // This saves the member address in case we need to resolve // one of the members to an array index. c->Addr[Sub.Pos] = *View; if (!DoMember(Mem, View, Sub, Depth)) return false; } Stack.Length(InitLen); } } return true; } void SetTabs(int Depth) { ZeroObj(Tabs); memset(Tabs, ' ', Depth * 2); } bool DoStruct(StructDef *s, ViewContext *View, bool little, int Depth = 0) { SetTabs(Depth); auto InitLen = Stack.Length(); ScopeType &Scope = Stack.New(); Scope.Members = &s->Members; Little = little; if (s->Address) { // Resolve the address and seek to the right location... LScriptEngine e(App, App, NULL); LVariant v; if (e.EvaluateExpression(&v, this, s->Address)) { auto Addr = v.CastInt64(); if (Addr > 0) { if (!View->GotoAddress(Addr)) View->Out.Print("Error: Can't goto adddres: " LPrintfInt64 " .\n", Addr); } else View->Out.Print("Error: '%s' doesn't evaluate to an adddres.\n", s->Address.Get()); } else View->Out.Print("Error: evaluating the expression '%s'\n", s->Address.Get()); } bool Error = false; for (Scope.Pos=0; Scope.PosMembers.Length() && View->Len > 0; Scope.Pos++) { Member *Mem = s->Members[Scope.Pos]; Scope.Addr[Scope.Pos] = *View; if (!DoMember(Mem, View, Scope, Depth)) { Error = true; break; } } Stack.Length(InitLen); return !Error; } void Visualise(char *Data, size_t Len, LStream &Out, bool Little) { StartTs = LCurrentTime(); StructDef *Main = GetStruct("Main"); if (Main) { ViewContext Ctx(Out); Ctx.Base = (uint8_t*)Data; Ctx.Ptr = (uint8_t*)Data; Ctx.Len = Len; Ctx.End = Ctx.Base + Len; Ctx.Bit = 0; DoStruct(Main, &Ctx, Little); } else { Out.Print("No main defined."); } } char *GetFile() { return File; } void SetFile(char *NewName) { char *f = NewStr(NewName); char *d = f ? strrchr(f, DIR_CHAR) : 0; if (f) { char s[256]; strcpy_s(s, sizeof(s), d + 1); d = strrchr(s, '.'); if (d) *d = 0; SetText(s); if (File) { FileDev->Move(File, f); DeleteArray(File); } File = f; } } char *GetBody() { if (!Body && File) { Body = LReadTextFile(File); } return Body; } void SetBody(const char *s) { char *b = NewStr(s); DeleteArray(Body); Body = b; if (File) { LFile f; if (f.Open(File, O_WRITE)) { f.SetSize(0); if (Body) f.Write(Body, strlen(Body)); } } } class CompileState { LArray MemA; LArray MemW; public: bool Error; LAutoWString Base; char16 *s; CompileState(char *Init) { Error = false; s = NULL; if (Init) { Base.Reset(Utf8ToWide(Init)); s = Base; } } ~CompileState() { MemA.DeleteArrays(); MemW.DeleteArrays(); } char *NextA(bool AutoFree = true) { char16 *t = LexCpp(s, LexStrdup); if (!t) return NULL; char *u = WideToUtf8(t); DeleteArray(t); if (!u) return NULL; if (AutoFree) MemA.Add(u); return u; } char16 *NextW(bool AutoFree = true) { char16 *t = LexCpp(s, LexStrdup); if (!t) return NULL; if (AutoFree) MemW.Add(t); return t; } }; bool Err(CompileState &State, const char *Msg) { int Line = 1; char16 *Cur = State.s; while (Cur > State.Base) { if (*Cur == '\n') Line++; Cur--; } char *d = File ? strrchr(File, DIR_CHAR) : 0; Errs.Print("%s:%i - %s\n", d ? d + 1 : File, Line, Msg); return false; } char *To8(char16 *w) { char *u = WideToUtf8(w); DeleteArray(w); return u; } LString GetErrors() { - return Errs.NewGStr(); + return Errs.NewLStr(); } VarDefType *ParseDefType(char16 *t, bool ForceUnsigned) { VarDefType *v = 0; if (XCmp(t, "int", 3) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = !ForceUnsigned; v->Base->Type = TypeInteger; if (XCmp(t+3, "8") == 0) v->Base->Bytes = 1; else if (XCmp(t+3, "16") == 0) v->Base->Bytes = 2; else if (XCmp(t+3, "64") == 0) v->Base->Bytes = 8; else v->Base->Bytes = 4; } else if (XCmp(t, "uint", 4) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = false; v->Base->Type = TypeInteger; t += 4; auto Bits = Atoi(t); if (Bits == 8) v->Base->Bytes = 1; else if (Bits == 16) v->Base->Bytes = 2; else if (Bits == 64) v->Base->Bytes = 8; else v->Base->Bytes = 4; } else if (XCmp(t, "nibble", 4) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = false; v->Base->Type = TypeNibble; int Bits = AtoiW(t + 6); v->Base->Bytes = Bits ? Bits / 8 : 2; } else if (XCmp(t, "float", 5) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = true; v->Base->Type = TypeFloat; v->Base->Bytes = 4; } else if (XCmp(t, "double", 6) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = true; v->Base->Type = TypeFloat; v->Base->Bytes = 8; } else if (XCmp(t, "char", 4) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = !ForceUnsigned; v->Base->Type = TypeChar; if (XCmp(t+4, "16") == 0) v->Base->Bytes = 2; else if (XCmp(t+4, "64") == 0) v->Base->Bytes = 8; else v->Base->Bytes = 1; } else if (XCmp(t, "strz", 4) == 0) { v = new VarDefType; v->Base = new Basic; v->Base->Array = false; v->Base->Signed = false; v->Base->Type = TypeStrZ; if (XCmp(t+4, "16") == 0) v->Base->Bytes = 2; else if (XCmp(t+4, "64") == 0) v->Base->Bytes = 8; else v->Base->Bytes = 1; } else if (XCmp(t, "BYTE", 4) == 0) { v = new VarDefType; v->Base = new Basic(TypeInteger); v->Base->Bytes = 1; } else if (XCmp(t, "WORD", 4) == 0) { v = new VarDefType; v->Base = new Basic(TypeInteger); v->Base->Bytes = 2; } else if (XCmp(t, "DWORD", 4) == 0) { v = new VarDefType; v->Base = new Basic(TypeInteger); v->Base->Bytes = 4; } else { char *u = WideToUtf8(t); if (u) { StructDef *s = GetStruct(u); if (s) { v = new VarDefType; v->Cmplex = s; } DeleteArray(u); } } return v; } VarDef *ParseVar(CompileState &State, char16 *t) { bool IsHidden = false; bool IsDebug = false; bool ForceUnsigned = false; if (!XCmp(t, "hidden")) { IsHidden = true; t = State.NextW(); } if (!XCmp(t, "debug")) { IsDebug = true; t = State.NextW(); } if (!XCmp(t, "unsigned")) { ForceUnsigned = true; t = State.NextW(); } VarDefType *Type = ParseDefType(t, ForceUnsigned); if (!Type) { char m[256], *u = WideToUtf8(t); sprintf(m, "expected type, got '%s' instead", u); Err(State, m); DeleteArray(u); return NULL; } LAutoPtr Var(new VarDef(this)); if (!Var) { Err(State, "allocation error"); return NULL; } Var->Type = Type; Var->Hidden = IsHidden; Var->Debug = IsDebug; t = State.NextW(); if (!XCmp(t, "<")) { Var->Enums.SetFixedLength(false); Var->Enums.Add(State.NextW()); t = State.NextW(); if (XCmp(t, ">")) { Err(State, "expecting '>'"); return NULL; } t = State.NextW(); } Var->Name = WideToUtf8(t); if (!Var->Name) { Err(State, "no name for vardef"); return NULL; } t = State.NextW(); if (!t) { Err(State, "expecting end of var def ';' or '[' for array"); return NULL; } if (t && !XCmp(t, ":")) { // Bitfield t = State.NextW(); Var->Type->Base->Bits = AtoiW(t); LAssert(Var->Type->Base->Bits != 0); t = State.NextW(); } while (!XCmp(t, "[")) { // Array ArrayDimension &ad = Var->Type->Length.New(); int Depth = 0; while ((t = State.NextW(false))) { if (!XCmp(t, "[")) { Depth++; ad.Expression.Add(t); } else if (!XCmp(t, "]")) { if (Depth) { Depth--; ad.Expression.Add(t); } else { // Finished expression break; } } else { ad.Expression.Add(t); } } t = State.NextW(); } if (t && !XCmp(t, "=")) { // Constraint Var->Value.OwnStr(TrimStr(State.NextA(false), "\"\'")); t = State.NextW(); } if (t && !XCmp(t, "*")) { // Pointer type Var->TargetType = State.NextA(); t = State.NextW(); } if (t && !XCmp(t, "@")) { Var->TargetAddr = State.NextA(); Var->TargetAddr = Var->TargetAddr.Strip("\"\'"); t = State.NextW(); } if (t && XCmp(t, ";") == 0) { } else { Err(State, "expected ';'"); return NULL; } return Var.Release(); } bool DoDefine(CompileState &State) { char *Name = State.NextA(); while (*State.s && strchr(WhiteSpace, *State.s)) State.s++; char16 *Eol = StrchrW(State.s, '\n'); if (!Eol) Eol = State.s + StrlenW(State.s); LAutoWString Value(NewStrW(State.s, Eol - State.s)); State.s = *Eol ? Eol + 1 : Eol; auto *Comment = Strstr(Value.Get(), L"//"); if (Comment) *Comment = 0; Defines.Add(Name, Value.Release()); return true; } bool Compile() { CompileState State(GetBody()); Compiled.DeleteObjects(); if (State.Base) { #define CheckTok(lit) \ if (!(t && XCmp(t, lit) == 0)) \ { \ char m[256], *u = WideToUtf8(t); \ sprintf(m, "expecting '%s', got '%s'", lit, u); \ Err(State, m); \ DeleteArray(u) \ State.Error = true; \ break; \ } #define Literal(lit) \ t = State.NextW(); \ CheckTok(lit); char16 *t = 0; while (!State.Error) { t = State.NextW(); if (!t) break; if (!XCmp(t, "#define")) { if (!DoDefine(State)) return false; } else if (!XCmp(t, "struct")) { // Parse struct def StructDef *Def = new StructDef; if (Def) { Def->Name = State.NextA(); t = State.NextW(); while (XCmp(t, "{")) { if (XCmp(t, ":") == 0) { Literal("inherits"); Def->Base = State.NextA(); t = State.NextW(); } else if (XCmp(t, "@") == 0) { Def->Address = State.NextA(); t = State.NextW(); } else { Err(State, "unexpected token."); return false; } } CheckTok("{"); while (!State.Error) { // Parse types... t = State.NextW(); DoType: if (!t) { Err(State, "No token"); State.Error = true; break; } if (!XCmp(t, "#define")) { if (!DoDefine(State)) return false; } else if (!XCmp(t, "if")) { t = State.NextW(); CheckTok("("); LAutoPtr c(new ConditionDef); if (!c) break; while ((t = State.NextW(false))) { if (!XCmp(t, ")")) { DeleteArray(t); break; } c->Expression.Add(t); } t = State.NextW(); CheckTok("{"); while ((t = State.NextW())) { if (!XCmp(t, "}")) { break; } else { VarDef *v = ParseVar(State, t); if (!v) break; c->Members.Add(v); } } Def->Members.Add(c.Release()); } else if (!XCmp(t,"}")) { Literal(";"); break; } else { VarDef *v = ParseVar(State, t); if (!v) { LString Msg; Msg.Printf("ParseVar failed at '%S'", t); Err(State, Msg); break; } Def->Members.Add(v); } } if (Def->Base) { StructDef *Parent = GetStruct(Def->Base); if (Parent) { Parent->Children.Add(Def); } else { Err(State, "parent class not defined."); } } else { Compiled.Add(Def); } } } else if (!XCmp(t, "enum")) { LAutoPtr Enum(new EnumDef); if (!Enum) return Err(State, "alloc failed."); Enum->Name = State.NextW(); t = State.NextW(); if (XCmp(t, "{")) return Err(State, "expecting '{'"); int64 Prev = -1; while (t) { t = State.NextW(); if (!XCmp(t, "}")) break; LString Name = t, Val; t = State.NextW(); if (!XCmp(t, "=")) Val = State.NextW(); t = State.NextW(); if (Name) { int64 v = Val ? Prev = Val.Int() : ++Prev; Enum->ToValue.Add(Name, v); Enum->ToName.Add(v, Name); } if (!XCmp(t, "}")) break; if (XCmp(t, ",")) return Err(State, "expecting ',' or '}'"); } t = State.NextW(); if (XCmp(t, ";")) return Err(State, "expecting ';' after enum defn"); LString n = Enum->Name; Enums.Add(n, Enum.Release()); } else { LString m; m.Printf("unexpected token '%S'", t); Err(State, m); return false; } } } return !State.Error; } }; class MapEditor : public LWindow, public LResourceLoad { AppWnd *App; StructureMap *Map; char *Basepath; LList *Lst; LTextView3 *Txt; public: MapEditor(AppWnd *app, StructureMap *map, char *basepath, LList *lst) { App = app; Basepath = basepath; Lst = lst; Map = map; Txt = 0; Name("Map Editor"); if (Attach(0)) { LoadFromResource(IDD_MAP_EDIT, this); Txt = dynamic_cast(FindControl(IDC_TEXT)); if (Txt) { Txt->Sunken(true); OnPosChange(); } LRect r(0, 0, 1000, 900); SetPos(r); MoveSameScreen(App); AttachChildren(); if (Map) { SetCtrlName(IDC_NAME, Map->GetText(0)); SetCtrlName(IDC_TEXT, Map->GetBody()); } Visible(true); } } void OnPosChange() { if (Txt) { LRect c = GetClient(); LRect t = Txt->GetPos(); t.x2 = c.x2 - 7; t.y2 = c.y2 - 7; Txt->SetPos(t); } } int OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_VISUALISER_HELP: { App->Help("Visual.html"); break; } case IDOK: { if (ValidStr(GetCtrlName(IDC_NAME))) { if (Basepath) { char p[300]; LMakePath(p, sizeof(p), Basepath, GetCtrlName(IDC_NAME)); char *Ext = LGetExtension(p); if (!Ext) strcat(p, ".map"); if (Map) { Map->SetFile(p); } else { Map = new StructureMap(App, p); } if (Map && Lst) { Lst->Insert(Map); } } if (Map && Txt) { Map->SetBody(Txt->Name()); Map->Compile(); } } else { LgiMsg(this, "Set the structure name.", AppName); } Quit(); break; } } return 0; } }; class LMapWnd : public LLayout { public: LToolBar *Cmds; LList *Lst; LMapWnd() { SetPourLargest(true); Children.Insert(Cmds = LgiLoadToolbar(this, "MapCmds.gif", 16, 16)); Children.Insert(Lst = new LList(IDC_LIST, 0, 0, 100, 100)); Cmds->AppendButton("New", IDM_NEW, TBT_PUSH); Cmds->AppendButton("Delete", IDM_DELETE, TBT_PUSH); Cmds->AppendButton("Compile", IDM_COMPILE, TBT_PUSH); Cmds->AppendButton("Lock Content", IDM_LOCK, TBT_TOGGLE); Cmds->Raised(false); Lst->SetPourLargest(true); Lst->AddColumn("Structure Maps", 300); } void OnCreate() { AttachChildren(); } bool Pour(LRegion &r) { LLayout::Pour(r); LRegion c(0, 0, X()-1, Y()-1); Cmds->Pour(c); LRect n(0, Cmds->GetPos().y2 + 1, X()-1, Y()-1); Lst->SetPos(n); return true; } void OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } }; ////////////////////////////////////////////////////////////////////////////////// #ifdef MAC LString ResourcesFld() { char Base[MAX_PATH_LEN] = ""; LMakePath(Base, sizeof(Base), LGetExeFile(), "Contents/Resources"); return Base; } LString MapFld() { char Base[MAX_PATH_LEN] = ""; LGetSystemPath(LSP_APP_ROOT, Base, sizeof(Base)); if (!LDirExists(Base)) FileDev->CreateFolder(Base); LMakePath(Base, sizeof(Base), Base, "Maps"); if (!LDirExists(Base)) FileDev->CreateFolder(Base); return Base; } #else LString MapFld() { char Base[MAX_PATH_LEN] = ""; LGetSystemPath(LSP_APP_INSTALL, Base, sizeof(Base)); return Base; } #endif LVisualiseView::LVisualiseView(AppWnd *app, char *DefVisual) { App = app; Value(150); IsVertical(false); Raised(false); SetViewA(Map = new LMapWnd, false); SetViewB(Txt = new LTextView3(80, 0, 0, 100, 100), true); #ifdef MAC auto Res = ResourcesFld(); auto Maps = MapFld(); if (Maps) { LDirectory d; for (auto b = d.First(Res); b; b = d.Next()) { if (d.IsDir()) continue; auto e = LGetExtension(d.GetName()); if (e && !stricmp(e, "map")) { LFile::Path p(Maps); p += d.GetName(); if (!p.Exists()) FileDev->Copy(d.FullPath(), p.GetFull()); } } #else auto Maps = MapFld(); if (Maps) { #endif strcpy_s(Base, sizeof(Base), Maps); LArray Files; LArray Ext; Ext.Add("*.map"); if (LRecursiveFileSearch(Base, &Ext, &Files)) { for (int i=0; iLst->Insert(sm); sm->Select(DefVisual && stristr(f, DefVisual)); } } } } } int LVisualiseView::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_LIST: { if (n.Type == LNotifyItemDoubleClick) { StructureMap *s = dynamic_cast(Map->Lst->GetSelected()); if (s) { new MapEditor(App, s, Base, 0); } } break; } case IDM_NEW: { new MapEditor(App, 0, Base, Map->Lst); break; } case IDM_DELETE: { List Sel; if (Map->Lst && Map->Lst->GetSelection(Sel)) { for (auto i: Sel) { StructureMap *m = dynamic_cast(i); if (m) { FileDev->Delete(m->GetFile()); DeleteObj(m); } } } break; } case IDM_COMPILE: { List Sel; if (Map->Lst && Map->Lst->GetSelection(Sel)) { for (auto i: Sel) { StructureMap *m = dynamic_cast(i); if (m) { if (m->Compile()) { Txt->Name("Compile OK."); } else { auto Err = m->GetErrors(); if (Err) Txt->Name(Err); } } } } break; } } return 0; } void LVisualiseView::Visualise(char *Data, size_t Len, bool Little) { if (!GetCtrlValue(IDM_LOCK)) { StructureMap *m = 0; if (Map->Lst) { m = dynamic_cast(Map->Lst->GetSelected()); } if (m) { if (m->Compiled.Length() == 0) { if (!m->Compile()) { auto e = m->GetErrors(); if (e) Txt->Name(e); return; } } if (m->Compiled.Length()) { LStringPipe p(4 << 10); m->Visualise(Data, Len, p, Little); - auto s = p.NewGStr(); + auto s = p.NewLStr(); if (s) Txt->Name(s); } } } } diff --git a/Code/iHex.cpp b/Code/iHex.cpp --- a/Code/iHex.cpp +++ b/Code/iHex.cpp @@ -1,3439 +1,3439 @@ /*hdr ** FILE: iHex.cpp ** AUTHOR: Matthew Allen ** DATE: 7/5/2002 ** DESCRIPTION: Hex viewer/editor ** ** Copyright (C) 2002, Matthew Allen ** fret@memecode.com */ /* Hex view line format: Hex: [2ch hex][:][8ch hex][2ch space][3*16=48ch hex bytes][2ch space][16ch ascii] Bin: [11ch decimal ][2ch space][3*16=48ch hex bytes][2ch space][16ch ascii] */ #define _WIN32_WINNT 0x0400 #include "iHex.h" #include "lgi/common/Token.h" #include "lgi/common/About.h" #include "lgi/common/Combo.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Combo.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/StatusBar.h" #include "lgi/common/Charset.h" #include "resdefs.h" #include "Diff.h" #include "iHexView.h" #ifdef WIN32 #include "wincrypt.h" #include "resource.h" #endif /////////////////////////////////////////////////////////////////////////////////////////////// // Application identification const char *AppName = "i.Hex"; const char *Untitled = "Untitled Document"; bool CancelSearch = false; #define DEBUG_COVERAGE_CHECK 0 #define ColourSelectionFore LColour(255, 255, 0) #define ColourSelectionBack LColour(0, 0, 255) #define CursorColourBack LColour(192, 192, 192) #define HEX_COLUMN 13 // characters, location of first files hex column #define TEXT_COLUMN (HEX_COLUMN + (3 * BytesPerLine) + GAP_HEX_ASCII) #define GAP_HEX_ASCII 2 // characters, this is the gap between the hex and ascii columns #define GAP_FILES 6 // characters, this is the gap between 2 files when comparing #define FILE_BUFFER_SIZE 1024 #define UI_UPDATE_SPEED 500 // ms LColour ChangedFore(0xf1, 0xe2, 0xad); LColour ChangedBack(0xef, 0xcb, 0x05); LColour DeletedBack(0xc0, 0xc0, 0xc0); /////////////////////////////////////////////////////////////////////////////////////////////// class RandomData : public LStream { #ifdef WIN32 typedef BOOLEAN (APIENTRY *RtlGenRandom)(void*, ULONG); HCRYPTPROV phProv; HMODULE hADVAPI32; RtlGenRandom GenRandom; #endif public: RandomData() { #ifdef WIN32 phProv = 0; hADVAPI32 = 0; GenRandom = 0; if (!CryptAcquireContext(&phProv, 0, 0, PROV_RSA_FULL, 0)) { // f***ing windows... try a different strategy. hADVAPI32 = LoadLibrary("ADVAPI32.DLL"); if (hADVAPI32) { GenRandom = (RtlGenRandom) GetProcAddress(hADVAPI32, "SystemFunction036"); } } #endif } ~RandomData() { #ifdef WIN32 if (phProv) CryptReleaseContext(phProv, 0); else if (hADVAPI32) FreeLibrary(hADVAPI32); #endif } ssize_t Read(void *Ptr, ssize_t Len, int Flags = 0) { #ifdef WIN32 if (phProv) { if (CryptGenRandom(phProv, Len, (uchar*)Ptr)) return Len; } else if (GenRandom) { if (GenRandom(Ptr, Len)) return Len; } #endif return 0; } }; /////////////////////////////////////////////////////////////////////////////////////////////// class ChangeSizeDlg : public LDialog { int OldUnits; public: int64 Size; ChangeSizeDlg(AppWnd *app, int64 size) { SetParent(app); if (LoadFromResource(IDD_CHANGE_FILE_SIZE)) { MoveToCenter(); LCombo *Units = dynamic_cast(FindControl(IDC_UNITS)); if (Units) { Units->Insert("bytes"); Units->Insert("KB"); Units->Insert("MB"); Units->Insert("GB"); if (size < 10 << 10) { SetCtrlValue(IDC_UNITS, 0); } else if (size < 1 << 20) { SetCtrlValue(IDC_UNITS, 1); } else if (size < 1 << 30) { SetCtrlValue(IDC_UNITS, 2); } else { SetCtrlValue(IDC_UNITS, 3); } SetBytes(size); OnNotify(FindControl(IDC_NUMBER), LNotifyValueChanged); } } } void SetBytes(int64 size) { switch (GetCtrlValue(IDC_UNITS)) { case 0: { char s[64]; sprintf(s, LPrintfInt64, size); SetCtrlName(IDC_NUMBER, s); break; } case 1: { char s[64]; double d = (double)size / 1024.0; sprintf(s, "%f", d); SetCtrlName(IDC_NUMBER, s); break; } case 2: { char s[64]; double d = (double)size / 1024.0 / 1024.0; sprintf(s, "%f", d); SetCtrlName(IDC_NUMBER, s); break; } case 3: { char s[64]; double d = (double)size / 1024.0 / 1024.0 / 1024.0; sprintf(s, "%f", d); SetCtrlName(IDC_NUMBER, s); break; } } OldUnits = (int)GetCtrlValue(IDC_UNITS); } int64 GetBytes(int Units = -1) { int64 n = 0; const char *s = GetCtrlName(IDC_NUMBER); if (s) { switch (Units >= 0 ? Units : GetCtrlValue(IDC_UNITS)) { case 0: // bytes { n = atoi64(s); break; } case 1: // KB { n = (int64) (atof(s) * 1024.0); break; } case 2: // MB { n = (int64) (atof(s) * 1024.0 * 1024.0); break; } case 3: // GB { n = (int64) (atof(s) * 1024.0 * 1024.0 * 1024.0); break; } } } return n; } int OnNotify(LViewI *c, LNotification n) { if (!c) return 0; switch (c->GetId()) { case IDC_UNITS: { SetBytes(Size); break; } case IDC_NUMBER: { Size = GetBytes(); char s[64]; sprintf(s, "(" LPrintfInt64 " bytes)", GetBytes()); SetCtrlName(IDC_BYTE_SIZE, s); break; } case IDOK: case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; class IHexBar : public LLayout, public LResourceLoad { friend class AppWnd; AppWnd *App; LHexView *View; int Y; public: bool NotifyOff; IHexBar(AppWnd *a, int y); ~IHexBar(); bool IsHex(); int64 GetOffset(int IsHex = -1, bool *Select = 0); void SetOffset(int64 Offset); bool IsSigned(); bool IsLittleEndian(); bool Pour(LRegion &r); void OnPaint(LSurface *pDC); int OnNotify(LViewI *c, LNotification n); }; ///////////////////////////////////////////////////////////////////////////////////// IHexBar::IHexBar(AppWnd *a, int y) { App = a; View = NULL; Y = y; _IsToolBar = true; NotifyOff = false; Attach(App); LoadFromResource(IDD_HEX, this); for (auto c: Children) { LRect r = c->GetPos(); r.Offset(1, 4); c->SetPos(r); } AttachChildren(); LVariant v; if (!a->GetOptions() || !a->GetOptions()->GetValue("IsHex", v)) v = true; bool HexFmt = v.CastInt32() != 0; SetCtrlValue(IDC_IS_HEX, HexFmt); if (!a->GetOptions() || !a->GetOptions()->GetValue("LittleEndian", v)) v = true; SetCtrlValue(IDC_LITTLE, v.CastInt32()); SetOffset(0); } IHexBar::~IHexBar() { if (App && App->GetOptions()) { LVariant v; App->GetOptions()->SetValue("IsHex", v = GetCtrlValue(IDC_IS_HEX)); App->GetOptions()->SetValue("LittleEndian", v = GetCtrlValue(IDC_LITTLE)); } } bool IHexBar::Pour(LRegion &r) { LRect *Best = FindLargestEdge(r, GV_EDGE_TOP); if (Best) { LRect r = *Best; if (r.Y() != Y) r.y2 = r.y1 + Y - 1; SetPos(r, true); return true; } return false; } void IHexBar::OnPaint(LSurface *pDC) { LRect r = GetClient(); // LThinBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); #define Divider(x) \ pDC->Colour(L_LOW); \ pDC->Line(x, 1, x, pDC->Y()); \ pDC->Colour(L_WHITE); \ pDC->Line(x+1, 1, x+1, pDC->Y()); Divider(134); Divider(268); } bool IHexBar::IsSigned() { return GetCtrlValue(IDC_SIGNED); } bool IHexBar::IsLittleEndian() { return GetCtrlValue(IDC_LITTLE); } bool IHexBar::IsHex() { return GetCtrlValue(IDC_IS_HEX) != 0; } void IHexBar::SetOffset(int64 Offset) { if (IsHex()) { LString s; s.Printf("0x%" PRIx64, Offset); SetCtrlName(IDC_OFFSET, s); } else { SetCtrlValue(IDC_OFFSET, Offset); } } int64 IHexBar::GetOffset(int IsHex, bool *Select) { int64 c = -1; LString s = GetCtrlName(IDC_OFFSET); LString Src; Src.Printf("return %s;", s.Get()); LScriptEngine Eng(this, NULL, NULL); LVariant Ret; LCompiledCode Code; LExecutionStatus Status = Eng.RunTemporary(&Code, Src, &Ret); if (Status != ScriptError) { c = Ret.CastInt64(); } return c; } int IHexBar::OnNotify(LViewI *c, LNotification n) { if (NotifyOff) return 0; switch (c->GetId()) { case IDC_SIGNED: case IDC_LITTLE: { if (View) { View->DoInfo(); View->Focus(true); } break; } case IDC_OFFSET: { if (View && n.Type == LNotifyReturnKey) { // Set the cursor bool Select = false; int64 Off = GetOffset(-1, &Select); View->SetCursor(NULL, Select ? Off - 1 : Off, Select, Select); // Return focus to the view View->Focus(true); } break; } case IDC_IS_HEX: { if (!View) break; bool HexFmt = IsHex(); auto i = GetCtrlName(IDC_OFFSET); if (i) { int64 NewAddr = -1; if (HexFmt) // Dec -> Hex NewAddr = Atoi(i); else // Hex -> Dec NewAddr = htoi(i); if (NewAddr >= 0) SetOffset(NewAddr); } // Tell the hex view View->SetIsHex(HexFmt); // Return focus to the view View->Focus(true); break; } case IDC_BIT7: { if (View) View->SetBit(0x80, c->Value()); break; } case IDC_BIT6: { if (View) View->SetBit(0x40, c->Value()); break; } case IDC_BIT5: { if (View) View->SetBit(0x20, c->Value()); break; } case IDC_BIT4: { if (View) View->SetBit(0x10, c->Value()); break; } case IDC_BIT3: { if (View) View->SetBit(0x08, c->Value()); break; } case IDC_BIT2: { if (View) View->SetBit(0x04, c->Value()); break; } case IDC_BIT1: { if (View) View->SetBit(0x02, c->Value()); break; } case IDC_BIT0: { if (View) View->SetBit(0x01, c->Value()); break; } case IDC_DEC_1: { if (View && n.Type == LNotifyReturnKey) View->SetByte(atoi(c->Name())); break; } case IDC_HEX_1: { if (View && n.Type == LNotifyReturnKey) View->SetByte(htoi(c->Name())); break; } case IDC_DEC_2: { if (View && n.Type == LNotifyReturnKey) View->SetShort(atoi(c->Name())); break; } case IDC_HEX_2: { if (View && n.Type == LNotifyReturnKey) View->SetShort(htoi(c->Name())); break; } case IDC_DEC_4: { if (View && n.Type == LNotifyReturnKey) View->SetInt(atoi(c->Name())); break; } case IDC_HEX_4: { if (View && n.Type == LNotifyReturnKey) View->SetInt(htoi(c->Name())); break; } } return 0; } ////////////////////////////////////////////////////////////////////////////////////// bool LHexBuffer::Save() { if (!File || !File->IsOpen()) { LgiTrace("%s:%i - No open file.\n", _FL); return false; } if (File->SetPos(BufPos) != BufPos) { LgiTrace("%s:%i - Failed to set pos: " LPrintfInt64 ".\n", _FL, BufPos); return false; } auto Wr = File->Write(Buf, BufUsed); if (Wr != BufUsed) { LgiTrace("%s:%i - Failed to write %i bytes: %i.\n", _FL, BufUsed, Wr); return false; } IsDirty = false; return true; } void LHexBuffer::SetDirty(bool Dirty) { if (IsDirty ^ Dirty) { IsDirty = Dirty; if (Dirty) { View->App->SetDirty(true); } } } bool LHexBuffer::GetData(int64 Start, size_t Len) { static bool IsAsking = false; bool Status = false; // is the range outside the buffer's bounds? if (Start < 0 || Start + Len > Size) { return false; } if (!IsAsking) // && File && File->IsOpen() { // is the buffer allocated if (!Buf) { BufLen = FILE_BUFFER_SIZE << 10; BufPos = 1; Buf = new uchar[BufLen]; LAssert(Buf); } // is the cursor outside the buffer? if (Start < BufPos || (Start + Len) > (BufPos + BufLen)) { // clear changes IsAsking = true; bool IsClean = View->App->SetDirty(false); IsAsking = false; if (IsClean) { // move buffer to cover cursor pos auto Half = BufLen >> 1; BufPos = Start - (Half ? (Start % Half) : 0); if (File) { if (File->Seek(BufPos, SEEK_SET) == BufPos) { memset(Buf, 0xcc, BufLen); BufUsed = File->Read(Buf, BufLen); Status = (Start >= BufPos) && ((Start + Len) < (BufPos + BufUsed)); } else { BufUsed = 0; } } else { // In memory doc? Status = true; } } } else { Status = (Start >= BufPos) && (Start + Len <= BufPos + BufUsed); } } return Status; } bool LHexBuffer::GetLocationOfByte(LArray &Loc, int64 Offset, const char16 *LineBuf) { if (Offset < 0) return false; int64 X = Offset % View->BytesPerLine; int64 Y = Offset / View->BytesPerLine; int64 YPos = View->VScroll ? View->VScroll->Value() : 0; int64 YPx = (Y - YPos) * View->CharSize.y; int64 YOff = Y - YPos; LAutoWString w; int HexLen = (int)X * 3; int AsciiLen = (int)((View->BytesPerLine * 3) + GAP_HEX_ASCII + X); if (!LineBuf) { if (!Content.IdxCheck(YOff) || !Content[YOff]) { return false; } if (!w.Reset(Utf8ToWide(Content[YOff]))) { LAssert(!"Conversion failed"); return false; } LineBuf = w.Get(); } { LRect &rcHex = Loc.New(); LDisplayString ds(View->Font, LineBuf, (int)HexLen); int x1 = ds.X(); LDisplayString ds2(View->Font, LineBuf, (int)HexLen+2); int x2 = ds2.X(); rcHex.ZOff(x2 - x1 - 1, View->CharSize.y-1); rcHex.Offset(x1 + Pos.x1, (int) (YPx + Pos.y1)); } { LRect &rcAscii = Loc.New(); LDisplayString ds(View->Font, LineBuf, (int)AsciiLen); int x1 = ds.X(); LDisplayString ds2(View->Font, LineBuf, (int)AsciiLen+1); int x2 = ds2.X(); rcAscii.ZOff(x2 - x1 - 1, View->CharSize.y-1); rcAscii.Offset(x1 + Pos.x1, (int) (YPx + Pos.y1)); } return true; } enum ColourFlags { ForeCol = 0, BackCol = 1, SelectedCol = 2, ChangedCol = 4, CursorCol = 8, }; #define Int2Hex(c) ( (c) < 10 ? '0' + (c) : (c) - 10 + 'A' ) void LHexBuffer::OnPaint(LSurface *pDC, int64 Start, int64 Len, LHexBuffer *Compare) { // First position the layout int64 BufOff = Start - BufPos; int64 Bytes = MIN(Len, BufUsed - BufOff); int Lines = (int)((Bytes + View->BytesPerLine - 1) / View->BytesPerLine); // Colour setup bool SelectedBuf = View->Cursor.Buf == this; LColour WkSp = L_WORKSPACE; float Mix = 0.85f; LColour Colours[16]; // memset(&Colours, 0xaa, sizeof(Colours)); Colours[ForeCol] = LColour(L_TEXT); Colours[BackCol] = LColour(L_WORKSPACE); Colours[ForeCol | SelectedCol] = SelectedBuf ? ColourSelectionFore : LColour(L_TEXT); Colours[BackCol | SelectedCol] = SelectedBuf ? ColourSelectionBack : LColour(ColourSelectionBack).Mix(WkSp, Mix); Colours[ForeCol | ChangedCol] = LColour(L_TEXT); Colours[BackCol | ChangedCol].Rgb(239, 203, 5); Colours[ForeCol | ChangedCol | SelectedCol] = Colours[ForeCol | SelectedCol].Mix(Colours[ForeCol | ChangedCol]); Colours[BackCol | ChangedCol | SelectedCol] = Colours[BackCol | SelectedCol].Mix(Colours[BackCol | ChangedCol]); Colours[ForeCol | CursorCol] = LColour(L_TEXT); Colours[BackCol | CursorCol].Rgb(192, 192, 192); for (int i = 10; i < 16; i++) { LColour a = Colours[i - 8], b; if (a.GetGray() > 0x80) b = LColour::Black.Mix(a, 0.75); else b = LColour::White.Mix(a, 0.5); Colours[i | CursorCol] = b; } #if 0 static bool First = true; if (First) { First = false; for (int i=0; i 
\n", i, i & BackCol ? "Back" : "Fore", i & SelectedCol ? "Selected" : "Unselected", i & ChangedCol ? "Changed" : "Unchanged", i & CursorCol ? "Cursor" : "NonCursor", R24(Colours[i]), G24(Colours[i]), B24(Colours[i])); } } #endif // Now draw the layout data char s[256] = {0}; uint8_t ForeFlags[256]; uint8_t BackFlags[256]; int EndY = Pos.y1 + (Lines * View->CharSize.y); EndY = MAX(EndY, Pos.y1); Content.Length(0); for (int Line=0; LineCharSize.y); int Ch = 0; // This is relative to the start of the buffer. int64 LineStart = BufOff + (Line * View->BytesPerLine); // Absolute file position int64 AbsPos = BufPos + LineStart; // Setup comparison stuff uint8_t *CompareBuf = NULL; int CompareLen = 0; if (Compare && Compare->GetData(AbsPos, View->BytesPerLine)) { CompareBuf = Compare->Buf + (AbsPos - Compare->BufPos); CompareLen = View->BytesPerLine; } else { CompareBuf = NULL; CompareLen = 0; } // Clear the colours for this line memset(&ForeFlags, ForeCol, sizeof(ForeFlags)); memset(&BackFlags, BackCol, sizeof(BackFlags)); // Print the hex bytes to the line int64 n; int64 FromStart = BufOff + (Line * View->BytesPerLine); int64 From = FromStart, To = FromStart + View->BytesPerLine; for (n=From; n> 4); s[Ch++] = Int2Hex(Buf[n] & 0xf); } else { s[Ch++] = ' '; s[Ch++] = ' '; } s[Ch++] = ' '; } // Separator between hex/ascii Ch += sprintf_s(s + Ch, sizeof(s) - Ch, " "); // Print the ascii characters to the line char *p = s + Ch; int StartOfAscii = Ch; for (n=From; n= ' ' && c < 0x7f) ? c : '.'; } else { *p++ = ' '; } } *p++ = 0; Content[Line] = s; int64 CursorOff = -1; if (View->Cursor.Buf == this) { if (View->Cursor.Offset >= BufPos && View->Cursor.Offset < BufPos + BufUsed) { CursorOff = View->Cursor.Offset - BufPos; if ((CursorOff >= From) && (CursorOff < To)) CursorOff -= From; else CursorOff = -1; } } // Draw text LFont *Font = View->Font; Font->Colour(L_TEXT, L_WORKSPACE); char16 *Wide = (char16*)LNewConvertCp(LGI_WideCharset, s, "iso-8859-1"); if (Wide) { // Paint the selection into the colour buffers int64 DocPos = BufPos + LineStart; int64 Min = View->HasSelection() ? MIN(View->Selection.Offset, View->Cursor.Offset) : -1; int64 Max = View->HasSelection() ? MAX(View->Selection.Offset, View->Cursor.Offset) : -1; if (Min < DocPos + View->BytesPerLine && Max >= DocPos) { // Part or all of this line is selected int64 s = ((View->Selection.Offset - DocPos) * 3) + View->Selection.Nibble; int64 e = ((View->Cursor.Offset - DocPos) * 3) + View->Cursor.Nibble; if (s > e) { int64 i = s; s = e; e = i; } if (s < 0) s = 0; if (e > View->BytesPerLine * 3 - 2) e = View->BytesPerLine * 3 - 2; for (int64 i=s; i<=e; i++) { ForeFlags[i] |= SelectedCol; BackFlags[i] |= SelectedCol; } for (int64 i=(s/3)+StartOfAscii; i<=(e/3)+StartOfAscii; i++) { ForeFlags[i] |= SelectedCol; BackFlags[i] |= SelectedCol; } } // Colour the back of the cursor gray... if (CursorOff >= 0 && /*View->Selection.Index < 0 && */View->Cursor.Flash) { BackFlags[(CursorOff * 3) + View->Cursor.Nibble] |= CursorCol; BackFlags[StartOfAscii + CursorOff] |= CursorCol; } // Go through the colour buffers, painting in runs of similar colour LRect r; int CxF = Pos.x1 << LDisplayString::FShift; auto Len = p - s; for (int i=0; iColour(Colours[ForeFlags[i]], Colours[BackFlags[i]]); Str.FDraw(pDC, CxF, CurY<> LDisplayString::FShift; if (Cx < Pos.x2) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(Cx, CurY, Pos.x2, CurY+View->CharSize.y); } DeleteArray(Wide); } if (CursorOff >= 0) { // Draw cursor GetLocationOfByte(View->Cursor.Pos, View->Cursor.Offset, Wide); pDC->Colour(View->Focus() ? L_TEXT : L_LOW); for (unsigned i=0; iCursor.Pos.Length(); i++) { LRect r = View->Cursor.Pos[i]; r.y1 = r.y2; if (i == 0) { // Hex side.. if (View->Cursor.Nibble) r.x1 += View->CharSize.x; else r.x2 -= View->CharSize.x; if (View->Cursor.Pane == HexPane) r.y1--; } else if (View->Cursor.Pane == AsciiPane) { r.y1--; } pDC->Rectangle(&r); } } } if (EndY < Pos.y2) { LRect r(Pos.x1, EndY, Pos.x2, Pos.y2); pDC->Colour(L_WORKSPACE); pDC->Rectangle(&r); } } ////////////////////////////////////////////////////////////////////////////////////// LHexView::LHexView(AppWnd *app, IHexBar *bar) { // Init App = app; Bar = bar; Font = 0; CharSize.x = 8; CharSize.y = 16; IsHex = true; BytesPerLine = 16; IntWidth = 1; SetId(IDC_HEX_VIEW); // Font LFontType Type; if (Type.GetSystemFont("Fixed")) { Font = Type.Create(); if (Font) { LDisplayString ds(Font, "A"); CharSize.x = ds.X(); CharSize.y = ds.Y(); } } else LAssert(0); Attach(App); Name("LHexView"); SetScrollBars(false, true); if (VScroll) { VScroll->SetNotify(this); } } LHexView::~LHexView() { DeleteObj(Font); Empty(); } bool LHexView::Empty() { Buf.DeleteObjects(); Cursor.Empty(); Selection.Empty(); return true; } int LHexView::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_VSCROLL: { Invalidate(); break; } } return 0; } int64 LHexView::GetFileSize() { if (Buf.Length() && Buf[0]) return Buf[0]->Size; return -1; } bool LHexView::SetFileSize(int64 size) { // Save any changes if (App->SetDirty(false) && Buf.Length() && Cursor.Buf) { Cursor.Buf->SetSize((size_t)size); auto p = Cursor.Buf->BufPos; Cursor.Buf->BufPos++; Cursor.Buf->GetData(p, 1); UpdateScrollBar(); Invalidate(); } return false; } void LHexView::SetIsHex(bool i) { if (i != IsHex) { IsHex = i; Invalidate(); } } void LHexView::Copy(FormatType Fmt) { if (Buf.Length() == 0 || !Cursor.Buf) { LgiMsg(this, "Error: No buffer to copy.", AppName); return; } LClipBoard c(this); LHexBuffer *b = Cursor.Buf; int64 Min, Max; if (HasSelection()) { Min = MIN(Selection.Offset, Cursor.Offset); Max = MAX(Selection.Offset, Cursor.Offset); } else { Min = 0; Max = b->Size - 1; } int64 Len = Max - Min + 1; if (!b->GetData(Min, (size_t)Len)) { LgiMsg(this, "Error: Failed to get source buffer.", AppName); return; } uint64 Offset = Min - b->BufPos; uchar *Ptr = b->Buf + Offset; if (Len > b->BufUsed - Offset) { Len = b->BufUsed - Offset; } LStringPipe p; if (Fmt == FmtCode) p.Print("unsigned char Var[] = {\n\t"); int64 i; for (i=0; i Ptr; ssize_t Len = 0; char *Txt; #ifdef WIN32 if (c.Binary(CF_PRIVATEFIRST, Ptr, &Len)) { } else #endif { Txt = c.Text(); if (!Txt) return; if (Fmt == FmtHex) { // Convert from binary... LArray Out; int High = -1; bool HasComma = false; for (char *i = Txt; *i; i++) { if (*i == ',') { HasComma = true; break; } } if (HasComma) { // Comma separated integers? for (char *i = Txt; *i; ) { while (*i && !IsDigit(*i)) i++; char *e = i; while (*e && IsDigit(*e)) e++; Out.Add(Atoi(i)); i = e; } } else { // Hex data? for (char *i = Txt; *i; i++) { int n = -1; if (*i >= '0' && *i <= '9') n = *i - '0'; else if (*i >= 'a' && *i <= 'f') n = *i - 'a' + 10; else if (*i >= 'A' && *i <= 'F') n = *i - 'A' + 10; if (n >= 0) { if (High >= 0) { Out.Add(High << 4 | n); High = -1; } else { High = n; } } } } if (Out.Length()) { Len = Out.Length(); Ptr.Reset(Out.Release()); } } else { Len = strlen(Txt); Ptr.Reset((uint8_t*)NewStr(Txt)); } } if (Ptr && Len > 0) { Cursor.Offset = MAX(0, Cursor.Offset); if (Buf.Length() == 0 || !Buf[0]->GetData(Cursor.Offset, Len)) { if (!CreateFile(Len)) return; if (!Buf[0]->GetData(0, Len)) return; } LHexBuffer *b = Buf[0]; if (b) { memcpy(b->Buf + Cursor.Offset - b->BufPos, Ptr, Len); b->SetDirty(); App->SetDirty(true); Invalidate(); DoInfo(); } } } void LHexView::UpdateScrollBar() { int Lines = GetClient().Y() / CharSize.y; int64 DocLines = 0; for (unsigned i=0; iSize + 15) / 16; DocLines = MAX(DocLines, BufLines); } SetScrollBars(false, DocLines > Lines); if (VScroll) { VScroll->SetRange(DocLines > 0 ? DocLines : 0); VScroll->SetPage(Lines); } } void LHexView::SwapBytes(void *p, int Len) { if (Bar && !Bar->IsLittleEndian()) { uchar *c = (uchar*)p; for (int i=0; i>1; i++) { uchar t = c[i]; c[i] = c[Len-1-i]; c[Len-1-i] = t; } } } bool LHexView::GetDataAtCursor(char *&Data, size_t &Len) { LHexBuffer *b = Buf.Length() ? Buf.First() : NULL; if (b && b->Buf) { size_t Offset = (size_t)(Cursor.Offset - b->BufPos); Data = (char*)b->Buf + Offset; Len = MIN(b->BufUsed, b->BufLen) - Offset; return true; } return false; } bool LHexView::HasSelection() { return Selection.Offset >= 0; } int64 LHexView::GetSelectedNibbles() { if (!HasSelection()) return 0; auto c = (Cursor.Offset << 1) + Cursor.Nibble; auto s = (Selection.Offset << 1) + Selection.Nibble; int64 Min, Max; if (s < c) { Min = s; Max = c; } else { Max = s; Min = c; } return Max - Min + 1; } void LHexView::InvalidateLines(LArray &NewLoc, LArray &OldLoc) { if (NewLoc.Length() > 0 && OldLoc.Length() > 0) { // Work out the union of NewLoc and OldLoc int MinY = MIN(NewLoc[0].y1, OldLoc[0].y1); int MaxY = MAX(NewLoc[0].y2, OldLoc[0].y2); LRect u(0, MinY, X()-1, MaxY); Invalidate(&u); } else { Invalidate(); } } LHexBuffer *LHexView::GetCursorBuffer() { return Cursor.Buf; } void LHexView::SetCursor(LHexBuffer *b, int64 cursor, int nibble, bool Selecting) { LArray OldLoc, NewLoc; bool SelectionChanging = false; bool SelectionEnding = false; if (!b) b = Cursor.Buf; if (!b) return; if (Selecting) { if (!HasSelection()) // Start selection Selection = Cursor; SelectionChanging = true; } else { if (HasSelection()) { // Deselecting // Repaint the entire selection area... b->GetLocationOfByte(NewLoc, Cursor.Offset, NULL); b->GetLocationOfByte(OldLoc, Selection.Offset, NULL); InvalidateLines(NewLoc, OldLoc); SelectionEnding = true; Selection.Offset = -1; } } if (!SelectionEnding) b->GetLocationOfByte(OldLoc, Cursor.Offset, NULL); // else the selection just ended and the old cursor location just got repainted anyway // Limit to doc if (cursor >= b->Size) { cursor = b->Size - 1; nibble = 1; } if (cursor < 0) { cursor = 0; nibble = 0; } // Is different? if (Cursor.Buf != b || Cursor.Offset != cursor || Cursor.Nibble != nibble) { // Set the cursor Cursor.Buf = b; Cursor.Offset = cursor; Cursor.Nibble = nibble; // Make sure the cursor is in the viewable area? if (VScroll) { int64 Start = (uint64) (VScroll ? VScroll->Value() : 0) * 16; int Lines = GetClient().Y() / CharSize.y; int64 End = MIN(b->Size, Start + (Lines * 16)); if (Cursor.Offset < Start) { // Scroll up VScroll->Value((Cursor.Offset - (Cursor.Offset%16)) / 16); Invalidate(); } else if (Cursor.Offset >= End) { // Scroll down int64 NewVal = (Cursor.Offset - (Cursor.Offset%16) - ((Lines-1) * 16)) / 16; VScroll->Value(NewVal); Invalidate(); } } if (Bar) { Bar->SetOffset(Cursor.Offset); DoInfo(); } Cursor.Flash = true; if (b->GetLocationOfByte(NewLoc, Cursor.Offset, NULL)) { if (!SelectionChanging) { // Just update the cursor's old and new locations NewLoc.Add(OldLoc); // DbgRect = NewLoc; for (unsigned i=0; iGetLocationOfByte(NewLoc, Cursor.Offset, NULL); InvalidateLines(NewLoc, OldLoc); } SendNotify(LNotifyCursorChanged); } int64 LHexView::Search(SearchDlg *For, uchar *Bytes, size_t Len) { int64 Hit = -1; if (For->Bin && For->Length > 0) { for (int i=0; iLength; i++) { bool Match = true; for (int n=0; nLength; n++) { if (For->MatchCase || For->ForHex) { if (For->Bin[n] != Bytes[i+n]) { Match = false; break; } } else { if (tolower(For->Bin[n]) != tolower(Bytes[i+n])) { Match = false; break; } } } if (Match) { Hit = i; break; } } } return Hit; } void LHexView::DoSearch(SearchDlg *For) { size_t Block = 32 << 10; int64 Hit = -1, c; int64 Time = LCurrentTime(); LProgressDlg *Prog = 0; LHexBuffer *b = Cursor.Buf; if (!b) return; // Search through to the end of the file... for (c = Cursor.Offset + 1; c < b->Size; c += Block) { size_t Actual = (size_t)MIN(Block, GetFileSize() - c); if (b->GetData(c, Actual)) { Hit = Search(For, b->Buf + (c - b->BufPos), Actual); if (Hit >= 0) { Hit += c; break; } int64 Now = LCurrentTime(); if (Now - Time > UI_UPDATE_SPEED) { Time = Now; if (!Prog) { if ((Prog = new LProgressDlg(this))) { Prog->SetDescription("Searching..."); Prog->SetRange(GetFileSize()); Prog->SetScale(1.0 / 1024.0); Prog->SetType("kb"); } } else { Prog->Value(c - Cursor.Offset); // LYield(); } } } else break; } if (Hit < 0) { // Now search from the start of the file to the original cursor for (c = 0; c < Cursor.Offset; c += Block) { if (b->GetData(c, Block)) { size_t Actual = (size_t)MIN(Block, Cursor.Offset - c); Hit = Search(For, b->Buf + (c - b->BufPos), Actual); if (Hit >= 0) { Hit += c; break; } int64 Now = LCurrentTime(); if (Now - Time > UI_UPDATE_SPEED) { Time = Now; if (!Prog) { if ((Prog = new LProgressDlg(this))) { Prog->SetDescription("Searching..."); Prog->SetRange(GetFileSize()); Prog->SetScale(1.0 / 1024.0); Prog->SetType("kb"); } } else { Prog->Value(b->Size - Cursor.Offset + c); // LYield(); } } } else break; } } if (Hit >= 0) { SetCursor(b, Hit); SetCursor(b, Hit + For->Length - 1, 1, true); } DeleteObj(Prog); } void LHexView::SetBit(uint8_t Bit, bool On) { LHexBuffer *b = Cursor.Buf; if (!b) return; if (b->GetData(Cursor.Offset, 1)) { if (On) { b->Buf[Cursor.Offset - b->BufPos] |= Bit; } else { b->Buf[Cursor.Offset - b->BufPos] &= ~Bit; } App->SetDirty(true); Invalidate(); DoInfo(); } } void LHexView::SetByte(uint8_t Byte) { LHexBuffer *b = Cursor.Buf; if (!b) return; if (b->GetData(Cursor.Offset, 1)) { if (b->Buf[Cursor.Offset - b->BufPos] != Byte) { b->Buf[Cursor.Offset - b->BufPos] = Byte; App->SetDirty(true); Invalidate(); DoInfo(); } } } void LHexView::SetShort(uint16 Short) { LHexBuffer *b = Cursor.Buf; if (!b) return; if (b->GetData(Cursor.Offset, 2)) { SwapBytes(&Short, sizeof(Short)); uint16 *p = (uint16*) (&b->Buf[Cursor.Offset - b->BufPos]); if (*p != Short) { *p = Short; App->SetDirty(true); Invalidate(); DoInfo(); } } } void LHexView::SetInt(uint32_t Int) { LHexBuffer *b = Cursor.Buf; if (!b) return; if (b->GetData(Cursor.Offset, 4)) { SwapBytes(&Int, sizeof(Int)); uint32_t *p = (uint32_t*) (&b->Buf[Cursor.Offset - b->BufPos]); if (*p != Int) { *p = Int; App->SetDirty(true); Invalidate(); DoInfo(); } } } void LHexView::DoInfo() { LHexBuffer *b = Cursor.Buf; if (Bar && b) { bool IsSigned = Bar->IsSigned(); LView *w = GetWindow(); char s[256] = ""; if (b->GetData(Cursor.Offset, 1)) { int c = b->Buf[Cursor.Offset - b->BufPos], sc; if (IsSigned) sc = (char)b->Buf[Cursor.Offset - b->BufPos]; else sc = b->Buf[Cursor.Offset - b->BufPos]; Bar->NotifyOff = true; sprintf(s, "%i", sc); w->SetCtrlName(IDC_DEC_1, s); sprintf(s, "%02.2X", c); w->SetCtrlName(IDC_HEX_1, s); sprintf(s, "%c", c >= ' ' && c <= 0x7f ? c : '.'); w->SetCtrlName(IDC_ASC_1, s); uint8_t Bits = b->Buf[Cursor.Offset - b->BufPos]; Bar->SetCtrlValue(IDC_BIT7, (Bits & 0x80) != 0); Bar->SetCtrlValue(IDC_BIT6, (Bits & 0x40) != 0); Bar->SetCtrlValue(IDC_BIT5, (Bits & 0x20) != 0); Bar->SetCtrlValue(IDC_BIT4, (Bits & 0x10) != 0); Bar->SetCtrlValue(IDC_BIT3, (Bits & 0x08) != 0); Bar->SetCtrlValue(IDC_BIT2, (Bits & 0x04) != 0); Bar->SetCtrlValue(IDC_BIT1, (Bits & 0x02) != 0); Bar->SetCtrlValue(IDC_BIT0, (Bits & 0x01) != 0); Bar->NotifyOff = false; } LViewI *Hex, *Dec; if (w->GetViewById(IDC_HEX_2, Hex) && w->GetViewById(IDC_DEC_2, Dec)) { bool Valid = b->GetData(Cursor.Offset, 2); LString sHex, sDec; if (Valid) { uint16 *sp = (uint16*)(b->Buf+(Cursor.Offset - b->BufPos)); uint16 c = *sp; SwapBytes(&c, sizeof(c)); int c2 = (int16)c; sDec.Printf("%i", IsSigned ? c2 : c); sHex.Printf("%04.4X", c); } Dec->Name(Valid ? sDec : NULL); Hex->Name(Valid ? sHex : NULL); Dec->Enabled(Valid); Hex->Enabled(Valid); } if (w->GetViewById(IDC_HEX_4, Hex) && w->GetViewById(IDC_DEC_4, Dec)) { bool Valid = b->GetData(Cursor.Offset, 4); LString sHex, sDec; if (Valid) { uint32_t *lp = (uint32_t*)(b->Buf + (Cursor.Offset - b->BufPos)); uint32_t c = *lp; SwapBytes(&c, sizeof(c)); sDec.Printf(IsSigned ? "%i" : "%u", c); sHex.Printf("%08.8X", c); } Dec->Name(Valid ? sDec : NULL); Hex->Name(Valid ? sHex : NULL); Dec->Enabled(Valid); Hex->Enabled(Valid); } } } bool FileToArray(LArray &a, const char *File) { LFile f; if (!f.Open(File, O_READ)) return false; if (!a.Length((size_t)f.GetSize())) return false; auto rd = f.Read(&a[0], a.Length()); if (rd != a.Length()) return false; return true; } void LHexView::CompareFile(const char *CmpFile) { if (LFileExists(CmpFile)) { LAutoPtr b(new LHexBuffer(this)); if (b) { if (b->Open(CmpFile, false)) { Buf.Add(b.Release()); UpdateScrollBar(); Invalidate(); } } } } bool LHexView::CreateFile(int64 Len) { if (!App->SetDirty(false)) return false; LHexBuffer *b = new LHexBuffer(this); if (!b) return false; Buf.Add(b); b->Buf = new uchar[b->BufLen = (size_t)Len]; if (!b->Buf) { Buf.DeleteObjects(); return false; } memset(b->Buf, 0, (size_t)Len); b->BufUsed = (size_t)Len; b->Size = Len; Focus(true); SetCursor(b, 0); UpdateScrollBar(); if (Bar) Bar->SetOffset(0); Invalidate(); DoInfo(); App->Name(AppName); App->OnDocument(true); return true; } bool LHexView::OpenFile(const char *FileName, bool ReadOnly) { bool Status = false; if (App->SetDirty(false)) { Empty(); LAutoPtr b(new LHexBuffer(this)); if (b && FileName) { if (b->Open(FileName, ReadOnly)) { Focus(true); SetCursor(b, 0); Buf[0] = b.Release(); Status = true; } else { LgiMsg(this, "Couldn't open '%s' for reading.", AppName, MB_OK, FileName); } } if (Bar) { Bar->SetOffset(0); } Invalidate(); DoInfo(); char Title[MAX_PATH_LEN + 100]; sprintf_s(Title, sizeof(Title), "%s [%s]", AppName, FileName); App->Name(Title); UpdateScrollBar(); } return Status; } bool LHexView::CloseFile(ssize_t Index) { if (Index < 0) Index = Buf.Length() - 1; if (!Buf.AddressOf((unsigned) Index)) return false; delete Buf[Index]; Buf.DeleteAt(Index, true); Cursor.Empty(); Selection.Empty(); Invalidate(); return true; } bool LHexView::IsDirty() { for (auto b : Buf) { if (b->IsDirty) return true; } return false; } enum Msgs { M_PROCESS_SAVE = M_USER, }; struct SaveState : public LView::ViewEventTarget { bool status = true; LHexView *view = NULL; LArray work; std::function finished; SaveState(LHexView *v) : LView::ViewEventTarget(v, M_PROCESS_SAVE) { view = v; } ~SaveState() { LAssert(work.Length() == 0); } void Start() { PostEvent(M_PROCESS_SAVE); } void SaveBuffer(LHexBuffer *b) { b->Save(); b->SetDirty(false); } LMessage::Result OnEvent(LMessage *Msg) { if (Msg->Msg() != M_PROCESS_SAVE) return 0; if (work.Length() == 0) { finished(status); delete this; return 0; } auto b = work[0]; work.DeleteAt(0); if (b->IsDirty || !b->File) { if (!b->File) { auto s = new LFileSelect; s->Parent(view); s->Save([this, b](auto s, auto code) { if (code) { b->File = new LFile; if (b->File && !b->File->Open(s->Name(), O_READWRITE)) DeleteObj(b->File); view->GetApp()->SetCurFile(s->Name()); SaveBuffer(b); } else { status = false; } delete s; }); } SaveBuffer(b); } return 0; } }; void LHexView::Save(std::function callback) { // Save all buffers auto ss = new SaveState(this); ss->work = Buf; ss->finished = callback; ss->Start(); } bool LHexView::SaveFile(LHexBuffer *b, const char *FileName) { bool Status = false; if (!b) b = Cursor.Buf; if (b && b->File && FileName) { if (stricmp(FileName, b->File->GetName()) == 0) { if (b->File->Seek(b->BufPos, SEEK_SET) == b->BufPos) { size_t Len = (size_t)MIN(b->BufLen, b->Size - b->BufPos); Status = b->File->Write(b->Buf, Len) == Len; } } } return Status; } bool LHexView::HasFile() { if (Buf.Length() && Buf[0]) return Buf.First()->File != NULL; return false; } void LHexView::SaveSelection(LHexBuffer *b, const char *FileName) { if (!b) b = Cursor.Buf; if (b && b->HasData() && FileName) { LFile f; if (HasSelection() && f.Open(FileName, O_WRITE)) { int64 Min = MIN(Selection.Offset, Cursor.Offset); int64 Max = MAX(Selection.Offset, Cursor.Offset); int64 Len = Max - Min + 1; f.SetSize(Len); f.SetPos(0); int64 Block = 4 << 10; for (int64 i=0; iGetData(AbsPos, Bytes)) { uchar *p = b->Buf + (AbsPos - b->BufPos); f.Write(p, Bytes); } } } } } void LHexView::SelectAll() { LHexBuffer *b = Cursor.Buf; if (b) { SetCursor(b, 0, 0, false); SetCursor(b, b->Size-1, 1, true); } } void LHexView::SelectionFillRandom(LStream *Rnd) { if (!Rnd || !Cursor.Buf) return; LHexBuffer *b = Cursor.Buf; int64 Min = MIN(Selection.Offset, Cursor.Offset); int64 Max = MAX(Selection.Offset, Cursor.Offset); int64 Len = Max - Min + 1; if (b->File) { int64 Last = LCurrentTime(); int64 Start = Last; LProgressDlg Dlg(App); LArray Buf; Buf.Length(2 << 20); Dlg.SetRange(Len); Dlg.SetScale(1.0 / 1024.0 / 1024.0); Dlg.SetType("MB"); b->File->SetPos(Min); #if 1 if (Rnd->Read(&Buf[0], Buf.Length()) != Buf.Length()) { LgiMsg(this, "Random stream failed.", AppName); return; } #endif for (int64 i=0; !Dlg.IsCancelled() && iRead(&Buf[0], Remain) != Remain) { LgiMsg(this, "Random stream failed.", AppName); return; } #endif ssize_t w = b->File->Write(&Buf[0], (size_t)Remain); if (w != Remain) { LgiMsg(this, "Write file failed.", AppName); break; } int64 Now = LCurrentTime(); if (Now - Last > 500) { Dlg.Value(i); // LYield(); Last = Now; double Sec = (double)(int64)(Now - Start) / 1000.0; double Rate = (double)(int64)(i + Remain) / Sec; int TotalSeconds = (int) ((Len - i - Remain) / Rate); char s[64]; sprintf(s, "%i:%02.2i:%02.2i remaining", TotalSeconds/3600, (TotalSeconds%3600)/60, TotalSeconds%60); Dlg.SetDescription(s); } } if (b->File->SetPos(b->BufPos) == b->BufPos) { b->BufUsed = b->File->Read(b->Buf, b->BufLen); } Invalidate(); } } bool LHexView::Pour(LRegion &r) { LRect *Best = FindLargest(r); if (Best) { SetPos(*Best, true); return true; } return false; } void LHexView::OnPosChange() { UpdateScrollBar(); LLayout::OnPosChange(); } void LHexView::InvalidateCursor() { for (int i=0; i= 500) { Cursor.Flash = !Cursor.Flash; Cursor.FlashTs = Now; InvalidateCursor(); } if (IsCapturing()) { LMouse m; if (GetMouse(m)) { LRect c = GetClient(); if (!c.Overlap(m.x, m.y)) { OnMouseMove(m); } } } } void LHexView::OnPaint(LSurface *pDC) { LRect Cli = GetClient(); LRect r = Cli; #if DEBUG_COVERAGE_CHECK pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif LRegion TopMargin; if (Buf.Length() > 1) { r.y1 += (int) (LSysBold->GetHeight() * 1.5); TopMargin = LRect(0, 0, r.x2, r.y1-1); } int64 YPos = VScroll ? VScroll->Value() : 0; int64 Start = YPos * BytesPerLine; int Columns = (3 * BytesPerLine) + GAP_HEX_ASCII + (BytesPerLine); int Lines = (r.Y() + CharSize.y -1) / CharSize.y; Cursor.Pos.Length(0); int64 MaxSize = 0; for (unsigned int BufIdx = 0; BufIdx < Buf.Length(); BufIdx++) MaxSize = MAX(MaxSize, Buf[BufIdx]->Size); int64 AddrLines = (MaxSize + BytesPerLine - 1) / BytesPerLine; int64 Addrs = AddrLines - YPos; // Draw all the addresses Font->Transparent(false); Font->Colour(L_TEXT, L_WORKSPACE); int CurrentY = 0; int CurrentX = 0; for (int Line=0; Line r.y2) break; int64 LineAddr = Start + (Line * BytesPerLine); LString p; if (IsHex) p.Printf("%02.2x:%08.8X ", (uint)(LineAddr >> 32), (uint)LineAddr); else #ifdef WIN32 p.Printf("%11.11I64i ", LineAddr); #else p.Printf("%11.11lli ", LineAddr); #endif LDisplayString ds(Font, p); ds.Draw(pDC, r.x1, CurrentY); CurrentX = ds.X(); } if (Addrs) CurrentY += CharSize.y; if (CurrentY < r.y2) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(r.x1, CurrentY, CurrentX-1, r.y2); } // Draw all the data buffers... for (unsigned int BufIdx = 0; BufIdx < Buf.Length(); BufIdx++) { LHexBuffer *b = Buf[BufIdx]; b->Pos.ZOff(Columns * CharSize.x, Lines * CharSize.y); b->Pos.Offset(r.x1 + (HEX_COLUMN * CharSize.x), r.y1); if (BufIdx) b->Pos.Offset ( BufIdx * (Columns + GAP_FILES) * CharSize.x, 0 ); if (CurrentX < b->Pos.x1) { // Paint any whitespace before this column pDC->Colour(L_WORKSPACE); pDC->Rectangle(CurrentX + 1, r.y1, b->Pos.x1 - 1, r.y2); } if (Buf.Length() > 1) { LSysBold->Transparent(false); LSysBold->Colour(L_TEXT, L_WORKSPACE); LDisplayString Ds(LSysBold, b->File ? b->File->GetName() : LLoadString(IDS_UNTITLED_BUFFER)); LRect r(b->Pos.x1, 0, b->Pos.x1+Ds.X()-1, Ds.Y()-1); Ds.Draw(pDC, r.x1, r.y1); TopMargin.Subtract(&r); } int64 End = MIN(b->Size, Start + (Lines * BytesPerLine)); if (b->GetData(Start, (size_t)(End-Start))) { LHexBuffer *Comp = Buf.Length() > 1 ? Buf[!BufIdx] : NULL; b->OnPaint(pDC, Start, End - Start, Comp); } CurrentX = b->Pos.x2; } if (CurrentX < r.x2) { // Paint any whitespace after the last column pDC->Colour(L_WORKSPACE); pDC->Rectangle(CurrentX, r.y1, r.x2, r.y2); } if (TopMargin.Length() > 0) { for (unsigned i=0; iColour(L_WORKSPACE); pDC->Rectangle(r); } } } bool LHexView::OnMouseWheel(double Lines) { if (VScroll) { VScroll->Value(VScroll->Value() + (int)Lines); Invalidate(); } return true; } bool LHexView::GetCursorFromLoc(int x, int y, GHexCursor &c) { uint64 Start = ((uint64)(VScroll ? VScroll->Value() : 0)) * BytesPerLine; int HexCols = BytesPerLine * 3; int AsciiCols = HexCols + GAP_HEX_ASCII; for (unsigned i=0; i= b->Pos.x1 && x <= b->Pos.x2) { int row = (y - b->Pos.y1) / CharSize.y; auto Row = b->Content[row]; if (!Row) Row = b->Content.First(); if (Row) { LDisplayString Ds(Font, b->Content[row]); auto col = Ds.CharAt(x - b->Pos.x1); if (col >= 0 && col < HexCols) { auto Byte = col / 3; int Bit = col % 3; c.Buf = b; c.Offset = Start + (row * BytesPerLine) + Byte; c.Nibble = Bit > 0; c.Pane = HexPane; c.BufIndex = i; return true; } else if (col >= AsciiCols) { auto Asc = col - AsciiCols; if (Asc < BytesPerLine) { c.Buf = b; c.Offset = Start + (row * BytesPerLine) + Asc; c.Nibble = 0; c.Pane = AsciiPane; c.BufIndex = i; return true; } } } else { LAssert(!"No content?"); return false; } } } return false; } void LHexView::OnMouseClick(LMouse &m) { Capture(m.Down()); if (m.Down()) { Focus(true); if (m.Left()) { GHexCursor c; if (GetCursorFromLoc(m.x, m.y, c)) { SetCursor(c.Buf, c.Offset, c.Nibble, m.Shift()); Cursor.Pane = c.Pane; } } } } void LHexView::OnMouseMove(LMouse &m) { if (IsCapturing()) { GHexCursor c; if (GetCursorFromLoc(m.x, m.y, c)) { if (c.Pane == AsciiPane && c.Offset >= Cursor.Offset) { c.Nibble = 1; } SetCursor(c.Buf, c.Offset, c.Nibble, true); } } } void LHexView::OnFocus(bool f) { Invalidate(); } void LHexView::InvalidateByte(int64 Idx) { for (unsigned i=0; i Loc; if (b->GetLocationOfByte(Loc, Idx, NULL)) { Loc[0].x2 += CharSize.x; for (unsigned i=0; iIsReadOnly) { if (k.Down()) { if (Cursor.Pane == HexPane) { int c = -1; if (k.c16 >= '0' && k.c16 <= '9') c = k.c16 - '0'; else if (k.c16 >= 'a' && k.c16 <= 'f') c = k.c16 - 'a' + 10; else if (k.c16 >= 'A' && k.c16 <= 'F') c = k.c16 - 'A' + 10; if (c >= 0 && c < 16) { uchar *Byte = b->Buf + (Cursor.Offset - b->BufPos); if (Cursor.Nibble) *Byte = (*Byte & 0xf0) | c; else *Byte = (c << 4) | (*Byte & 0xf); b->SetDirty(); InvalidateByte(Cursor.Offset); if (Cursor.Nibble == 0) SetCursor(b, Cursor.Offset, 1); else if (Cursor.Offset < b->Size - 1) SetCursor(b, Cursor.Offset+1, 0); } } else if (Cursor.Pane == AsciiPane) { uchar *Byte = b->Buf + (Cursor.Offset - b->BufPos); *Byte = k.c16; InvalidateByte(Cursor.Offset); b->SetDirty(); SetCursor(b, Cursor.Offset + 1); } } return true; } break; } case LK_RIGHT: { if (b && k.Down()) { if (Cursor.Pane == HexPane) { if (Cursor.Nibble == 0) SetCursor(b, Cursor.Offset, 1, k.Shift()); else if (Cursor.Offset < b->Size - 1) SetCursor(b, Cursor.Offset + 1, 0, k.Shift()); } else { SetCursor(b, Cursor.Offset + 1, 0); } } return true; break; } case LK_LEFT: { if (b && k.Down()) { if (Cursor.Pane == HexPane) { if (Cursor.Nibble == 1) SetCursor(b, Cursor.Offset, 0, k.Shift()); else if (Cursor.Offset > 0) SetCursor(b, Cursor.Offset - 1, 1, k.Shift()); } else { SetCursor(b, Cursor.Offset - 1, 0); } } return true; break; } case LK_UP: { if (b && k.Down()) { SetCursor(b, Cursor.Offset - 16, Cursor.Nibble, k.Shift()); } return true; break; } case LK_DOWN: { if (b && k.Down()) { if (k.Ctrl()) { // Find next difference bool Done = false; /* for (int64 n = Cursor - BufPos + 1; !Done && n < Size; n += Block) { if (GetData(n, Block)) { int Off = n - BufPos; int Len = BufUsed - Off; if (Len > Block) Len = Block; for (int i=0; iSize - 1, 1, k.Shift()); else SetCursor(b, Cursor.Offset - (Cursor.Offset % 16) + 15, 1, k.Shift()); } return true; break; } case LK_BACKSPACE: { if (b && k.Down() && !k.IsChar) { if (Cursor.Pane == HexPane) { if (Cursor.Nibble == 0) SetCursor(b, Cursor.Offset - 1, 1); else SetCursor(b, Cursor.Offset, 0); } else { SetCursor(b, Cursor.Offset - 1); } } return true; break; } case '\t': { if (k.Down()) { if (k.IsChar) { if (Cursor.Pane == HexPane) Cursor.Pane = AsciiPane; else Cursor.Pane = HexPane; Invalidate(); } } return true; break; } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////// AppWnd::AppWnd() : LDocApp(AppName, #ifdef _WIN32 IDI_ICON1 #else "Resources/icon64.png" #endif ) { #ifdef MAC LgiGetResObj(false, "ihex"); #endif if (_Create()) { DropTarget(true); if (_LoadMenu("IDM_MENU", NULL, IDM_FILE_MENU, IDM_RECENT_MENU)) { auto i = Menu->FindItem(IDM_SAVEAS); DeleteObj(i); CmdSave.MenuItem = Menu->FindItem(IDM_SAVE); CmdClose.MenuItem = Menu->FindItem(IDM_CLOSE); CmdChangeSize.MenuItem = Menu->FindItem(IDM_CHANGE_SIZE); CmdFind.MenuItem = Menu->FindItem(IDM_SEARCH); CmdNext.MenuItem = Menu->FindItem(IDM_NEXT); } Tools = LgiLoadToolbar(this, "Tools.gif", 24, 24); if (Tools) { Tools->TextLabels(true); Tools->Attach(this); Tools->AppendButton("Open", IDM_OPEN); CmdSave.ToolButton = Tools->AppendButton("Save", IDM_SAVE, TBT_PUSH, false); // CmdSaveAs.ToolButton = Tools->AppendButton("Save As", IDM_SAVEAS, TBT_PUSH, false); CmdFind.ToolButton = Tools->AppendButton("Search", IDM_SEARCH, TBT_PUSH, false, 3); Tools->AppendSeparator(); CmdVisualise.ToolButton = Tools->AppendButton("Visualise", IDM_VISUALISE, TBT_TOGGLE, false, 4); CmdText.ToolButton = Tools->AppendButton("Text", IDM_TEXTVIEW, TBT_TOGGLE, false, 5); LRegion r(GetClient()); Tools->Pour(r); } PourAll(); int By = (int) (LSysFont->GetHeight() * 3.25); Bar = new IHexBar(this, MAX(Tools ? Tools->Y() : 20, By)); Status = new LStatusBar; if (Status) { StatusInfo[0] = Status->AppendPane("", -1); StatusInfo[1] = Status->AppendPane("", 200); if (StatusInfo[1]) StatusInfo[1]->Sunken(true); Status->Attach(this); } Doc = new LHexView(this, Bar); if (Bar && Doc) { Bar->View = Doc; Bar->View->SetIsHex(Bar->IsHex()); } OnDirty(false); #ifndef WINDOWS SetIcon("icon64.png"); #endif Visible(true); PourAll(); DropTarget(true); } } AppWnd::~AppWnd() { DeleteObj(SearchDlg::Inst); LAppInst->AppWnd = 0; DeleteObj(Bar); _Destroy(); } void AppWnd::OnReceiveFiles(LArray &Files) { if (Files.Length() > 0) { if (OpenFile(Files[0], false) && Files.Length() > 1) { Doc->CompareFile(Files[1]); } } } bool AppWnd::OnRequestClose(bool OsShuttingDown) { if (Doc && Doc->IsDirty()) { int r = LgiMsg(this, "Do you want to save?", AppName, OsShuttingDown ? MB_YESNO : MB_YESNOCANCEL); if (r == IDCANCEL) return false; if (r == IDYES) { Doc->Save([](auto status) { LCloseApp(); }); return false; } } if (!Active) return LWindow::OnRequestClose(OsShuttingDown); return false; } void AppWnd::OnDirty(bool NewValue) { CmdSave.Enabled(NewValue); CmdSaveAs.Enabled(NewValue); // CmdClose.Enabled(Doc && Doc->HasFile()); // CmdChangeSize.Enabled(Doc && Doc->HasFile()); } bool AppWnd::OnKey(LKey &k) { return false; } void AppWnd::OnPosChange() { LDocApp::OnPosChange(); } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_HEX_VIEW: { if (n.Type == LNotifyCursorChanged) { char *Data; size_t Len; if (Visual && Doc->GetDataAtCursor(Data, Len)) { Visual->Visualise(Data, Len, GetCtrlValue(IDC_LITTLE) ); } if (TextView && Doc->GetDataAtCursor(Data, Len)) { LString Cs = Charset ? Charset->Name() : "utf-8"; if (Cs.Equals("us-ascii")) { LStringPipe p(1024); for (char *s = Data; s < Data + Len && s < Data + (4 << 10) && *s; s++) { if (*s >= ' ' || *s == '\n' || *s == '\t') p.Push(s, 1); } - LString t = p.NewGStr(); + LString t = p.NewLStr(); TextView->Name(t); } else { LAutoString t((char*)LNewConvertCp("utf-8", Data, Cs, MIN(Len, 8<<10))); TextView->Name(t); } } auto SelLen = Doc->GetSelectedNibbles(); char s[256]; sprintf_s(s, sizeof(s), "Selection: %.1f bytes", (double)SelLen/2.0); StatusInfo[1]->Name(SelLen ? s : NULL); } break; } } return LDocApp::OnNotify(Ctrl, n); } void AppWnd::OnPulse() { } LMessage::Result AppWnd::OnEvent(LMessage *Msg) { return LDocApp::OnEvent(Msg); } void AppWnd::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } #define SPLIT_X 590 void AppWnd::ToggleVisualise() { if (GetCtrlValue(IDM_VISUALISE)) { if (!Split) Split = new LBox; if (Split) { LString DefVisual; LAppInst->GetOption("visual", DefVisual); Doc->Detach(); Split->Value(SPLIT_X); Split->Attach(this); Split->AddView(Doc); Split->AddView(Visual = new LVisualiseView(this, DefVisual)); Split->AttachChildren(); } } else { Doc->Detach(); DeleteObj(Split); Doc->Attach(this); Visual = NULL; } PourAll(); } class TextBox : public LBox { LTableLayout *Tbl; public: LCombo *Charset; LTextView3 *TextView; TextBox() { SetVertical(true); AddView(Tbl = new LTableLayout(99)); auto c = Tbl->GetCell(0, 0); c->VerticalAlign(LCss::VerticalMiddle); c->Add(new LTextLabel(-1, 0, 0, -1, -1, "Interpret with charset:")); c = Tbl->GetCell(1, 0); c->Add(Charset = new LCombo(98, 0, 0, -1, -1)); for (LCharset *cs = LGetCsList(); cs->Charset; cs++) Charset->Insert(cs->Charset); Tbl->GetCss(true)->Height("30px"); AddView(TextView = new LTextView3(100, 0, 0, 100, 100, 0)); } int OnNotify(LViewI *c, LNotification n) { return 0; } }; void AppWnd::ToggleTextView() { if (GetCtrlValue(IDM_TEXTVIEW)) { SetCtrlValue(IDM_VISUALISE, false); if (!Split) Split = new LBox; if (Split) { Doc->Detach(); Split->Value(SPLIT_X); Split->Attach(this); Split->AddView(Doc); TextBox *t = new TextBox; Split->AddView(t); TextView = t->TextView; Charset = t->Charset; Split->AttachChildren(); } } else { Doc->Detach(); DeleteObj(Split); Doc->Attach(this); TextView = NULL; Charset = NULL; } PourAll(); } int Cmp(const char **a, const char **b) { return stricmp(*a, *b); } int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_COPY_HEX: { if (!Doc) break; Doc->Copy(FmtHex); break; } case IDM_COPY_TEXT: { if (!Doc) break; Doc->Copy(FmtText); break; } case IDM_COPY_CODE: { if (!Doc) break; Doc->Copy(FmtCode); break; } case IDM_PASTE: { if (!Doc) break; Doc->Paste(FmtHex); break; } case IDM_PASTE_BINARY: { if (!Doc) break; Doc->Paste(FmtText); break; } case IDM_COMBINE_FILES: { auto s = new LFileSelect; s->Parent(this); s->MultiSelect(true); s->Open([this](auto s, auto status) { if (status) { int64 Size = 0; size_t i; for (i=0; iLength(); i++) Size += LFileSize((*s)[i]); auto o = new LFileSelect; o->Save([this,Size,s](auto o, auto ok) { if (ok) { LFile Out; if (!Out.Open(o->Name(), O_WRITE)) { LgiTrace("%s:%i - Can't open %s\n", _FL, o->Name()); } else { LProgressDlg Dlg(this); Dlg.SetRange(Size); Dlg.SetType("MB"); Dlg.SetScale(1.0/1024.0/1024.0); LArray Buf; Buf.Length(1 << 20); Out.SetSize(0); LArray Files; for (auto i=0; iLength(); i++) Files[i] = (*s)[i]; Files.Sort(Cmp); for (auto i=0; i 0) { auto w = Out.Write(&Buf[0], r); if (w != r) { LgiTrace("%s:%i - Write error...!\n", _FL); break; } p += w; Dlg.Value(Dlg.Value() + w); // LYield(); } else break; } } else LgiTrace("%s:%i - Can't open %s\n", _FL, Files[i]); } } } delete o; }); } delete s; }); break; } case IDM_VISUALISE: { if (GetCtrlValue(IDM_TEXTVIEW)) { SetCtrlValue(IDM_TEXTVIEW, false); ToggleTextView(); } ToggleVisualise(); OnNotify(Doc, LNotifyCursorChanged); break; } case IDM_TEXTVIEW: { if (GetCtrlValue(IDM_VISUALISE)) { SetCtrlValue(IDM_VISUALISE, false); ToggleVisualise(); } ToggleTextView(); OnNotify(Doc, LNotifyCursorChanged); break; } case IDM_SAVE: { if (Doc) Doc->Save(NULL); break; } case IDM_EXIT: { if (Doc) Doc->Empty(); LCloseApp(); break; } case IDM_NEW_BUFFER: { if (!Doc) break; Doc->CreateFile(256); break; } case IDM_CLOSE: { if (Doc) Doc->CloseFile(); else LCloseApp(); break; } case IDM_SAVE_SELECTION: { if (!Doc) break; auto s = new LFileSelect; s->Parent(this); s->Save([this](auto s, auto ok) { if (ok) Doc->SaveSelection(NULL, s->Name()); delete s; }); break; } case IDM_FILL_RND: { if (!Doc) break; RandomData Rnd; Doc->SelectionFillRandom(&Rnd); break; } case IDM_SEARCH: { if (SearchDlg::Inst) delete SearchDlg::Inst; new SearchDlg(this); break; } case IDM_NEXT: { if (Doc && SearchDlg::Inst) Doc->DoSearch(SearchDlg::Inst); break; } case IDM_FILE_COMPARE: { if (!Doc || !Doc->HasFile()) break; auto s = new LFileSelect; s->Parent(this); s->Open([this](auto s, auto ok) { if (ok) Doc->CompareFile(s->Name()); delete s; }); break; } case IDM_CHANGE_SIZE: { if (!Doc) break; LHexBuffer *Cur = Doc->GetCursorBuffer(); if (!Cur) break; auto Cursor = Bar->GetOffset(); auto Dlg = new ChangeSizeDlg(this, Cursor); Dlg->DoModal([this,Dlg](auto dlg, auto code) { if (code) Doc->SetFileSize(Dlg->Size); delete dlg; }); break; } case IDM_SELECT_ALL: { if (Doc) Doc->SelectAll(); break; } case IDM_HELP: { Help("index.html"); break; } case IDM_ABOUT: { LAbout Dlg( this, AppName, APP_VER, "\nSimple Hex Viewer", "_about.gif", "http://www.memecode.com/ihex.php", "fret@memecode.com"); break; } } return LDocApp::OnCommand(Cmd, Event, Wnd); } void AppWnd::Help(const char *File) { if (!File) return; char e[MAX_PATH_LEN]; sprintf_s(e, sizeof(e), "%s", #ifdef MAC LGetExeFile() #else LGetExePath() #endif .Get()); #ifdef WIN32 LString Leaf = LGetLeaf(e); if (Leaf.Find("Release") >= 0 || Leaf.Find("Debug") >= 0) LTrimDir(e); #elif defined(MAC) LMakePath(e, sizeof(e), e, "Contents/Resources"); #endif LMakePath(e, sizeof(e), e, "Help"); LMakePath(e, sizeof(e), e, File); if (LFileExists(e)) { LExecute(e); } else { LgiMsg(this, "The help file '%s' doesn't exist.", AppName, MB_OK, e); } } void AppWnd::SetStatus(int Pos, char *Text) { if (Pos >= 0 && Pos < 3 && StatusInfo[Pos] && Text) { StatusInfo[Pos]->Name(Text); } } LRect GetClient(LView *w) { #ifdef WIN32 RECT r = {0, 0, 0, 0}; if (w) { GetClientRect(w->Handle(), &r); } return LRect(r); #else return LRect(0, 0, (w)?w->X()-1:0, (w)?w->Y()-1:0); #endif } bool AppWnd::OpenFile(const char *FileName, bool ReadOnly) { bool Status = false; if (Doc) { Status = Doc->OpenFile(FileName, ReadOnly); OnDocument(Status); OnDirty(GetDirty()); SetCurFile(FileName); } return Status; } bool AppWnd::SaveFile(const char *FileName) { bool Status = false; if (Doc) { Status = Doc->SaveFile(NULL, FileName); } return Status; } bool AppWnd::Empty() { return (Doc) ? Doc->Empty() : true; } void AppWnd::OnDocument(bool Valid) { // LgiTrace("%s:%i - OnDocument(%i)\n", _FL, Valid); CmdFind.Enabled(Valid); CmdNext.Enabled(Valid); CmdVisualise.Enabled(Valid); CmdText.Enabled(Valid); bool Dirt = GetDirty(); CmdSave.Enabled(Valid && Dirt); CmdSaveAs.Enabled(Valid); CmdClose.Enabled(Valid); CmdChangeSize.Enabled(Valid); } ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LResources::SetLoadStyles(true); LApp a(AppArgs, "i.Hex"); if (a.IsOk()) { #if 0 auto s = LFile::Path::PrintAll(); printf("%s", s.Get()); #endif a.AppWnd = new AppWnd; a.Run(); } return 0; }