From 89533202e1214bc31e02b33cc815cc7d5d0eb0e6 Mon Sep 17 00:00:00 2001 From: esperknight Date: Sat, 24 Apr 2021 13:27:06 -0400 Subject: [PATCH] Initial checkin of Atlas --- Atlas.pdf | Bin 0 -> 72079 bytes Atlas.sln | 19 + Atlas.sln.old | 19 + Atlas.suo | Bin 0 -> 70656 bytes Atlas.suo.old | Bin 0 -> 13312 bytes Atlas.vcproj | 320 ++++++++++ Atlas.vcproj.8.00.old | 315 ++++++++++ Atlas.vcproj.HIKARI.Steve.user | 65 ++ AtlasCore.cpp | 1082 ++++++++++++++++++++++++++++++++ AtlasCore.h | 76 +++ AtlasExtension.cpp | 119 ++++ AtlasExtension.h | 53 ++ AtlasFile.cpp | 480 ++++++++++++++ AtlasFile.h | 87 +++ AtlasLogger.cpp | 91 +++ AtlasLogger.h | 42 ++ AtlasMain.cpp | 47 ++ AtlasParser.cpp | 362 +++++++++++ AtlasParser.h | 32 + AtlasStats.cpp | 208 ++++++ AtlasStats.h | 61 ++ AtlasTypes.cpp | 1 + AtlasTypes.h | 153 +++++ GenericVariable.cpp | 169 +++++ GenericVariable.h | 50 ++ Pointer.cpp | 463 ++++++++++++++ Pointer.h | 111 ++++ PointerHandler.cpp | 364 +++++++++++ PointerHandler.h | 82 +++ ReadMe.txt | 32 + Table.cpp | 639 +++++++++++++++++++ Table.h | 118 ++++ UpgradeLog.XML | 15 + UpgradeLog2.XML | 17 + stdafx.cpp | 8 + stdafx.h | 25 + 36 files changed, 5725 insertions(+) create mode 100644 Atlas.pdf create mode 100644 Atlas.sln create mode 100644 Atlas.sln.old create mode 100644 Atlas.suo create mode 100644 Atlas.suo.old create mode 100644 Atlas.vcproj create mode 100644 Atlas.vcproj.8.00.old create mode 100644 Atlas.vcproj.HIKARI.Steve.user create mode 100644 AtlasCore.cpp create mode 100644 AtlasCore.h create mode 100644 AtlasExtension.cpp create mode 100644 AtlasExtension.h create mode 100644 AtlasFile.cpp create mode 100644 AtlasFile.h create mode 100644 AtlasLogger.cpp create mode 100644 AtlasLogger.h create mode 100644 AtlasMain.cpp create mode 100644 AtlasParser.cpp create mode 100644 AtlasParser.h create mode 100644 AtlasStats.cpp create mode 100644 AtlasStats.h create mode 100644 AtlasTypes.cpp create mode 100644 AtlasTypes.h create mode 100644 GenericVariable.cpp create mode 100644 GenericVariable.h create mode 100644 Pointer.cpp create mode 100644 Pointer.h create mode 100644 PointerHandler.cpp create mode 100644 PointerHandler.h create mode 100644 ReadMe.txt create mode 100644 Table.cpp create mode 100644 Table.h create mode 100644 UpgradeLog.XML create mode 100644 UpgradeLog2.XML create mode 100644 stdafx.cpp create mode 100644 stdafx.h diff --git a/Atlas.pdf b/Atlas.pdf new file mode 100644 index 0000000000000000000000000000000000000000..905a6a957b5279c68607a93d949ff056258b6c22 GIT binary patch literal 72079 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%0At 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 z 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Atlas.vcproj.8.00.old b/Atlas.vcproj.8.00.old new file mode 100644 index 0000000..33eb3b8 --- /dev/null +++ b/Atlas.vcproj.8.00.old @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Atlas.vcproj.HIKARI.Steve.user b/Atlas.vcproj.HIKARI.Steve.user new file mode 100644 index 0000000..010270d --- /dev/null +++ b/Atlas.vcproj.HIKARI.Steve.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/AtlasCore.cpp b/AtlasCore.cpp new file mode 100644 index 0000000..2500aca --- /dev/null +++ b/AtlasCore.cpp @@ -0,0 +1,1082 @@ +//----------------------------------------------------------------------------- +// AtlasCore - A class to insert Atlas-type scripts +// By Steve Monaco (stevemonaco@hotmail.com) +//----------------------------------------------------------------------------- + +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Table.h" +#include "AtlasCore.h" +#include "AtlasTypes.h" +#include "GenericVariable.h" +#include "AtlasFile.h" +#include "Pointer.h" +#include "AtlasLogger.h" +#include "AtlasStats.h" +#include "PointerHandler.h" +#include "AtlasExtension.h" + +using namespace std; + +// Constructor + +AtlasCore Atlas; +unsigned int CurrentLine = 1; +int bSwap = 0; +int StringAlign = 0; +int MaxEmbPtr = 0; + +AtlasCore::AtlasCore() : PtrHandler(&VarMap), Parser(&VarMap), Extensions(&VarMap) +{ + CurrentLine = 1; + HeaderSize = 0; + IsInJmp = false; +} + +// Destructor +AtlasCore::~AtlasCore() +{ +} + +bool AtlasCore::Insert(const char* RomFileName, const char* ScriptFileName) +{ + ifstream script; + script.open(ScriptFileName, ios::in); + if(!script.is_open()) + { + printf("Unable to open script file '%s'\n", ScriptFileName); + return false; + } + + if(!File.OpenFileT(RomFileName)) + { + printf("Unable to open target file '%s'\n", RomFileName); + return false; + } + + // Target and pointer files will initially be the same file + if(!File.OpenFileP(RomFileName)) + { + printf("Unable to open pointer file '%s'\n", RomFileName); + return false; + } + + // Parse file + bool ParseSuccess = false; + clock_t ParseStart = clock(); + ParseSuccess = Parser.ParseFile(script); + clock_t ParseTime = clock() - ParseStart; + + PrintSummary("Parsing", ParseTime); + + if(!ParseSuccess) + return false; + + EmbPtrs.SetListSize(MaxEmbPtr+1); + + // Insert file + clock_t InsertionStart = clock(); + + for(ListBlockIt Block = Parser.Blocks.begin(); Block != Parser.Blocks.end(); Block++) + { + File.FlushText(); + + // Execute list of commands + for(ListCmdIt com = Block->Commands.begin(); com != Block->Commands.end(); com++) + { + CurrentLine = com->Line; + if(!ExecuteCommand(*com)) + goto InsertionSummary; + } + + if(Block->StartLine != -1) + CurrentLine = Block->StartLine; + + // Insert text strings + for(ListStringIt text = Block->TextLines.begin(); text != Block->TextLines.end(); text++) + { + if(!text->empty()) + { + if(!IsInJmp) + { + Logger.ReportError(CurrentLine, "\"You must specify an address using JMP before inserting text\""); + goto InsertionSummary; + } + if(!File.InsertText(*text, CurrentLine)) + goto InsertionSummary; + } + CurrentLine++; + } + } + + File.FlushText(); + +InsertionSummary: + clock_t InsertionTime = clock() - InsertionStart; + + PrintSummary("Insertion", InsertionTime); + + Stats.End(CurrentLine); // Hack for the last line + PrintStatistics(); + if(MaxEmbPtr != 0) + PrintUnwrittenPointers(); + + return true; +} + +unsigned int AtlasCore::GetHeaderSize() +{ + return HeaderSize; +} + +void AtlasCore::SetDebugging(FILE* output) +{ + if(output == NULL) + Logger.SetLogStatus(false); + else + Logger.SetLogStatus(true); + Logger.SetLogSource(output); +} + +void AtlasCore::PrintStatistics() +{ + const char* Frame = "+------------------------------------------------------------------------------"; + + if(Stats.Stats.size() == 0); // Do nothing + else if(Stats.Stats.size() == 1) + { + PrintStatisticsBlock("Total", Stats.Stats.front()); + } + else // Print each block's content + { + unsigned int blocknum = 1; + char buf[127]; + for(ListStatsIt i = Stats.Stats.begin(); i != Stats.Stats.end(); i++) + { + _snprintf(buf, 127, "Block %d", blocknum); + PrintStatisticsBlock(buf, *i); + blocknum++; + } + + Stats.GenerateTotalStats(Total); + + // Print out the total statistics + printf("%s\n", Frame); + printf("| Total Statistics\n"); + printf("| Script Size %u\n", Total.ScriptSize); + printf("| Script Inserted %u\n", Total.ScriptSize - Total.ScriptOverflowed); + if(Total.ScriptOverflowed != 0) + printf("| Script Overflowed %u\n", Total.ScriptOverflowed); + if(Total.MaxBound != -1) + printf("| Space Remaining %u\n", Total.SpaceRemaining); + printf("|\n"); + + if(Total.HasCommands()) + { + printf("| Command Execution Listing\n"); + for(int j = 0; j < CommandCount; j++) + { + if(Total.ExecCount[j] != 0) + printf("| %s: %u\n", CommandStrings[j], Total.ExecCount[j]); + } + } + + if(Total.EmbPointerWrites > 0 || Total.AutoPointerWrites > 0 || + Total.FailedListWrites > 0 || Total.ExtPointerWrites > 0) + printf("| Pointer Listing\n"); + if(Total.PointerWrites != 0) + printf("| General Pointers Written: %u\n", Total.PointerWrites); + if(Total.EmbPointerWrites != 0) + printf("| Embedded Pointers Written: %u\n", Total.EmbPointerWrites); + if(Total.AutoPointerWrites != 0) + printf("| Autowrite Pointers Written: %u\n", Total.AutoPointerWrites); + if(Total.FailedListWrites != 0) + printf("| Failed PointerList Writes: %u\n", Total.FailedListWrites); + if(Total.ExtPointerWrites != 0) + printf("| Extension Pointer Writes: %u\n", Total.ExtPointerWrites); + printf("%s\n\n", Frame); + } +} + +void AtlasCore::PrintStatisticsBlock(const char* Title, InsertionStatistics& Stats) +{ + const char* Frame = "+------------------------------------------------------------------------------"; + + printf("%s\n", Frame); + printf("| %s\n| Start: Line %u File Position $%X", Title, Stats.LineStart, Stats.StartPos); + if(Stats.MaxBound != -1) + printf(" Bound $%X", Stats.MaxBound); + printf("\n"); + printf("| End: Line %u File Position $%X\n", Stats.LineEnd, Stats.StartPos + Stats.ScriptSize - Stats.ScriptOverflowed); + printf("%s\n", Frame); + printf("| Script size %u\n", Stats.ScriptSize); + printf("| Bytes Inserted %u", Stats.ScriptSize - Stats.ScriptOverflowed); + if(Stats.ScriptOverflowed != 0) + printf("\n| Script Overflowed %u", Stats.ScriptOverflowed); + if(Stats.MaxBound != -1) + printf("\n| Space Remaining %u", Stats.SpaceRemaining); + printf("\n|\n"); + + if(Stats.HasCommands()) + { + printf("| Command Execution Listing\n"); + for(int j = 0; j < CommandCount; j++) + { + if(Stats.ExecCount[j] != 0) + printf("| %s: %u\n", CommandStrings[j], Stats.ExecCount[j]); + } + printf("|\n"); + } + + printf("| Pointer Listing\n"); + printf("| General Pointers Written: %u\n", Stats.PointerWrites); + if(Stats.EmbPointerWrites != 0) + printf("| Embedded Pointers Written: %u\n", Stats.EmbPointerWrites); + if(Stats.AutoPointerWrites != 0) + printf("| Autowrite Pointers Written: %u\n", Stats.AutoPointerWrites); + if(Stats.FailedListWrites != 0) + printf("| Failed PointerList Writes: %u\n", Stats.FailedListWrites); + if(Stats.ExtPointerWrites != 0) + printf("| Extension Pointer Writes: %u\n", Stats.ExtPointerWrites); + printf("%s\n\n", Frame); +} + +void AtlasCore::PrintSummary(const char* Title, unsigned int TimeCompleted) +{ + unsigned int SumErrors = 0; + unsigned int SumWarnings = 0; + printf("%s summary: %u msecs\n", Title, TimeCompleted); + + // Print errors and warnings + for(ListErrorIt i = Logger.Errors.begin(); i != Logger.Errors.end(); i++) + { + if(i->Severity == FATALERROR) + { + printf("Error: "); + SumErrors++; + } + else if(i->Severity == WARNING) + { + printf("Warning: "); + SumWarnings++; + } + printf("%s on line %d\n", i->Error.c_str(), i->LineNumber); + } + Logger.Errors.clear(); + printf("%s - %u error(s), %u warning(s)\n\n", Title, SumErrors, SumWarnings); +} + +void AtlasCore::PrintUnwrittenPointers() +{ + const char* Frame = "+------------------------------------------------------------------------------"; + + printf("%s\n", Frame); + printf("Printing Initialized But Unwritten Embedded Pointers Summary\n"); + unsigned int TextPos, PointerPos, count = 0; + + for(int i = 0; i < EmbPtrs.GetListSize(); i++) + { + if(!EmbPtrs.GetPointerState(i, TextPos, PointerPos)) // Pointer is uninit'd or not fully written + { + if(TextPos == -1 && PointerPos == -1) // Uninitialized + continue; + printf("| EmbPtr $%X EMBWRITE $%08X EMBSET $%08X\n", i, TextPos, PointerPos); + count++; + } + } + printf("|\n| %d Pointer(s) Detected\n", count); + printf("%s\n\n", Frame); +} + +bool AtlasCore::ExecuteCommand(Command& Cmd) +{ + static unsigned int PtrValue; + static unsigned int PtrNum; + static unsigned int PtrPos; + static unsigned int Size; + static bool Success; + static unsigned char PtrByte; + static unsigned int StartPos; + static PointerList* List = NULL; + static PointerTable* Tbl = NULL; + static AtlasContext* Context = NULL; + static AtlasExtension* Ext = NULL; + string FuncName; + + if(IsInJmp && Cmd.Function != CMD_JMP1 && Cmd.Function != CMD_JMP2) + Stats.AddCmd(Cmd.Function); + else + Total.AddCmd(Cmd.Function); + + switch(Cmd.Function) + { + case CMD_JMP1: + File.MoveT(StringToUInt(Cmd.Parameters[0].Value), -1); + Stats.NewStatsBlock(File.GetPosT(), -1, Cmd.Line); + Logger.Log("%6u JMP ROM Position is now $%X\n", Cmd.Line, StringToUInt(Cmd.Parameters[0].Value)); + IsInJmp = true; + return true; + case CMD_JMP2: + File.MoveT(StringToUInt(Cmd.Parameters[0].Value), StringToUInt(Cmd.Parameters[1].Value)); + Stats.NewStatsBlock(File.GetPosT(), StringToUInt(Cmd.Parameters[1].Value), Cmd.Line); + Logger.Log("%6u JMP ROM Position is now $%X with max bound of $%X\n", + Cmd.Line, StringToUInt(Cmd.Parameters[0].Value), StringToUInt(Cmd.Parameters[1].Value)); + IsInJmp = true; + return true; + case CMD_SMA: + Success = DefaultPointer.SetAddressType(Cmd.Parameters[0].Value); + if(Success) + Logger.Log("%6u SMA Addressing type is now '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_HDR: + unsigned int Size; + Size = StringToUInt(Cmd.Parameters[0].Value); + EmbPtrs.SetHeaderSize(Size); + DefaultPointer.SetHeaderSize(Size); + HeaderSize = Size; + Logger.Log("%6u HDR Header size is now $%X\n", Cmd.Line, StringToUInt(Cmd.Parameters[0].Value)); + return true; + case CMD_STRTYPE: + Success = File.SetStringType(Cmd.Parameters[0].Value); + if(Success) + Logger.Log("%6u STRTYPE String type is now '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_ADDTBL: + Success = AddTable(Cmd); + if(Success) + Logger.Log("%6u ADDTBL Added table '%s' as '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), Cmd.Parameters[1].Value.c_str()); + return Success; + case CMD_ACTIVETBL: + Success = ActivateTable(Cmd.Parameters[0].Value); + if(Success) + Logger.Log("%6u ACTIVETBL Active table is now '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_VAR: // Already handled by AtlasParser to validate types, should never get here + return true; + case CMD_WUB: + PtrValue = DefaultPointer.GetUpperByte(File.GetPosT()); + File.WriteP(&PtrValue, 1, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u WUB ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_WBB: + PtrValue = DefaultPointer.GetBankByte(File.GetPosT()); + File.WriteP(&PtrValue, 1, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u WBB ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_WHB: + PtrValue = DefaultPointer.GetHighByte(File.GetPosT()); + File.WriteP(&PtrValue, 1, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u WHB ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_WLB: + PtrValue = DefaultPointer.GetLowByte(File.GetPosT()); + File.WriteP(&PtrValue, 1, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u WLB ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_W16: + PtrValue = DefaultPointer.Get16BitPointer(File.GetPosT()); + if(bSwap) + PtrValue = EndianSwap(PtrValue, 2); + File.WriteP(&PtrValue, 2, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u W16 ScriptPos $%X PointerPos $%X PointerValue $%04X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_W24: + PtrValue = DefaultPointer.Get24BitPointer(File.GetPosT()); + if(bSwap) + PtrValue = EndianSwap(PtrValue, 3); + File.WriteP(&PtrValue, 3, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u W24 ScriptPos $%X PointerPos $%X PointerValue $%06X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_W32: + PtrValue = DefaultPointer.Get32BitPointer(File.GetPosT()); + if(bSwap) + PtrValue = EndianSwap(PtrValue, 4); + File.WriteP(&PtrValue, 4, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u W32 ScriptPos $%X PointerPos $%X PointerValue $%08\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_EMBSET: + PtrNum = StringToUInt(Cmd.Parameters[0].Value); + Success = EmbPtrs.SetPointerPosition(PtrNum, File.GetPosT()); + Size = EmbPtrs.GetSize(PtrNum); + if(Size == -1) + return false; + Logger.Log("%6u EMBSET Pointer Position %u set to $%X\n", Cmd.Line, PtrNum, File.GetPosT()); + if(Success) // Write out embedded pointer + { + PtrValue = EmbPtrs.GetPointerValue(PtrNum); + if(File.GetMaxWritableBytes() > Size / 8) + { + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + int tpos = File.GetPosT(); + File.WriteT(&PtrValue, Size / 8, 1); // Emb pointers are within script files + Logger.Log("%6u EMBSET Triggered Write: ScriptPos $%X PointerPos $%X PointerValue $%X Size %dd", Cmd.Line, + EmbPtrs.GetTextPosition(PtrNum), tpos, PtrValue, Size); + Stats.IncEmbPointerWrites(); + } + else + Logger.Log("%6u EMBSET Failed to write due to insufficient space\n"); + } + else // Reserve space so the embedded pointer and script don't compete + { // for the same part of the file + if(File.GetMaxWritableBytes() > Size / 8) + { + unsigned int Zero = 0; + File.WriteT(&Zero, Size/8, 1); + } + else + Logger.Log("%6u EMBSET Failed to write due to insufficient space\n"); + } + return true; + case CMD_EMBTYPE: + Success = EmbPtrs.SetType(Cmd.Parameters[0].Value, StringToInt64(Cmd.Parameters[2].Value), StringToUInt(Cmd.Parameters[1].Value)); + if(!Success) + Logger.ReportError(Cmd.Line, "Bad size %d for EMBTYPE", StringToUInt(Cmd.Parameters[0].Value)); + else + Logger.Log("%6u EMBTYPE Embedded Pointer size %u Offsetting %I64d\n", Cmd.Line, StringToUInt(Cmd.Parameters[1].Value), StringToInt64(Cmd.Parameters[0].Value)); + return Success; + case CMD_EMBWRITE: + PtrNum = StringToUInt(Cmd.Parameters[0].Value); + Success = EmbPtrs.SetTextPosition(PtrNum, File.GetPosT()); + Size = EmbPtrs.GetSize(PtrNum); + if(Size == -1) + return false; + Logger.Log("%6u EMBWRITE Pointed Position %u set to $%X\n", Cmd.Line, PtrNum, File.GetPosT()); + if(Success) // Write out embedded pointer + { + PtrPos = EmbPtrs.GetPointerPosition(PtrNum); + PtrValue = EmbPtrs.GetPointerValue(PtrNum); + if(File.GetMaxWritableBytes() > Size / 8) + { + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteT(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u EMBWRITE Triggered Write: ScriptPos $%X PointerPos $%X PointerValue $%X Size %dd\n", Cmd.Line, + File.GetPosT(), PtrPos, PtrValue, Size); + Stats.IncEmbPointerWrites(); + } + else + Logger.Log("%6u EMBWRITE Failed to write due to insufficient space\n"); + } + return true; + case CMD_BREAK: + return false; + case CMD_PTRTBL: + Success = PtrHandler.CreatePointerTable(Cmd.Parameters[0].Value, StringToUInt(Cmd.Parameters[1].Value), StringToUInt(Cmd.Parameters[2].Value), Cmd.Parameters[3].Value); + if(Success) + Logger.Log("%6u PTRTBL Pointer Table '%s' created StartPos $%X Increment %dd CustomPointer '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + StringToUInt(Cmd.Parameters[1].Value), StringToUInt(Cmd.Parameters[2].Value), Cmd.Parameters[3].Value.c_str()); + return Success; + case CMD_WRITETBL: + PtrValue = PtrHandler.GetTableAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size, PtrPos); + if(PtrValue == -1) + return false; + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteP(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u WRITE PointerTable '%s' ScriptPos $%X PointerPos $%X PointerValue $%08X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + case CMD_PTRLIST: + Success = PtrHandler.CreatePointerList(Cmd.Parameters[0].Value, Cmd.Parameters[1].Value.c_str(), Cmd.Parameters[2].Value); + if(Success) + Logger.Log("%6u PTRTBL Pointer List '%s' created from file '%s' CustomPointer '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + Cmd.Parameters[1].Value.c_str(), Cmd.Parameters[2].Value.c_str()); + return Success; + case CMD_WRITELIST: + PtrValue = PtrHandler.GetListAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size, PtrPos); + if(PtrValue == -1) + return false; + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteP(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u WRITE PointerList '%s' ScriptPos $%X PointerPos $%X PointerValue $%08X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + case CMD_AUTOWRITETBL: + Tbl = (PointerTable*)VarMap.GetVar(Cmd.Parameters[0].Value)->GetData(); + if(Tbl == NULL) + { + Logger.ReportError(CurrentLine, "Identifier '%s' has not been initialized with PTRTBL", Cmd.Parameters[0].Value.c_str()); + return false; + } + Success = File.AutoWrite(Tbl, Cmd.Parameters[1].Value); + if(!Success) + Logger.ReportError(CurrentLine, "'%s' has not been defined as an end token in the active table", Cmd.Parameters[1].Value.c_str()); + else + Logger.Log("%6u AUTOWRITE EndTag '%s' PointerTable '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + Cmd.Parameters[1].Value.c_str()); + return Success; + case CMD_AUTOWRITELIST: + List = (PointerList*)VarMap.GetVar(Cmd.Parameters[0].Value)->GetData(); + if(List == NULL) + { + Logger.ReportError(CurrentLine, "Identifier '%s' has not been initialized with PTRLIST", Cmd.Parameters[0].Value.c_str()); + return false; + } + Success = File.AutoWrite(List, Cmd.Parameters[1].Value); + if(!Success) + Logger.ReportError(CurrentLine, "'%s' has not been defined as an end token in the active table", Cmd.Parameters[1].Value.c_str()); + else + Logger.Log("%6u AUTOWRITE EndTag '%s' PointerList '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + Cmd.Parameters[1].Value.c_str()); + return Success; + case CMD_CREATEPTR: + Success = PtrHandler.CreatePointer(Cmd.Parameters[0].Value, Cmd.Parameters[1].Value, + StringToInt64(Cmd.Parameters[2].Value), StringToUInt(Cmd.Parameters[3].Value), HeaderSize); + if(Success) + Logger.Log("%6u CREATEPTR CustomPointer '%s' Addressing '%s' Offsetting %I64d Size %dd HeaderSize $%X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), Cmd.Parameters[1].Value.c_str(), StringToInt64(Cmd.Parameters[2].Value), StringToUInt(Cmd.Parameters[3].Value), HeaderSize); + return Success; + case CMD_WRITEPTR: + PtrValue = PtrHandler.GetPtrAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size); + PtrPos = StringToUInt(Cmd.Parameters[1].Value); + if(PtrValue == -1) + return false; + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteP(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u WRITE CustomPointer '%s' ScriptPos $%X PointerPos $%X PointerValue $%08X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + case CMD_LOADEXT: + Success = Extensions.LoadExtension(Cmd.Parameters[0].Value, Cmd.Parameters[1].Value); + if(Success) + Logger.Log("%6u LOADEXT Loaded extension %s successfully\n", Cmd.Line, Cmd.Parameters[1].Value.c_str()); + return Success; + case CMD_EXECEXT: + return ExecuteExtension(Cmd.Parameters[0].Value, Cmd.Parameters[1].Value, &Context); + case CMD_DISABLETABLE: + Tbl = (PointerTable*)VarMap.GetVar(Cmd.Parameters[0].Value)->GetData(); + if(Tbl == NULL) + { + Logger.ReportError(CurrentLine, "Identifier '%s' has not been initialized with PTRTBL", Cmd.Parameters[0].Value.c_str()); + return false; + } + Success = File.DisableWrite(Cmd.Parameters[1].Value, true); + if(!Success) + Logger.ReportError(CurrentLine, "'%s' has not been defined as an autowrite end token", Cmd.Parameters[1].Value.c_str()); + else + Logger.Log("%6u DISABLE EndTag '%s' PointerTable '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + Cmd.Parameters[1].Value.c_str()); + return Success; + case CMD_DISABLELIST: + List = (PointerList*)VarMap.GetVar(Cmd.Parameters[0].Value)->GetData(); + if(List == NULL) + { + Logger.ReportError(CurrentLine, "Identifier '%s' has not been initialized with PTRLIST", Cmd.Parameters[0].Value.c_str()); + return false; + } + Success = File.DisableWrite(Cmd.Parameters[1].Value, false); + if(!Success) + Logger.ReportError(CurrentLine, "'%s' has not been defined as an autowrite end token", Cmd.Parameters[1].Value.c_str()); + else + Logger.Log("%6u DISABLE EndTag '%s' PointerList '%s'\n", Cmd.Line, Cmd.Parameters[1].Value.c_str(), + Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_PASCALLEN: + Success = File.SetPascalLength(StringToUInt(Cmd.Parameters[0].Value)); + if(Success) + Logger.Log("%6u PASCALLEN Length for pascal strings set to %u\n", Cmd.Line, StringToUInt(Cmd.Parameters[0].Value)); + else + Logger.ReportError(CurrentLine, "Invalid length %u for PASCALLEN", StringToUInt(Cmd.Parameters[0].Value)); + return Success; + case CMD_AUTOEXEC: + Ext = (AtlasExtension*)VarMap.GetVar(Cmd.Parameters[0].Value)->GetData(); + if(Ext == NULL) + { + Logger.ReportError(CurrentLine, "Identifier '%s' has not been initialized with LOADEXT", Cmd.Parameters[0].Value.c_str()); + return false; + } + Success = File.AutoWrite(Ext, Cmd.Parameters[1].Value, Cmd.Parameters[2].Value); + if(Success) + Logger.Log("%6u AUTOEXEC EndTag '%s' Extension '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + Cmd.Parameters[1].Value.c_str()); + return Success; + case CMD_DISABLEEXEC: + Success = File.DisableAutoExtension(Cmd.Parameters[0].Value, Cmd.Parameters[1].Value); + if(Success) + Logger.Log("%6u DISABLE EndTag '%s' Extension Function '%s'\n", Cmd.Line, Cmd.Parameters[1].Value.c_str(), + Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_FIXEDLENGTH: + Success = File.SetFixedLength(StringToUInt(Cmd.Parameters[0].Value), StringToUInt(Cmd.Parameters[1].Value)); + if(Success) + Logger.Log("%6u FIXEDLENGTH Length %d PaddingValue %d\n", Cmd.Line, StringToUInt(Cmd.Parameters[0].Value), + StringToUInt(Cmd.Parameters[0].Value)); + else + Logger.ReportError(CurrentLine, "FixedLength used a padding value not in the range of 0-255"); + return Success; + case CMD_WUBCUST: + PtrValue = PtrHandler.GetPtrAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size); + PtrPos = StringToUInt(Cmd.Parameters[1].Value); + if(PtrValue == -1) + return false; + PtrByte = (PtrValue & 0xFF000000) >> 24; + File.WriteP(&PtrByte, 1, 1, PtrPos); + Logger.Log("%6u WUB CustomPointer '%s' ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrByte); + return true; + case CMD_WBBCUST: + PtrValue = PtrHandler.GetPtrAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size); + PtrPos = StringToUInt(Cmd.Parameters[1].Value); + if(PtrValue == -1) + return false; + PtrByte = (PtrValue & 0xFF0000) >> 16; + File.WriteP(&PtrByte, 1, 1, PtrPos); + Logger.Log("%6u WBB CustomPointer '%s' ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrByte); + return true; + case CMD_WHBCUST: + PtrValue = PtrHandler.GetPtrAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size); + PtrPos = StringToUInt(Cmd.Parameters[1].Value); + if(PtrValue == -1) + return false; + PtrByte = (PtrValue & 0xFF00) >> 8; + File.WriteP(&PtrByte, 1, 1, PtrPos); + Logger.Log("%6u WHB CustomPointer '%s' ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrByte); + return true; + case CMD_WLBCUST: + PtrValue = PtrHandler.GetPtrAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size); + PtrPos = StringToUInt(Cmd.Parameters[1].Value); + if(PtrValue == -1) + return false; + PtrByte = PtrValue & 0xFF; + File.WriteP(&PtrByte, 1, 1, PtrPos); + Logger.Log("%6u WLB CustomPointer '%s' ScriptPos $%X PointerPos $%X PointerValue $%02X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrByte); + return true; + case CMD_ENDIANSWAP: + Success = SetEndianSwap(Cmd.Parameters[0].Value); + if(Success) + Logger.Log("%6u ENDIANSWAP '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_STRINGALIGN: + StringAlign = StringToUInt(Cmd.Parameters[0].Value); + Logger.Log("%6u STRINGALIGN '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return true; + case CMD_EMBPTRTABLE: + Success = PtrHandler.CreateEmbPointerTable(Cmd.Parameters[0].Value, File.GetPosT(), StringToUInt(Cmd.Parameters[1].Value), Cmd.Parameters[2].Value); + StartPos = File.GetPosT(); + Size = PtrHandler.GetPtrSize(Cmd.Parameters[2].Value); + if(Success) + { + int TableSize = (Size / 8) * StringToUInt(Cmd.Parameters[1].Value); + if(File.GetMaxWritableBytes() == -1 || (int)File.GetMaxWritableBytes() > TableSize) + { + // Reserve space inside the script for the embedded pointer table + unsigned char Zero = 0; + for(int i = 0; i < TableSize; i++) + File.WriteT(&Zero, 1, 1); + + Logger.Log("%6u EMBPTRTBL Pointer Table '%s' created StartPos $%X PtrCount %dd CustomPointer '%s'\n", Cmd.Line, Cmd.Parameters[0].Value.c_str(), + StartPos, StringToUInt(Cmd.Parameters[1].Value), Cmd.Parameters[2].Value.c_str()); + } + else + Logger.Log("%6u EMBPTRTABLE Failed to allocate due to insufficient space within script\n", Cmd.Line); + } + return Success; + case CMD_WHW: + PtrValue = DefaultPointer.GetHighWord(File.GetPosT()); + if(bSwap) + PtrValue = EndianSwap(PtrValue, 2); + File.WriteP(&PtrValue, 2, 1, StringToUInt(Cmd.Parameters[0].Value)); + Logger.Log("%6u WHW ScriptPos $%X PointerPos $%X PointerValue $%04X\n", Cmd.Line, + File.GetPosT(), StringToUInt(Cmd.Parameters[0].Value), PtrValue); + return true; + case CMD_WHWCUST: + PtrValue = PtrHandler.GetPtrAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size); + PtrPos = StringToUInt(Cmd.Parameters[1].Value); + if(PtrValue == -1) + return false; + PtrValue = (PtrValue & 0xFFFF0000) >> 16; + if(bSwap) + PtrValue = EndianSwap(PtrValue, 2); + File.WriteP(&PtrByte, 2, 1, PtrPos); + Logger.Log("%6u WHW CustomPointer '%s' ScriptPos $%X PointerPos $%X PointerValue $%04X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + case CMD_SETTARGETFILE: + File.CloseFileT(); + Success = File.OpenFileT(Cmd.Parameters[0].Value.c_str()); + IsInJmp = false; + if(!Success) + Logger.ReportError(CurrentLine, "SETTARGETFILE Could not open file '%s'", Cmd.Parameters[0].Value.c_str()); + else + Logger.Log("%6u SETTARGETFILE '%s'", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_SETPTRFILE: + File.CloseFileP(); + Success = File.OpenFileP(Cmd.Parameters[0].Value.c_str()); + if(!Success) + Logger.ReportError(CurrentLine, "SETPTRFILE Could not open file '%s'", Cmd.Parameters[0].Value.c_str()); + else + Logger.Log("%6u SETPTRFILE '%s'", Cmd.Line, Cmd.Parameters[0].Value.c_str()); + return Success; + case CMD_WRITEEMBTBL1: + PtrValue = PtrHandler.GetEmbTableAddress(Cmd.Parameters[0].Value, File.GetPosT(), Size, PtrPos); + if(PtrValue == -1) + { + Logger.ReportError(CurrentLine, "WRITE EMBPTRTBL '%s' could not write due to insufficient space'", Cmd.Parameters[0].Value.c_str()); + return false; + } + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteT(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u WRITE PointerTable '%s' ScriptPos $%X PointerPos $%X PointerValue $%08X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + case CMD_WRITEEMBTBL2: + PtrValue = PtrHandler.GetEmbTableAddress(Cmd.Parameters[0].Value, File.GetPosT(), StringToUInt(Cmd.Parameters[1].Value), Size, PtrPos); + if(PtrValue == -1) + { + Logger.ReportError(CurrentLine, "WRITE EMBPTRTBL '%s' could not write PtrNum '%d' due to out of table range'", Cmd.Parameters[0].Value.c_str(), StringToUInt(Cmd.Parameters[1].Value)); + return false; + } + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteT(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u WRITE PointerTable '%s' ScriptPos $%X PointerPos $%X PointerValue $%08X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + case CMD_WRITETBL2: + PtrValue = PtrHandler.GetTableAddress(Cmd.Parameters[0].Value, File.GetPosT(), StringToUInt(Cmd.Parameters[1].Value), Size, PtrPos); + if(PtrValue == -1) + return false; + if(bSwap) + PtrValue = EndianSwap(PtrValue, Size/8); + File.WriteP(&PtrValue, Size/8, 1, PtrPos); + Logger.Log("%6u WRITE PointerTable '%s' ScriptPos $%X PointerPos $%X PointerValue $%08X\n", Cmd.Line, + Cmd.Parameters[0].Value.c_str(), File.GetPosT(), PtrPos, PtrValue); + return true; + default: + Logger.BugReport(__LINE__, __FILE__, "Bad Cmd #%u", Cmd.Function); + return false; + } +} + +bool AtlasCore::ActivateTable(std::string& TableName) +{ + Table* Tbl = (Table*)(VarMap.GetVar(TableName))->GetData(); + if(Tbl == NULL) + { + ostringstream ErrorStr; + ErrorStr << "Uninitialized variable " << TableName << " used"; + Logger.ReportError(CurrentLine, "Uninitialized variable '%s' used", TableName); + return false; + } + else + { + File.SetTable(Tbl); + return true; + } +} + +bool AtlasCore::AddTable(Command& Cmd) +{ + Table* Tbl; + GenericVariable* Var; + Tbl = (Table*)(VarMap.GetVar(Cmd.Parameters[1].Value))->GetData(); + if(!LoadTable(Cmd.Parameters[0].Value, &Tbl)) + return false; + + Var = new GenericVariable(Tbl, P_TABLE); + VarMap.SetVar(Cmd.Parameters[1].Value, Var); + return true; +} + +bool AtlasCore::LoadTable(std::string& FileName, Table** Tbl) +{ + if(*Tbl != NULL) // Initialized already, overwrite + { + delete Tbl; + Tbl = NULL; + } + *Tbl = new Table; + int Result = (*Tbl)->OpenTable(FileName.c_str()); + + ostringstream ErrorStr; + switch(Result) + { + case TBL_OK: + break; + case TBL_PARSE_ERROR: + Logger.ReportError(CurrentLine, "The table file '%s' is incorrectly formatted", FileName.c_str()); + return false; + case TBL_OPEN_ERROR: + Logger.ReportError(CurrentLine, "The table file '%s' could not be opened", FileName.c_str()); + return false; + } + + return true; +} + + +void AtlasCore::CreateContext(AtlasContext** Context) +{ + if(*Context == NULL) + *Context = new AtlasContext; + (*Context)->CurrentLine = CurrentLine; + (*Context)->ScriptPos = File.GetPosT(); + (*Context)->ScriptRemaining = File.GetMaxWritableBytes(); + (*Context)->Target = File.GetFileT(); + File.GetScriptBuf((*Context)->StringTable); + (*Context)->PointerPosition = 0; + (*Context)->PointerSize = 0; + (*Context)->PointerValue = 0; +} + + +bool AtlasCore::ExecuteExtension(std::string& ExtId, std::string& FunctionName, + AtlasContext** Context) +{ + bool Success = false; + CreateContext(Context); + unsigned int DllRet = Extensions.ExecuteExtension(ExtId, FunctionName, Context); + if(DllRet == -1) + { + DllRet = NO_ACTION; + Success = false; + } + if(DllRet > MAX_RETURN_VAL) + { + Logger.ReportWarning(CurrentLine, "Extension returned invalid value %u", DllRet); + Success = false;; + } + if(DllRet & REPLACE_TEXT) + { + File.SetScriptBuf((*Context)->StringTable); + Logger.Log("%6u EXECEXT REPLACE_TEXT\n", CurrentLine); + Success = true; + } + if(DllRet & WRITE_POINTER) + { + unsigned int Size = (*Context)->PointerSize; + if(Size == 8 || Size == 16 || Size == 24 || Size == 32) + { + Size /= 8; + File.WriteP(&(*Context)->PointerValue, (*Context)->PointerSize, 1, (*Context)->PointerPosition); + Logger.Log("%6u EXECEXT WRITE_POINTER ScriptPos $%X PointerPos $%X PointerValue $%06X\n", CurrentLine, + File.GetPosT(), (*Context)->PointerPosition, (*Context)->PointerValue); + Success = true; + Stats.IncExtPointerWrites(); + } + else + { + Logger.ReportError(CurrentLine, "EXTEXEC Extension function '%s' returning WRITE_POINTER has an unsupported PointerSize field", FunctionName); + Success = false; + } + } + + delete (*Context); + *Context = NULL; + Logger.Log("%6u EXTEXEC Executed function '%s' from '%s' successfully\n", CurrentLine, FunctionName.c_str(), ExtId.c_str()); + return true; +} + +bool AtlasCore::ExecuteExtensionFunction(ExtensionFunction Func, AtlasContext** Context) +{ + unsigned int DllRet = Func(Context); + bool Success = false; + if(DllRet > MAX_RETURN_VAL) + { + Logger.ReportWarning(CurrentLine, "Extension returned invalid value %u", DllRet); + Success = false; + } + + if(DllRet & REPLACE_TEXT) + { + File.SetScriptBuf((*Context)->StringTable); + Logger.Log("%6u EXECEXT REPLACE_TEXT\n", CurrentLine); + Success = true; + } + if(DllRet & WRITE_POINTER) + { + unsigned int Size = (*Context)->PointerSize; + if(Size == 8 || Size == 16 || Size == 24 || Size == 32) + { + Size /= 8; + File.WriteP(&(*Context)->PointerValue, (*Context)->PointerSize, 1, (*Context)->PointerPosition); + Logger.Log("%6u EXTEXEC WRITE_POINTER ScriptPos $%X PointerPos $%X PointerValue $%06X\n", CurrentLine, + File.GetPosT(), (*Context)->PointerPosition, (*Context)->PointerValue); + Success = true; + Stats.IncExtPointerWrites(); + } + else + { + Logger.ReportError(CurrentLine, "EXTEXEC Extension function returning WRITE_POINTER has an unsupported PointerSize field"); + Success = false; + } + } + + delete (*Context); + *Context = NULL; + return true; +} + +bool AtlasCore::SetEndianSwap(string& Swap) +{ + if(Swap == "TRUE") + bSwap = 1; + else if(Swap == "FALSE") + bSwap = 0; + else + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// StringToUInt() - Converts a $ABCD string from hexadecimal radix else decimal +// Status - Working +//----------------------------------------------------------------------------- + +unsigned int StringToUInt(std::string& NumberString) +{ + unsigned int offset = 0; + + if(NumberString[0] == '$') + { + offset = strtoul(NumberString.substr(1, NumberString.length()).c_str(), NULL, 16); + } + else + offset = strtoul(NumberString.c_str(), NULL, 10); + + return offset; +} + +//----------------------------------------------------------------------------- +// StringToInt64() - Converts a string to int64 +// Status - Works+Fixed +//----------------------------------------------------------------------------- + +__int64 StringToInt64(string& NumberString) +{ + __int64 Num = 0; + bool bNeg = false; + size_t Pos = 0; + unsigned __int64 Mult; + + if(NumberString[Pos] == '$') // hex + { + Pos++; + if(NumberString[Pos] == '-') + { + bNeg = true; + Pos++; + } + size_t i = NumberString.length() - 1; + Num += GetHexDigit(NumberString[i]); + i--; + Mult = 16; + for(i; i >= Pos; i--, Mult*=16) + Num += Mult * GetHexDigit(NumberString[i]); + } + else // dec + { + if(NumberString[Pos] == '-') + { + bNeg = true; + Pos++; + } + size_t i = NumberString.length() - 1; + Num += GetHexDigit(NumberString[i]); + if(i != 0) + { + i--; + Mult = 10; + for(i; i > Pos; i--, Mult*=10) + Num += Mult * (NumberString[i] - '0'); + Num += Mult * (NumberString[i] - '0'); // prevent underflow of i + } + } + + if(bNeg) + Num = -Num; + return Num; +} + +unsigned int GetHexDigit(char digit) +{ + switch(digit) + { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'A': case 'a': + return 10; + case 'B': case 'b': + return 11; + case 'C': case 'c': + return 12; + case 'D': case 'd': + return 13; + case 'E': case 'e': + return 14; + case 'F': case 'f': + return 15; + default: + return 0; + } +} + +unsigned int EndianSwap(unsigned int Num, int Size) +{ + unsigned int a = 0; + switch(Size) + { + case 1: + return Num; + case 2: + a = (Num & 0xFF00) >> 8; + a |= (Num & 0x00FF) << 8; + return a; + case 3: + a = (Num & 0xFF) << 16; + a |= (Num & 0xFF00); + a |= (Num & 0xFF0000) >> 16; + return a; + case 4: + a = (Num & 0xFF) << 24; + a |= (Num & 0xFF00) << 8; + a |= (Num & 0xFF0000) >> 8; + a |= (Num & 0xFF000000) >> 24; + return a; + } + + return -1; +} \ No newline at end of file diff --git a/AtlasCore.h b/AtlasCore.h new file mode 100644 index 0000000..a391e11 --- /dev/null +++ b/AtlasCore.h @@ -0,0 +1,76 @@ +#pragma once + +#include "stdafx.h" +#include +#include +#include +#include +#include +#include "Table.h" +#include "GenericVariable.h" +#include "AtlasParser.h" +#include "AtlasFile.h" +#include "Pointer.h" +#include "AtlasLogger.h" +#include "AtlasStats.h" +#include "PointerHandler.h" +#include "AtlasExtension.h" +using namespace std; + +//----------------------------------------------------------------------------- +// AtlasCore Functionality +//----------------------------------------------------------------------------- + +class AtlasCore +{ +public: + AtlasCore(); + ~AtlasCore(); + + bool Insert(const char* RomFileName, const char* ScriptFileName); + void SetDebugging(FILE* output); + void CreateContext(AtlasContext** Context); + bool ExecuteExtension(string& ExtId, string& FunctionName, AtlasContext** Context); + bool ExecuteExtensionFunction(ExtensionFunction Func, AtlasContext** Context); + unsigned int GetHeaderSize(); + +private: + AtlasParser Parser; + AtlasFile File; + //AtlasFile GameFile; + //AtlasFile PtrFile; + VariableMap VarMap; // Variable Map for identifiers + PointerHandler PtrHandler; + Pointer DefaultPointer; + EmbeddedPointerHandler EmbPtrs; + InsertionStatistics Total; + ExtensionManager Extensions; + + bool IsInJmp; + unsigned int HeaderSize; + + bool ExecuteCommand(Command& Cmd); + + void PrintSummary(const char* Title, unsigned int TimeCompleted); + void PrintStatistics(); + void PrintStatisticsBlock(const char* Title, InsertionStatistics& Stats); + void PrintUnwrittenPointers(); + + bool AddTable(Command& Cmd); + bool ActivateTable(string& TableName); + bool LoadTable(string& FileName, Table** Tbl); + bool SetEndianSwap(string& Swap); +}; + +// Global Core variables +extern unsigned int CurrentLine; +extern int bSwap; +extern int StringAlign; +extern int MaxEmbPtr; +extern AtlasCore Atlas; + +// Misc functions +inline unsigned int StringToUInt(std::string& NumberString); +__int64 StringToInt64(std::string& NumberString); +unsigned int GetHexDigit(char digit); +unsigned int EndianSwap(unsigned int Num, int Size); \ No newline at end of file diff --git a/AtlasExtension.cpp b/AtlasExtension.cpp new file mode 100644 index 0000000..093a66c --- /dev/null +++ b/AtlasExtension.cpp @@ -0,0 +1,119 @@ +#include "stdafx.h" +#include +#include +#include "AtlasExtension.h" +#include "AtlasLogger.h" +#include "AtlasCore.h" + +using namespace std; + +ExtensionManager::ExtensionManager(VariableMap* Map) +{ + VarMap = Map; +} + +bool ExtensionManager::LoadExtension(string& ExtId, string& ExtensionFile) +{ + // Use file extension to pick which derived AtlasExtension to use + AtlasExtension* Ext; + size_t Pos = ExtensionFile.find_last_of('.'); + if(Pos == string::npos || Pos >= ExtensionFile.length()-1) + return false; + else if(ExtensionFile.substr(Pos+1, ExtensionFile.length()-1) == "dll") // dll + { + } + else + { + Logger.ReportError(CurrentLine, "Unsupported file format used in LOADEXT"); + return false; + } + + Ext = (AtlasExtension*)VarMap->GetVar(ExtId)->GetData(); + if(Ext != NULL) + { + Logger.ReportError(CurrentLine, "%s has alrady been initialized with LOADEXT", ExtId.c_str()); + delete Ext; + return false; + } + Ext = NULL; + Ext = new AtlasExtension; + if(!Ext->LoadExtension(ExtensionFile)) + { + Logger.ReportError(CurrentLine, "%s could not be loaded", ExtensionFile.c_str()); + delete Ext; + return false; + } + + VarMap->SetVarData(ExtId, Ext, P_EXTENSION); + return true; +} + +unsigned int ExtensionManager::ExecuteExtension(string& ExtId, string& FunctionName, AtlasContext** Context) +{ + AtlasExtension* Ext; + Ext = (AtlasExtension*)VarMap->GetVar(ExtId)->GetData(); + if(Ext == NULL) + { + Logger.ReportError(CurrentLine, "%s has not been initialized by LOADEXT", ExtId.c_str()); + return -1; + } + if(!Ext->IsLoaded()) + { + ReportBug("Extension not loaded but initialized in ExtensionManager::ExecuteExtension"); + return -1; + } + + ExtensionFunction Func = Ext->GetFunction(FunctionName); + if(Func == NULL) + { + Logger.ReportError(CurrentLine, "Function %s was not found in the extension file", FunctionName.c_str()); + return -1; + } + + unsigned int Res = Func(Context); + if(Res > MAX_RETURN_VAL) + { + Logger.ReportWarning(CurrentLine, "Extension returned invalid value %u", Res); + Res = NO_ACTION; + } + + return Res; +} + +AtlasExtension::AtlasExtension() : Extension(NULL) +{ +} + +AtlasExtension::~AtlasExtension() +{ + if(Extension) + FreeLibrary(Extension); +} + +bool AtlasExtension::LoadExtension(string& ExtensionName) +{ + Extension = LoadLibraryA(ExtensionName.c_str()); + + if(Extension) + return true; + else + return false; +} + +bool AtlasExtension::IsLoaded() +{ + if(Extension) + return true; + else + return false; +} + +ExtensionFunction AtlasExtension::GetFunction(string& FunctionName) +{ + if(NULL == Extension) + return NULL; + + ExtensionFunction func = (ExtensionFunction)GetProcAddress(Extension, FunctionName.c_str()); + + return func; +} \ No newline at end of file diff --git a/AtlasExtension.h b/AtlasExtension.h new file mode 100644 index 0000000..cd2f82a --- /dev/null +++ b/AtlasExtension.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include "Table.h" +#include "GenericVariable.h" + +const unsigned int MAX_RETURN_VAL = 3; + +const unsigned int NO_ACTION = 0; +const unsigned int REPLACE_TEXT = 1; +const unsigned int WRITE_POINTER = 2; + +typedef struct AtlasContext +{ + unsigned int CurrentLine; + + std::list StringTable; + FILE* Target; + + unsigned int ScriptPos; + unsigned int ScriptRemaining; + + unsigned int PointerValue; + unsigned int PointerPosition; + unsigned int PointerSize; +} AtlasContext; + +typedef unsigned int (*ExtensionFunction) (AtlasContext** Context); + +class ExtensionManager +{ +public: + ExtensionManager(VariableMap* Map); + bool LoadExtension(std::string& ExtId, std::string& ExtensionFile); + unsigned int ExecuteExtension(std::string& ExtId, std::string& FunctionName, AtlasContext** Context); +private: + VariableMap* VarMap; +}; + +class AtlasExtension +{ +public: + AtlasExtension(); + ~AtlasExtension(); + + bool LoadExtension(std::string& ExtensionName); + bool IsLoaded(); + ExtensionFunction GetFunction(std::string& FunctionName); + +private: + HMODULE Extension; +}; \ No newline at end of file diff --git a/AtlasFile.cpp b/AtlasFile.cpp new file mode 100644 index 0000000..ea45bca --- /dev/null +++ b/AtlasFile.cpp @@ -0,0 +1,480 @@ +#include "stdafx.h" +#include +#include +#include "AtlasFile.h" +#include "Table.h" +#include "AtlasLogger.h" +#include "AtlasStats.h" +#include "AtlasCore.h" +#include "AtlasExtension.h" + +using namespace std; + +AtlasFile::AtlasFile() +{ + tfile = NULL; + pfile = NULL; + + MaxScriptPos = -1; + ActiveTbl = NULL; + BytesInserted = 0; + TotalBytes = 0; + TotalBytesSkipped = 0; + + StrType = STR_ENDTERM; + PascalLength = 1; + + FixedPadValue = 0; + StringLength = 0; +} + +AtlasFile::~AtlasFile() +{ + if(tfile != NULL) + fclose(tfile); + if(pfile != NULL) + fclose(pfile); +} + +bool AtlasFile::OpenFileT(const char* FileName) +{ + // Reset vars for new file + MaxScriptPos = -1; + + tfile = fopen(FileName, "r+b"); + return tfile != NULL; +} + +bool AtlasFile::OpenFileP(const char* Filename) +{ + pfile = fopen(Filename, "r+b"); + return pfile != NULL; +} + +void AtlasFile::CloseFileT() +{ + if(tfile != NULL) + fclose(tfile); +} + +void AtlasFile::CloseFileP() +{ + if(pfile != NULL) + fclose(pfile); +} + +void AtlasFile::WriteP(const void* Data, const unsigned int Size, + const unsigned int DataCount, const unsigned int Pos) +{ + unsigned int OldPos = ftell(pfile); + fseek(pfile, Pos, SEEK_SET); + fwrite(Data, Size, DataCount, pfile); + fseek(pfile, OldPos, SEEK_SET); +} + +void AtlasFile::WriteT(const void* Data, const unsigned int Size, + const unsigned int DataCount, const unsigned int Pos) +{ + unsigned int OldPos = ftell(tfile); + fseek(tfile, Pos, SEEK_SET); + fwrite(Data, Size, DataCount, tfile); + fseek(tfile, OldPos, SEEK_SET); +} + +// Does not revert file offset +void AtlasFile::WriteT(const void* Data, const unsigned int Size, const unsigned int DataCount) +{ + fwrite(Data, Size, DataCount, tfile); +} + +unsigned int AtlasFile::GetPosT() +{ + return ftell(tfile); +} + +FILE* AtlasFile::GetFileT() +{ + return tfile; +} + +FILE* AtlasFile::GetFileP() +{ + return pfile; +} + +void AtlasFile::GetScriptBuf(std::list& Strings) +{ + Strings = ActiveTbl->StringTable; +} + +void AtlasFile::SetScriptBuf(std::list& Strings) +{ + ActiveTbl->StringTable = Strings; +} + +unsigned int AtlasFile::GetStringType() +{ + return StrType; +} + +void AtlasFile::MoveT(const unsigned int Pos, const unsigned int ScriptBound) +{ + if(tfile) + fseek(tfile, Pos, SEEK_SET); + MaxScriptPos = ScriptBound; +} + +void AtlasFile::MoveT(const unsigned int Pos) +{ + if(tfile) + fseek(tfile, Pos, SEEK_SET); +} + +void AtlasFile::SetTable(Table* Tbl) +{ + FlushText(); + ActiveTbl = Tbl; +} + +bool AtlasFile::SetStringType(std::string& Type) +{ + for(int i = 0; i < StringTypeCount; i++) + { + if(Type == StringTypes[i]) + { + StrType = i; + return true; + } + } + + return false; +} + +bool AtlasFile::SetPascalLength(unsigned int Length) +{ + switch(Length) + { + case 1: case 2: case 3: case 4: + PascalLength = Length; + break; + default: + return false; + } + return true; +} + +bool AtlasFile::SetFixedLength(unsigned int StrLength, unsigned int PadValue) +{ + if(PadValue > 65536) + return false; + + StringLength = StrLength; + FixedPadValue = (unsigned char)PadValue; + + return true; +} + +bool AtlasFile::DisableWrite(string& EndTag, bool isPointerTable) +{ + if(isPointerTable) + { + std::map::iterator it; + it = TblAutoWrite.find(EndTag); + if(it == TblAutoWrite.end()) + return false; + TblAutoWrite.erase(it); + } + else + { + std::map::iterator it; + it = ListAutoWrite.find(EndTag); + if(it == ListAutoWrite.end()) + return false; + ListAutoWrite.erase(it); + } + return true; +} + +bool AtlasFile::DisableAutoExtension(string& FuncName, string& EndTag) +{ + std::map::iterator it; + it = ExtAutoWrite.find(EndTag); + if(it == ExtAutoWrite.end()) + { + Logger.ReportError(CurrentLine, "'%s' has not been defined as an autoexec end token", EndTag); + return false; + } + ExtAutoWrite.erase(it); + return true; +} + +bool AtlasFile::AutoWrite(PointerList* List, string& EndTag) +{ + bool EndTokenFound = false; + for(size_t i = 0; i < ActiveTbl->EndTokens.size(); i++) + { + if(EndTag == ActiveTbl->EndTokens[i]) + EndTokenFound = true; + } + if(EndTokenFound) + ListAutoWrite.insert(map::value_type(EndTag, List)); + return EndTokenFound; +} + +bool AtlasFile::AutoWrite(PointerTable* Tbl, string& EndTag) +{ + bool EndTokenFound = false; + for(size_t i = 0; i < ActiveTbl->EndTokens.size(); i++) + { + if(EndTag == ActiveTbl->EndTokens[i]) + EndTokenFound = true; + } + if(EndTokenFound) + TblAutoWrite.insert(map::value_type(EndTag, Tbl)); + return EndTokenFound; +} + +bool AtlasFile::AutoWrite(AtlasExtension* Ext, string& FuncName, string& EndTag) +{ + bool EndTokenFound = false; + ExtensionFunction Func; + + for(size_t i = 0; i < ActiveTbl->EndTokens.size(); i++) + { + if(EndTag == ActiveTbl->EndTokens[i]) + EndTokenFound = true; + } + Func = Ext->GetFunction(FuncName); + if(!EndTokenFound) + { + Logger.ReportError(CurrentLine, "'%s' has not been defined as an end token in the active table", EndTag); + return false; + } + if(Func == NULL) + { + Logger.ReportError(CurrentLine, "Function 's' could not be found in the extension", FuncName); + return false; + } + + ExtAutoWrite.insert(map::value_type(EndTag, Func)); + return true; +} + +bool AtlasFile::InsertText(string& Text, unsigned int Line) +{ + if(ActiveTbl == NULL) + { + // Add error + printf("No active table loaded\n"); + return false; + } + unsigned int BadCharPos = 0; + if(ActiveTbl->EncodeStream(Text, BadCharPos) == -1) // Failed insertion, char missing from tbl + { + // Add error + Logger.ReportError(Line, "Character '%c' missing from table. String = '%s'", Text[BadCharPos], Text.c_str()); + return false; + } + return true; +} + +bool AtlasFile::FlushText() +{ + static unsigned int Size; + static unsigned int Address; + static unsigned int WritePos; + AtlasContext* Context = NULL; + + if(ActiveTbl == NULL) + return false; + + if(ActiveTbl->StringTable.empty()) + return true; + + // For every string, check autowrite/autoexec (list, table, and extension) + // Automatically write pointer if appropriate end string is found, then write the text string to ROM + ListTxtStringIt j = ActiveTbl->TxtStringTable.begin(); + for(ListTblStringIt i = ActiveTbl->StringTable.begin(); + i != ActiveTbl->StringTable.end(); i++, j++) + { + // #ALIGNSTRING, must do before autowrite writes a pointer + AlignString(); + + if(!i->EndToken.empty()) // If there's an end token, check for autowrite + { + ListIt = ListAutoWrite.find(i->EndToken); + if(ListIt != ListAutoWrite.end()) + { + Address = ListIt->second->GetAddress(GetPosT(), Size, WritePos); + if(Address != -1) + { + if(bSwap) + Address = EndianSwap(Address, Size/8); + WriteP(&Address, Size/8, 1, WritePos); + Logger.Log("%6u AUTOWRITE Invoked ScriptPos $%X PointerPos $%X PointerValue $%08X\n", CurrentLine, + GetPosT(), WritePos, Address); + Stats.IncAutoPointerWrites(); + } + else + Stats.IncFailedListWrites(); + } + TblIt = TblAutoWrite.find(i->EndToken); + if(TblIt != TblAutoWrite.end()) + { + Address = TblIt->second->GetAddress(GetPosT(), Size, WritePos); + if(bSwap) + Address = EndianSwap(Address, Size/8); + WriteP(&Address, Size/8, 1, WritePos); + Logger.Log("%6u AUTOWRITE Invoked ScriptPos $%X PointerPos $%X PointerValue $%08X\n", CurrentLine, + GetPosT(), WritePos, Address); + Stats.IncAutoPointerWrites(); + } + ExtIt = ExtAutoWrite.find(i->EndToken); + if(ExtIt != ExtAutoWrite.end()) + { + Atlas.CreateContext(&Context); + bool Success = Atlas.ExecuteExtensionFunction(ExtIt->second, &Context); + delete Context; + Context = NULL; + if(!Success) + { + Logger.ReportError(CurrentLine, "Autoexecuting extension with end token '%s' failed", i->EndToken); + return false; + } + else + Logger.Log("%6u AUTOEXEC Invoked ScriptPos $%X PointerPos $%X PointerValue $%08X\n", CurrentLine, + GetPosT(), WritePos, Address); + } + } + + CurTextString = j->Text; + WriteString(i->Text); + Logger.Log("%s\n", CurTextString.c_str()); + CurTextString.clear(); + } + + ActiveTbl->StringTable.clear(); + + return true; +} + +inline bool AtlasFile::WriteString(string& text) +{ + unsigned int StringSize = 0; + int PadBytes; + + // Write string type + if(StrType == STR_ENDTERM) + StringSize = WriteNullString(text); + else if(StrType == STR_PASCAL) + StringSize = WritePascalString(text); + else + return false; + + // #FIXEDLENGTH padding + if(StringLength != 0) + { + PadBytes = StringLength - StringSize; + if(PadBytes > 0) + { + for(int i = 0; i < PadBytes; i++) + fputc((int)FixedPadValue, tfile); + BytesInserted += PadBytes; + } + } + + return true; +} + +inline unsigned int AtlasFile::WriteNullString(string& text) +{ + unsigned int size = (unsigned int)text.length(); + unsigned int maxwrite = GetMaxWritableBytes(); + + Stats.AddScriptBytes(size); + + // Truncate string if it overflows ROM bounds + if(maxwrite < size) + { + int overflowbytes = size - maxwrite; + TotalBytesSkipped += overflowbytes; + size = maxwrite; + } + + // Truncate string if it's too long for a fixed length string + if(size > StringLength && StringLength != 0) + { + TotalBytesSkipped += (size - StringLength); + size = StringLength; + printf("Changed string length for %s to %d at %X\n", CurTextString.c_str(), StringLength, GetPosT()); + } + + fwrite(text.data(), 1, size, tfile); + BytesInserted += size; + + return size; +} + +inline unsigned int AtlasFile::WritePascalString(string& text) +{ + unsigned int size = (unsigned int)text.length(); + unsigned int maxwrite = GetMaxWritableBytes(); + + Stats.AddScriptBytes(size+PascalLength); + + // Truncate string if it overflows ROM bounds + if(PascalLength > maxwrite) // PascalLength doesn't even fit + goto nowrite; + if(maxwrite < size + PascalLength) // PascalLength and maybe partial string fits + { + int overflowbytes = (size+PascalLength) - maxwrite; + TotalBytesSkipped += overflowbytes; + size = maxwrite - PascalLength; + } + + // Truncate string if it's too long for a fixed length string + if(size > StringLength && StringLength != 0) + { + TotalBytesSkipped += (size - StringLength); + size = StringLength - PascalLength; + printf("Changed string length for %s to %d at %X\n", text.c_str(), StringLength, GetPosT()); + } + + int swaplen = size; + if(bSwap) + swaplen = EndianSwap(size, PascalLength); + + fwrite(&swaplen, PascalLength, 1, tfile); + fwrite(text.c_str(), 1, size, tfile); + BytesInserted += size+PascalLength; + +nowrite: + return size+PascalLength; +} + +inline void AtlasFile::AlignString() +{ + if(StringAlign != 0) // String align turned on + { + int curoffset = GetPosT() - Atlas.GetHeaderSize(); + int PadBytes = StringAlign - (curoffset % StringAlign); + if(PadBytes == StringAlign) + PadBytes = 0; + if(PadBytes > 0) + { + for(int i = 0; i < PadBytes; i++) + fputc(0, tfile); + BytesInserted += PadBytes; + } + } +} + +inline unsigned int AtlasFile::GetMaxWritableBytes() +{ + if(MaxScriptPos == -1) + return -1; + unsigned int CurPos = ftell(tfile); + if(CurPos > MaxScriptPos) + return 0; + return MaxScriptPos - CurPos + 1; +} \ No newline at end of file diff --git a/AtlasFile.h b/AtlasFile.h new file mode 100644 index 0000000..5f3a19c --- /dev/null +++ b/AtlasFile.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include +#include "Table.h" +#include "PointerHandler.h" +#include "AtlasExtension.h" +using namespace std; + +static const unsigned int STR_ENDTERM = 0; +static const unsigned int STR_PASCAL = 1; +static const unsigned int StringTypeCount = 2; +static const char* StringTypes[StringTypeCount] = { "ENDTERM", "PASCAL" }; + +class AtlasFile +{ +public: + AtlasFile(); + ~AtlasFile(); + + bool AutoWrite(PointerList* List, string& EndTag); + bool AutoWrite(PointerTable* Tbl, string& EndTag); + bool AutoWrite(AtlasExtension* Ext, string& FuncName, string& EndTag); + bool DisableAutoExtension(string& FuncName, string& EndTag); + bool DisableWrite(string& EndTag, bool isPointerTable); + + // File functions. T for text file, P for pointer file + bool OpenFileT(const char* FileName); + bool OpenFileP(const char* FileName); + void CloseFileT(); + void CloseFileP(); + void MoveT(const unsigned int Pos, const unsigned int ScriptBound); + void MoveT(const unsigned int Pos); + void WriteP(const void* Data, const unsigned int Size, const unsigned int DataCount, const unsigned int Pos); + void WriteT(const void* Data, const unsigned int Size, const unsigned int DataCount, const unsigned int Pos); + void WriteT(const void* Data, const unsigned int Size, const unsigned int DataCount); + unsigned int GetPosT(); + + unsigned int GetMaxBound() { return MaxScriptPos; } + unsigned int GetBytesInserted() { return BytesInserted; } + unsigned int GetBytesOverflowed() { return TotalBytesSkipped; } + + void SetTable(Table* Tbl); + bool SetStringType(string& Type); + bool SetPascalLength(unsigned int Length); + bool SetFixedLength(unsigned int StrLength, unsigned int PadValue); + + bool InsertText(string& Text, unsigned int Line); + bool FlushText(); + + inline unsigned int GetMaxWritableBytes(); + FILE* GetFileT(); + FILE* GetFileP(); + void GetScriptBuf(list& Strings); + void SetScriptBuf(list& Strings); + unsigned int GetStringType(); + +private: + FILE* tfile; // Target file for script + FILE* pfile; // Pointer write file + Table* ActiveTbl; + PointerHandler* PtrHandler; + map ListAutoWrite; + map TblAutoWrite; + map ExtAutoWrite; + map::iterator ListIt; + map::iterator TblIt; + map::iterator ExtIt; + + inline bool WriteString(string& text); + inline unsigned int WriteNullString(string& text); + inline unsigned int WritePascalString(string& text); + inline void AlignString(); + + unsigned int MaxScriptPos; + unsigned int BytesInserted; + unsigned int TotalBytesSkipped; + unsigned int TotalBytes; + + unsigned int StrType; + unsigned int PascalLength; + + unsigned int StringLength; + unsigned char FixedPadValue; + string CurTextString; // Used to keep track and report text that overflows the FIXEDLENGTH value +}; \ No newline at end of file diff --git a/AtlasLogger.cpp b/AtlasLogger.cpp new file mode 100644 index 0000000..c02eca4 --- /dev/null +++ b/AtlasLogger.cpp @@ -0,0 +1,91 @@ +#include "stdafx.h" +#include +#include +#include +#include +#include "AtlasLogger.h" + +using namespace std; + +AtlasLogger Logger; + +AtlasLogger::AtlasLogger() +{ + output = NULL; + isLogging = true; + Errors.clear(); +} + +AtlasLogger::~AtlasLogger() +{ + if(output != NULL && output != stdout) + fclose(output); +} + +void AtlasLogger::ReportError(unsigned int ScriptLine, const char* FormatStr ...) +{ + AtlasError Error; + Error.Severity = FATALERROR; + Error.LineNumber = ScriptLine; + + va_list arglist; + va_start(arglist, FormatStr); + int length = _vsnprintf(buf, BufSize, FormatStr, arglist); + va_end(arglist); + + Error.Error.assign(buf, length); + + Errors.push_back(Error); +} + +void AtlasLogger::ReportWarning(unsigned int ScriptLine, const char* FormatStr ...) +{ + AtlasError Error; + Error.Severity = WARNING; + Error.LineNumber = ScriptLine; + + va_list arglist; + va_start(arglist, FormatStr); + int length = _vsnprintf(buf, BufSize, FormatStr, arglist); + va_end(arglist); + + Error.Error.assign(buf, length); + + Errors.push_back(Error); +} + +void AtlasLogger::Log(const char* FormatStr ...) +{ + if(isLogging && output) + { + va_list arglist; + va_start(arglist, FormatStr); + vfprintf(output, FormatStr, arglist); + va_end(arglist); + } +} + +void AtlasLogger::SetLogStatus(bool LoggingOn) +{ + isLogging = LoggingOn; +} + +void AtlasLogger::SetLogSource(FILE* OutputSource) +{ + output = OutputSource; +} + +void AtlasLogger::BugReportLine(unsigned int Line, const char* Filename, const char* Msg) +{ + fprintf(stderr, "Bug: %s Line %u in source file %s\n", Msg, Line, Filename); +} + +void AtlasLogger::BugReport(unsigned int Line, const char* Filename, const char* FormatStr, ...) +{ + fprintf(stderr, "Bug: "); + va_list arglist; + va_start(arglist, FormatStr); + vfprintf(stderr, FormatStr, arglist); + va_end(arglist); + fprintf(stderr, " Line %u in source file %s\n", Line, Filename); +} \ No newline at end of file diff --git a/AtlasLogger.h b/AtlasLogger.h new file mode 100644 index 0000000..6b51a98 --- /dev/null +++ b/AtlasLogger.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +enum ErrorSeverity { FATALERROR = 0, WARNING }; + +typedef struct AtlasError +{ + std::string Error; + ErrorSeverity Severity; + unsigned int LineNumber; +} AtlasError; + +typedef std::list::iterator ListErrorIt; + +class AtlasLogger +{ +public: + AtlasLogger(); + ~AtlasLogger(); + + void ReportError(unsigned int ScriptLine, const char* FormatStr ...); + void ReportWarning(unsigned int ScriptLine, const char* FormatStr ...); + void Log(const char* FormatStr ...); + void SetLogSource(FILE* OutputSource); + void SetLogStatus(bool LoggingOn); + void BugReportLine(unsigned int Line, const char* Filename, const char* Msg); + void BugReport(unsigned int Line, const char* Filename, const char* FormatStr ...); + + std::list Errors; + +private: + FILE* output; + static const unsigned int BufSize = 512; + char buf[BufSize]; + bool isLogging; +}; + +extern AtlasLogger Logger; + +#define ReportBug(msg) Logger.BugReport(__LINE__, __FILE__, msg) \ No newline at end of file diff --git a/AtlasMain.cpp b/AtlasMain.cpp new file mode 100644 index 0000000..767bcc1 --- /dev/null +++ b/AtlasMain.cpp @@ -0,0 +1,47 @@ +// Atlas main + +#include "stdafx.h" +#include +#include +#include +#include "AtlasCore.h" + +using namespace std; + +int _tmain(int argc, _TCHAR* argv[]) +{ + clock_t StartTime, EndTime, ElapsedTime; + int argoff = 0; + + Logger.SetLogStatus(false); + StartTime = clock(); + + printf("Atlas 1.11 by Klarth\n\n"); + if(argc != 3 && argc != 5) + { + printf("Usage: %s [switches] ROM.ext Script.txt\n", argv[0]); + printf("Switches: -d filename or -d stdout (debugging)\n"); + printf("Arguments in brackets are optional\n"); + return 1; + } + + if(strcmp("-d", argv[1]) == 0) + { + if(strcmp("stdout", argv[2]) == 0) + Atlas.SetDebugging(stdout); + else + Atlas.SetDebugging(fopen(argv[2], "w")); + argoff+=2; + } + + if(!Atlas.Insert(argv[1+argoff], argv[2+argoff])) + printf("Insertion failed\n\n"); + + EndTime = clock(); + + ElapsedTime = EndTime - StartTime; + + printf("Execution time: %u msecs\n", (unsigned int)ElapsedTime); + + return 0; +} \ No newline at end of file diff --git a/AtlasParser.cpp b/AtlasParser.cpp new file mode 100644 index 0000000..363cba2 --- /dev/null +++ b/AtlasParser.cpp @@ -0,0 +1,362 @@ +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include "AtlasTypes.h" +#include "AtlasParser.h" +#include "GenericVariable.h" +#include "AtlasLogger.h" +#include "AtlasCore.h" + +using namespace std; + +AtlasParser::AtlasParser(VariableMap* Map) +{ + VarMap = Map; + + // Initialize the function lookup map + for(unsigned int i=0; i < CommandCount; i++) + CmdMap.insert(multimap::value_type(CommandStrings[i], i)); + + for(unsigned int i=0; i < TypeCount; i++) + TypeMap.insert(multimap::value_type(TypeStrings[i], i)); + + CurrentLine = 0; + CurBlock.StartLine = -1; +} + +AtlasParser::~AtlasParser() +{ +} + +bool AtlasParser::ParseFile(ifstream& infile) +{ + list text; + unsigned char utfheader[4]; + + string line; + + // Detect UTF-8 header + if(infile.peek() == 0xEF) + { + infile.read((char*)utfheader, 3); + if(utfheader[0] != 0xEF || utfheader[1] != 0xBB || utfheader[2] != 0xBF) + infile.seekg(ios::beg); // Seek beginning, not a UTF-8 header + } + + // Read the file + while(!infile.eof()) + { + getline(infile, line); + text.push_back(line); + } + + infile.close(); + CurrentLine = 1; + + // Parse the file and build the series of AtlasBlocks + for(ListStringIt it = text.begin(); it != text.end(); it++) + { + ParseLine(*it); + CurrentLine++; + } + + if(!CurBlock.Commands.empty() || !CurBlock.TextLines.empty()) + FlushBlock(); + + for(ListErrorIt i = Logger.Errors.begin(); i != Logger.Errors.end(); i++) + { + if(i->Severity == FATALERROR) + return false; + } + + return true; +} + +void AtlasParser::ParseLine(string& line) +{ + size_t firstchar = line.find_first_not_of(" \t", 0); + + if(firstchar == string::npos) // All whitespace + { + string s = ""; + AddText(s); + return; + } + + string editline = line.substr(firstchar, line.length() - firstchar); + + switch(line[firstchar]) + { + case '#': // Atlas command + if(CurBlock.TextLines.empty()) // No text, build more commands + { + ParseCommand(editline); + } + else // Clear, parse the command + { + FlushBlock(); + ParseCommand(editline); + } + break; + case '/': // Possible comment + if(line.length() > firstchar+1) + { + if(line[firstchar+1] != '/') // Not a comment "//", but text + AddText(line); + // else; Comment + } + else // Single text character of '/' + AddText(line); + break; + default: // Text + AddText(line); + break; + } +} + +inline void AtlasParser::FlushBlock() +{ + Blocks.push_back(CurBlock); + CurBlock.TextLines.clear(); + CurBlock.Commands.clear(); + CurBlock.StartLine = -1; +} + +inline void AtlasParser::AddText(string& text) +{ + if(CurBlock.StartLine == -1) + CurBlock.StartLine = CurrentLine; + + CurBlock.TextLines.push_back(text); +} + +inline void AtlasParser::ParseCommand(string& line) +{ + if(line[0] != '#') + printf("Bug, %s %d. Should start with a '#'\n'%s'", __FILE__, __LINE__, line); + + size_t curpos = 1; + std::string CmdStr; + + Parameter Param; + Command Command; + Param.Type = P_INVALID; + + char ch; + + while(curpos < line.length() && (ch = line[curpos]) != '(') + { + if(isalpha(ch) || isdigit(ch)) + CmdStr += ch; + else Logger.ReportError(CurrentLine, "Invalid syntax: Nonalphabetical character in command"); + + curpos++; + } + + curpos = line.find_first_not_of(" \t", curpos + 1); // Skip ')', get first non + // wspace character + + // Parse parameters + unsigned int ParamNum = 1; + while(curpos < line.length() && (ch = line[curpos]) != ')') + { + if(ch == ',') + { + // Trim trailing whitespace + size_t Last; + for(Last = Param.Value.length() - 1; Last > 0; Last--) + if(Param.Value[Last] != ' ' && Param.Value[Last] != '\t') + break; + if(Last < Param.Value.length()) + Param.Value.erase(Last+1); + + Param.Type = IdentifyType(Param.Value); + if(Param.Type == P_INVALID) + Logger.ReportError(CurrentLine, "Invalid argument for %s for parameter %d", CmdStr.c_str(), ParamNum); + Command.Parameters.push_back(Param); + Param.Type = P_INVALID; + Param.Value.clear(); + ParamNum++; + curpos = line.find_first_not_of(" \t", curpos + 1); + } + else + { + Param.Value += ch; + curpos++; + } + } + + // Trim trailing whitespace + size_t Last; + for(Last = Param.Value.length() - 1; Last > 0; Last--) + if(Param.Value[Last] != ' ' && Param.Value[Last] != '\t') + break; + if(Last < Param.Value.length()) + Param.Value.erase(Last+1); + + Param.Type = IdentifyType(Param.Value); + if(Param.Type == P_INVALID) + Logger.ReportError(CurrentLine, "Invalid argument for %s for parameter %d", CmdStr.c_str(), ParamNum); + + for(ListErrorIt i = Logger.Errors.begin(); i != Logger.Errors.end(); i++) + if(i->Severity = FATALERROR) + return; + + Command.Parameters.push_back(Param); + + AddCommand(CmdStr, Command); +} + +inline unsigned int AtlasParser::IdentifyType(string& str) +{ + if(str.empty()) + return P_VOID; + + size_t charpos = 0; + + // Check for number (int/uint) + if(str[0] == '$') + { + charpos = str.find_first_of('-', 1); + if(charpos == 1 || charpos == string::npos) + { + charpos = str.find_first_not_of("-0123456789ABCDEF", 1); + if(charpos == string::npos) + return P_NUMBER; + } + } + else + { + charpos = str.find_first_of('-'); + if(charpos == 0 || charpos == string::npos) + { + charpos = str.find_first_not_of("0123456789", 1); + if(charpos == string::npos) + return P_NUMBER; + } + } + + // Check for double + charpos = str.find_first_not_of("0123456789.", 0); + if(charpos == string::npos) + return P_DOUBLE; + + // Check for variable + charpos = str.find_first_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ", 0); + if(charpos == 0) + { + charpos = str.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ0123456789", 0); + if(charpos == string::npos) + return P_VARIABLE; + } + + // Check for string + if((str[0] == '"') && (str[str.length()-1] == '"')) + { + str = str.substr(1, str.length()-2); + return P_STRING; + } + + return P_INVALID; +} + +inline bool AtlasParser::AddCommand(string& CmdStr, Command& Cmd) +{ + bool bFound = false; + unsigned int CmdNum = 0; + + Cmd.Line = CurrentLine; + pair val = CmdMap.equal_range(CmdStr); + if(val.first == val.second) // not found + { + Logger.ReportError(CurrentLine, "Invalid command %s", CmdStr.c_str()); + return false; + } + + // Found one or more matches + for(StrCmdMapIt i = val.first; i != val.second; i++) + { + CmdNum = i->second; + if(ParamCount[CmdNum] != Cmd.Parameters.size()) + continue; + // Found a matching arg count function, check types + ListParamIt it = Cmd.Parameters.begin(); + for(unsigned int j = 0; j < Cmd.Parameters.size(); j++, it++) + { + if((it->Type == P_VARIABLE) && (CmdNum != CMD_VAR)) // Type-checking for vars + { + GenericVariable* var = VarMap->GetVar(it->Value); + if(var) // Found, check the type + { + if(var->GetType() != Types[CmdNum][j]) // Type mismatch + break; + else + it->Type = Types[CmdNum][j]; + } + else // NULL + { + Logger.ReportError(CurrentLine, "Undefined variable %s", (it->Value).c_str()); + return false; + } + } + if(it->Type != Types[CmdNum][j]) // Type checking for everything + break; + if(j == Cmd.Parameters.size() - 1) // Verified final parameter + bFound = true; + } + if(bFound) + break; + } + + if(bFound) // Successful lookup + { + Cmd.Function = CmdNum; + if(CmdNum == CMD_VAR) // Variable declaration, handled here explicitly + { + return AddUnitializedVariable(Cmd.Parameters[0].Value, Cmd.Parameters[1].Value); + } + else + { + // Hack for preallocating just enough embedded pointers + if(CmdNum == CMD_EMBSET || CmdNum == CMD_EMBWRITE) + { + int ptrcount = StringToUInt(Cmd.Parameters[0].Value); + if(ptrcount > MaxEmbPtr) + MaxEmbPtr = ptrcount; + } + CurBlock.Commands.push_back(Cmd); + } + return true; + } + else + { + ostringstream ErrorStr; + ErrorStr << "Invalid parameters " << "("; + if(Cmd.Parameters.size() > 0) + ErrorStr << TypeStrings[Cmd.Parameters.front().Type]; + for(unsigned int i = 1; i < Cmd.Parameters.size(); i++) + ErrorStr << "," << TypeStrings[Cmd.Parameters[i].Type]; + ErrorStr << ") for " << CmdStr; + Logger.ReportError(CurrentLine, "%s", ErrorStr.str().c_str()); + return false; + } +} + +inline bool AtlasParser::AddUnitializedVariable(string& VarName, string& Type) +{ + StrTypeMapIt it = TypeMap.find(Type); + if(it == TypeMap.end()) // not found + { + Logger.ReportError(CurrentLine, "Invalid VAR declaration of type %s", Type.c_str()); + return false; + } + else // Add to the variable map + { + VarMap->AddVar(VarName, 0, it->second); + return true; + } +} \ No newline at end of file diff --git a/AtlasParser.h b/AtlasParser.h new file mode 100644 index 0000000..44c1acb --- /dev/null +++ b/AtlasParser.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include "AtlasTypes.h" +#include "GenericVariable.h" + +class AtlasParser +{ +public: + AtlasParser(VariableMap* Map); + ~AtlasParser(); + bool ParseFile(std::ifstream& infile); + + std::list Blocks; + +private: + void ParseLine(std::string& line); + inline void ParseCommand(std::string& line); + inline void FlushBlock(); + inline void AddText(std::string& text); + inline unsigned int IdentifyType(std::string& str); + inline bool AddCommand(std::string& CmdStr, Command& Cmd); + inline bool AddUnitializedVariable(std::string& VarName, std::string& Type); + + unsigned int CurrentLine; + AtlasBlock CurBlock; + StrCmdMap CmdMap; + StrTypeMap TypeMap; + VariableMap* VarMap; +}; \ No newline at end of file diff --git a/AtlasStats.cpp b/AtlasStats.cpp new file mode 100644 index 0000000..13d8430 --- /dev/null +++ b/AtlasStats.cpp @@ -0,0 +1,208 @@ +#include "stdafx.h" +#include +#include +#include "AtlasTypes.h" +#include "AtlasLogger.h" +#include "AtlasStats.h" + +StatisticsHandler Stats; + +InsertionStatistics::InsertionStatistics() +{ + ClearStats(); +} + +void InsertionStatistics::Init(unsigned int StartPos, unsigned int UpperBound, unsigned int LineStart) +{ + ClearStats(); + this->StartPos = StartPos; + this->LineStart = LineStart; + if(UpperBound != -1) + MaxBound = UpperBound; +} + +void InsertionStatistics::AddStats(InsertionStatistics& Stats) +{ + ScriptSize += Stats.ScriptSize; + ScriptOverflowed += Stats.ScriptOverflowed; + SpaceRemaining += Stats.SpaceRemaining; + PointerWrites += Stats.PointerWrites; + EmbPointerWrites += Stats.EmbPointerWrites; + AutoPointerWrites += Stats.AutoPointerWrites; + FailedListWrites += Stats.FailedListWrites; + ExtPointerWrites += Stats.ExtPointerWrites; + + for(int j = 0; j < CommandCount; j++) + ExecCount[j] += Stats.ExecCount[j]; +} + +void InsertionStatistics::ClearStats() +{ + ScriptSize = 0; + ScriptOverflowed = 0; + SpaceRemaining = 0; + PointerWrites = 0; + EmbPointerWrites = 0; + AutoPointerWrites = 0; + FailedListWrites = 0; + ExtPointerWrites = 0; + + StartPos = 0; + MaxBound = -1; + + LineStart = 0; + LineEnd = 0; + memset(ExecCount, 0, 4 * CommandCount); +} + +void InsertionStatistics::AddCmd(unsigned int CmdNum) +{ + ExecCount[CmdNum]++; + switch(CmdNum) + { + case CMD_WUB: case CMD_WBB: case CMD_WHB: case CMD_WLB: case CMD_WHW: + case CMD_W16: case CMD_W24: case CMD_W32: case CMD_WRITEPTR: + case CMD_WUBCUST: case CMD_WBBCUST: case CMD_WHBCUST: case CMD_WLBCUST: + case CMD_WHWCUST: + PointerWrites++; + break; + default: + break; + } +} + +bool InsertionStatistics::HasCommands() +{ + for(unsigned int i = 0; i < CommandCount; i++) + if(ExecCount[i] != 0) + return true; + + return false; +} + +InsertionStatistics& InsertionStatistics::operator=(const InsertionStatistics& rhs) +{ + if(this == &rhs) + return *this; + + ScriptSize = rhs.ScriptSize; + ScriptOverflowed = rhs.ScriptOverflowed; + SpaceRemaining = rhs.SpaceRemaining; + PointerWrites = rhs.PointerWrites; + EmbPointerWrites = rhs.EmbPointerWrites; + + StartPos = rhs.StartPos; + MaxBound = rhs.MaxBound; + + LineStart = rhs.LineStart; + LineEnd = rhs.LineEnd; + + for(unsigned int i = 0; i < CommandCount; i++) + ExecCount[i] = rhs.ExecCount[i]; + + return *this; +} + +StatisticsHandler::StatisticsHandler() +{ +} + +StatisticsHandler::~StatisticsHandler() +{ +} + +void StatisticsHandler::NewStatsBlock(unsigned int StartPos, unsigned int UpperBound, unsigned int LineStart) +{ + if(CurBlock.LineStart != 0) // If not first block + { + CurBlock.ScriptOverflowed = 0; + CurBlock.SpaceRemaining = 0; + + if(CurBlock.MaxBound != -1) // if there is a MaxBound, calc overflow and remaining space + { + if(CurBlock.StartPos + CurBlock.ScriptSize > CurBlock.MaxBound+1) + CurBlock.ScriptOverflowed = CurBlock.StartPos + CurBlock.ScriptSize - CurBlock.MaxBound; + else + CurBlock.ScriptOverflowed = 0; + + if(CurBlock.MaxBound+1 > (CurBlock.StartPos + CurBlock.ScriptSize)) + CurBlock.SpaceRemaining = CurBlock.MaxBound+1 - (CurBlock.StartPos + CurBlock.ScriptSize); + else + CurBlock.SpaceRemaining = 0; + } + CurBlock.LineEnd = LineStart; + Stats.push_back(CurBlock); + } + + CurBlock.Init(StartPos, UpperBound, LineStart); +} + +void StatisticsHandler::AddCmd(unsigned int CmdNum) +{ + if(CmdNum < CommandCount) + CurBlock.AddCmd(CmdNum); +} + +void StatisticsHandler::IncGenPointerWrites() +{ + CurBlock.PointerWrites++; +} + +void StatisticsHandler::IncEmbPointerWrites() +{ + CurBlock.EmbPointerWrites++; +} + +void StatisticsHandler::IncAutoPointerWrites() +{ + CurBlock.AutoPointerWrites++; +} + +void StatisticsHandler::IncFailedListWrites() +{ + CurBlock.FailedListWrites++; +} + +void StatisticsHandler::IncExtPointerWrites() +{ + CurBlock.ExtPointerWrites++; +} + +void StatisticsHandler::AddScriptBytes(unsigned int Count) +{ + CurBlock.ScriptSize += Count; +} + +void StatisticsHandler::End(unsigned int EndLine) +{ + if(CurBlock.StartPos + CurBlock.ScriptSize > CurBlock.MaxBound) + CurBlock.ScriptOverflowed = CurBlock.StartPos + CurBlock.ScriptSize - CurBlock.MaxBound; + else + CurBlock.ScriptOverflowed = 0; + + if(CurBlock.MaxBound != -1 && CurBlock.MaxBound > (CurBlock.StartPos + CurBlock.ScriptSize)) + CurBlock.SpaceRemaining = CurBlock.MaxBound - (CurBlock.StartPos + CurBlock.ScriptSize); + else + CurBlock.SpaceRemaining = 0; + + CurBlock.LineEnd = EndLine; + Stats.push_back(CurBlock); + CurBlock.ClearStats(); +} + +void StatisticsHandler::GenerateTotalStats(InsertionStatistics& Total) +{ + if(Stats.empty()) + { + ReportBug("Invalid size for statistics list in StatisticsHandler::GenerateTotalStats"); + return; + } + else if(Stats.size() == 1) + { + Total = Stats.front(); + return; + } + + for(ListStatsIt i = Stats.begin(); i != Stats.end(); i++) + Total.AddStats(*i); +} \ No newline at end of file diff --git a/AtlasStats.h b/AtlasStats.h new file mode 100644 index 0000000..4b2ecf9 --- /dev/null +++ b/AtlasStats.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include "AtlasTypes.h" + +class InsertionStatistics +{ +public: + InsertionStatistics(); + void AddStats(InsertionStatistics& Stats); + void ClearStats(); + void Init(unsigned int StartPos, unsigned int UpperBound, unsigned int LineStart); + void AddCmd(unsigned int CmdNum); + bool HasCommands(); + + InsertionStatistics& operator=(const InsertionStatistics& rhs); + + unsigned int StartPos; + unsigned int ScriptSize; + unsigned int ScriptOverflowed; + unsigned int SpaceRemaining; + unsigned int MaxBound; + + unsigned int LineStart; + unsigned int LineEnd; + + unsigned int PointerWrites; + unsigned int EmbPointerWrites; + unsigned int AutoPointerWrites; + unsigned int FailedListWrites; + unsigned int ExtPointerWrites; + + unsigned int ExecCount[CommandCount]; +}; + +class StatisticsHandler +{ +public: + StatisticsHandler(); + ~StatisticsHandler(); + + std::list Stats; + + void NewStatsBlock(unsigned int StartPos, unsigned int UpperBound, unsigned int LineStart); + void AddCmd(unsigned int CmdNum); + void AddScriptBytes(unsigned int Count); + void End(unsigned int EndLine); + void GenerateTotalStats(InsertionStatistics& Total); + void IncGenPointerWrites(); + void IncEmbPointerWrites(); + void IncAutoPointerWrites(); + void IncFailedListWrites(); + void IncExtPointerWrites(); + +private: + InsertionStatistics CurBlock; +}; + +typedef std::list::iterator ListStatsIt; +typedef std::list::reverse_iterator ListStatsRevIt; + +extern StatisticsHandler Stats; \ No newline at end of file diff --git a/AtlasTypes.cpp b/AtlasTypes.cpp new file mode 100644 index 0000000..1577c4e --- /dev/null +++ b/AtlasTypes.cpp @@ -0,0 +1 @@ +#include "stdafx.h" \ No newline at end of file diff --git a/AtlasTypes.h b/AtlasTypes.h new file mode 100644 index 0000000..16767c8 --- /dev/null +++ b/AtlasTypes.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include + +/* Misc Functions */ +static const unsigned int CMD_JMP1 = 0; +static const unsigned int CMD_JMP2 = 1; +static const unsigned int CMD_SMA = 2; +static const unsigned int CMD_HDR = 3; +static const unsigned int CMD_STRTYPE = 4; +static const unsigned int CMD_ADDTBL = 5; +static const unsigned int CMD_ACTIVETBL = 6; +static const unsigned int CMD_VAR = 7; +/* Pointer Functions */ +static const unsigned int CMD_WUB = 8; +static const unsigned int CMD_WBB = 9; +static const unsigned int CMD_WHB = 10; +static const unsigned int CMD_WLB = 11; +static const unsigned int CMD_W16 = 12; +static const unsigned int CMD_W24 = 13; +static const unsigned int CMD_W32 = 14; +static const unsigned int CMD_EMBSET = 15; +static const unsigned int CMD_EMBTYPE = 16; +static const unsigned int CMD_EMBWRITE = 17; +/* Debugging Functions */ +static const unsigned int CMD_BREAK = 18; +/* Extended Pointer Functionality */ +static const unsigned int CMD_PTRTBL = 19; +static const unsigned int CMD_WRITETBL = 20; +static const unsigned int CMD_PTRLIST = 21; +static const unsigned int CMD_WRITELIST = 22; +static const unsigned int CMD_AUTOWRITETBL = 23; +static const unsigned int CMD_AUTOWRITELIST = 24; +static const unsigned int CMD_CREATEPTR = 25; +static const unsigned int CMD_WRITEPTR = 26; + +static const unsigned int CMD_LOADEXT = 27; +static const unsigned int CMD_EXECEXT = 28; +static const unsigned int CMD_DISABLETABLE = 29; +static const unsigned int CMD_DISABLELIST = 30; +static const unsigned int CMD_PASCALLEN = 31; +static const unsigned int CMD_AUTOEXEC = 32; +static const unsigned int CMD_DISABLEEXEC = 33; +static const unsigned int CMD_FIXEDLENGTH = 34; + +static const unsigned int CMD_WUBCUST = 35; +static const unsigned int CMD_WBBCUST = 36; +static const unsigned int CMD_WHBCUST = 37; +static const unsigned int CMD_WLBCUST = 38; +static const unsigned int CMD_ENDIANSWAP = 39; +static const unsigned int CMD_STRINGALIGN = 40; + +// Add these commands! +static const unsigned int CMD_EMBPTRTABLE = 41; +static const unsigned int CMD_WHW = 42; +static const unsigned int CMD_WHWCUST = 43; +static const unsigned int CMD_SETTARGETFILE = 44; +static const unsigned int CMD_SETPTRFILE = 45; +static const unsigned int CMD_WRITEEMBTBL1 = 46; +static const unsigned int CMD_WRITEEMBTBL2 = 47; +static const unsigned int CMD_WRITETBL2 = 48; + +static const unsigned int CommandCount = 49; + +static const char* CommandStrings[CommandCount] = { "JMP", "JMP", "SMA", "HDR", "STRTYPE", + "ADDTBL", "ACTIVETBL", "VAR", "WUB", "WBB", "WHB", "WLB", "W16", "W24", "W32", + "EMBSET", "EMBTYPE", "EMBWRITE", "BREAK", "PTRTBL", "WRITE", "PTRLIST", "WRITE", + "AUTOWRITE", "AUTOWRITE", "CREATEPTR", "WRITE", "LOADEXT", "EXECEXT", "DISABLE", "DISABLE", + "PASCALLEN", "AUTOEXEC", "DISABLE", "FIXEDLENGTH", "WUB", "WBB", "WHB", "WLB", + "ENDIANSWAP", "STRINGALIGN", "EMBPTRTBL", "WHW", "WHW", "SETTARGETFILE", "SETPTRFILE", "WRITE", + "WRITE", "WRITE" }; + +// Parameter types +static const unsigned int TypeCount = 12; + +static const unsigned int P_INVALID = 0; +static const unsigned int P_VOID = 1; +static const unsigned int P_STRING = 2; +static const unsigned int P_VARIABLE = 3; +static const unsigned int P_NUMBER = 4; +static const unsigned int P_DOUBLE = 5; +static const unsigned int P_TABLE = 6; +static const unsigned int P_POINTERTABLE = 7; +static const unsigned int P_EMBPOINTERTABLE = 8; +static const unsigned int P_POINTERLIST = 9; +static const unsigned int P_CUSTOMPOINTER = 10; +static const unsigned int P_EXTENSION = 11; + +static const char* TypeStrings[TypeCount] = { "INVALID", "VOID", "STRING", "VARIABLE", + "NUMBER", "DOUBLE", "TABLE", "POINTERTABLE", "EMBPOINTERTABLE", "POINTERLIST", "CUSTOMPOINTER", "EXTENSION" }; + +static const unsigned int Types[CommandCount][5] = { { P_NUMBER }, { P_NUMBER, P_NUMBER }, // JMP1 JMP2 +{ P_STRING }, { P_NUMBER }, { P_STRING }, { P_STRING, P_TABLE }, { P_TABLE }, // SMA HDR STRTYPE ADDTBL ACTIVETBL +{ P_VARIABLE, P_VARIABLE }, { P_NUMBER }, {P_NUMBER }, // VAR WUB WBB +{ P_NUMBER }, { P_NUMBER }, { P_NUMBER }, { P_NUMBER }, { P_NUMBER }, // WHB WLB W16 W24 W32 +{ P_NUMBER }, { P_STRING, P_NUMBER, P_NUMBER }, { P_NUMBER }, // EMBSET EMBTYPE EMBWRITE +{ P_VOID }, // BREAK +{ P_POINTERTABLE, P_NUMBER, P_NUMBER, P_CUSTOMPOINTER }, { P_POINTERTABLE }, // PTRTBL WRITE +{ P_POINTERLIST, P_STRING, P_CUSTOMPOINTER }, { P_POINTERLIST }, // PTRLIST WRITE +{ P_POINTERTABLE, P_STRING }, { P_POINTERLIST, P_STRING }, // AUTOWRITE AUTOWRITE +{ P_CUSTOMPOINTER, P_STRING, P_NUMBER, P_NUMBER }, { P_CUSTOMPOINTER, P_NUMBER }, // CREATEPTR WRITE +{ P_EXTENSION, P_STRING }, { P_EXTENSION, P_STRING }, // LOADEXT EXECEXT +{ P_POINTERTABLE, P_STRING }, { P_POINTERLIST, P_STRING }, // DISABLE DISABLE +{ P_NUMBER }, { P_EXTENSION, P_STRING, P_STRING }, // PASCALLEN AUTOEXEC +{ P_STRING, P_STRING }, { P_NUMBER, P_NUMBER }, // DISABLE FIXEDLENGTH +{ P_CUSTOMPOINTER, P_NUMBER }, { P_CUSTOMPOINTER, P_NUMBER }, // WUB WBB +{ P_CUSTOMPOINTER, P_NUMBER }, { P_CUSTOMPOINTER, P_NUMBER }, // WHB WLB +{ P_STRING }, { P_NUMBER }, // ENDIANSWAP STRINGALIGN +{ P_EMBPOINTERTABLE, P_NUMBER, P_CUSTOMPOINTER }, // EMBPTRTBL +{ P_NUMBER }, { P_CUSTOMPOINTER, P_NUMBER }, { P_STRING }, {P_STRING }, // WHW WHW SETTARGETFILE SETPTRFILE +{ P_EMBPOINTERTABLE }, { P_EMBPOINTERTABLE, P_NUMBER }, { P_POINTERTABLE, P_NUMBER } // WRITE WRITE WRITE +}; + +static const unsigned int ParamCount[CommandCount] = { 1, 2, 1, // JMP1 JMP2 SMA +1, 1, 2, 1, 2, 1, 1, // HDR STRTYPE ADDTBL ACTIVETBL VAR WUB WBB +1, 1, 1, 1, 1, 1, 3, 1, // WHB WLB W16 W24 W32 EMBSET EMBTYPE EMBWRITE +1, // BREAK +4, 1, 3, 1, 2, 2, // PTRTBL WRITE PTRLIST WRITE AUTOWRITE AUTOWRITE +4, 2, 2, 2, 2, 2, 1, 3, 2, 2, // CREATEPTR WRITE LOADEXT EXECEXT DISABLE DISABLE PASCALLEN AUTOEXEC DISABLE FIXEDLENGTH +2, 2, 2, 2, 1, 1, 3, 1, 2, 1, 1, // WUB WBB WHB WLB ENDIANSWAP STRINGALIGN EMBPTRTBL WHW WHW SETTARGETFILE SETPTRFILE +1, 2, 2 }; // WRITE WRITE WRITE + +typedef struct Parameter +{ + std::string Value; + unsigned int Type; +} Parameter; + +typedef struct Command +{ + unsigned int Function; + std::vector Parameters; + unsigned int Line; +} Command; + +typedef struct AtlasBlock +{ + std::list Commands; + std::list TextLines; + unsigned int StartLine; +} AtlasBlock; + +typedef std::multimap StrCmdMap; +typedef std::multimap::const_iterator StrCmdMapIt; + +typedef std::map StrTypeMap; +typedef std::map::const_iterator StrTypeMapIt; + +typedef std::vector::iterator ListParamIt; +typedef std::list::iterator ListStringIt; +typedef std::list::iterator ListCmdIt; +typedef std::list::iterator ListBlockIt; \ No newline at end of file diff --git a/GenericVariable.cpp b/GenericVariable.cpp new file mode 100644 index 0000000..59088b8 --- /dev/null +++ b/GenericVariable.cpp @@ -0,0 +1,169 @@ +#include "stdafx.h" +#include +#include +#include +#include "GenericVariable.h" +#include "Table.h" +#include "AtlasCore.h" +#include "AtlasExtension.h" +#include "Pointer.h" +#include "AtlasTypes.h" + +using namespace std; + +GenericVariable::GenericVariable(void* Data, unsigned int Type) +{ + DataType = Type; + DataPointer = Data; +} + +GenericVariable::GenericVariable() +{ + DataType = P_INVALID; + DataPointer = NULL; +} + +GenericVariable::~GenericVariable() +{ + Free(); +} + +void GenericVariable::SetData(void* Data, unsigned int Type) +{ + Free(); + DataType = Type; + DataPointer = Data; +} + +void* GenericVariable::GetData() +{ + return DataPointer; +} + +unsigned int GenericVariable::GetType() +{ + return DataType; +} + +bool GenericVariable::Free() +{ + if(DataPointer == NULL) + return true; + switch(DataType) + { + case P_INVALID: + break; + case P_STRING: + delete (string*)DataPointer; + break; + case P_NUMBER: + delete (__int64*)DataPointer; + break; + case P_DOUBLE: + delete (double*)DataPointer; + break; + case P_TABLE: + delete (Table*)DataPointer; + break; + case P_POINTERTABLE: + delete (PointerTable*)DataPointer; + break; + case P_POINTERLIST: + delete (PointerList*)DataPointer; + break; + case P_CUSTOMPOINTER: + delete (CustomPointer*)DataPointer; + break; + case P_EXTENSION: + delete (AtlasExtension*)DataPointer; + break; + default: + return false; + } + + return true; +} + +VariableMap::VariableMap() +{ +} + +VariableMap::~VariableMap() +{ + for(VarMapIt = VarMap.begin(); VarMapIt != VarMap.end(); VarMapIt++) + delete VarMapIt->second; +} + +bool VariableMap::AddVar(string& Identifier, void* Data, unsigned int Type) +{ + VarMapIt = VarMap.find(string(Identifier)); + if(VarMapIt != VarMap.end()) // Already a variable under that Identifier + return false; + + GenericVariable* CVar = new GenericVariable; + CVar->SetData(Data, Type); + VarMap[Identifier] = CVar; + return true; +} + +bool VariableMap::Exists(string& Identifier) +{ + VarMapIt = VarMap.find(Identifier); + if(VarMapIt == VarMap.end()) // Not found + return false; + return true; +} + +bool VariableMap::Exists(string& Identifier, unsigned int Type) +{ + VarMapIt = VarMap.find(Identifier); + if(VarMapIt == VarMap.end()) // Identifier not found + return false; + if(VarMap[string(Identifier)]->GetType() != Type) + return false; + return true; +} + +GenericVariable* VariableMap::GetVar(string& Identifier) +{ + VarMapIt = VarMap.find(Identifier); + if(VarMapIt == VarMap.end()) + return NULL; + else + return VarMapIt->second; +} + +void VariableMap::SetVar(string& Identifier, GenericVariable* Var) +{ + VarMapIt = VarMap.lower_bound(Identifier); + if(VarMapIt != VarMap.end() && !(VarMap.key_comp()(Identifier, VarMapIt->first))) + { + delete VarMapIt->second; + VarMapIt->second = Var; + } + else // Add variable + VarMap.insert(VarMapIt, VariableMapValue(Identifier, Var)); +} + +void VariableMap::SetVarData(string& Identifier, void* Data, unsigned int Type) +{ + VarMapIt = VarMap.lower_bound(Identifier); + if(VarMapIt != VarMap.end() && !(VarMap.key_comp()(Identifier, VarMapIt->first))) + (VarMapIt->second)->SetData(Data, Type); + else // Add variable + VarMap.insert(VarMapIt, VariableMapValue(Identifier, new GenericVariable(Data, Type))); +} + +unsigned int VariableMap::GetVarType(string& Identifier) +{ + if(!Exists(Identifier)) + return P_INVALID; + return VarMap[string(Identifier)]->GetType(); +} + +void* VariableMap::GetData(string& Identifier) +{ + if(!Exists(Identifier)) + return NULL; + return VarMap[Identifier]->GetData(); +} \ No newline at end of file diff --git a/GenericVariable.h b/GenericVariable.h new file mode 100644 index 0000000..f907af9 --- /dev/null +++ b/GenericVariable.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include "AtlasTypes.h" + +using namespace std; + +class GenericVariable +{ +public: + GenericVariable(); + GenericVariable(void* Data, unsigned int Type); + ~GenericVariable(); + + unsigned int GetType(); + void* GetData(); + void SetData(void* Data, unsigned int Type); + void SetData(void* Data); + +private: + bool Free(); + + void* DataPointer; + unsigned int DataType; +}; + +typedef std::map VarMapType; +typedef std::map::iterator VariableMapIt; +typedef std::map::value_type VariableMapValue; + +class VariableMap +{ +public: + VariableMap(); + ~VariableMap(); + + bool AddVar(std::string& Identifier, void* Data, unsigned int Type); + bool Exists(std::string& Identifier, unsigned int Type); + bool Exists(std::string& Identifier); + GenericVariable* GetVar(std::string& Identifier); + void SetVarData(std::string& Identifier, void* Data, unsigned int Type); + void SetVar(std::string& Identifier, GenericVariable* Var); + void* GetData(std::string& Identifier); + unsigned int GetVarType(std::string& Identifier); + +private: + VarMapType VarMap; // Maps strings to variables + VariableMapIt VarMapIt; // Iterator for the map +}; \ No newline at end of file diff --git a/Pointer.cpp b/Pointer.cpp new file mode 100644 index 0000000..2d947c2 --- /dev/null +++ b/Pointer.cpp @@ -0,0 +1,463 @@ +#include "stdafx.h" +#include +#include "Pointer.h" +#include "AtlasLogger.h" + +using namespace std; + +Pointer::Pointer() +{ + AddressType = LINEAR; + HeaderSize = 0; +} + +Pointer::~Pointer() +{ +} + +bool Pointer::SetAddressType(string& Type) +{ + for(int i = 0; i < AddressTypeCount; i++) + { + if(Type == AddressTypes[i]) + { + AddressType = i; + return true; + } + } + + return false; +} + +bool Pointer::SetAddressType(unsigned int Type) +{ + if(Type < AddressTypeCount) + { + AddressType = Type; + return true; + } + else + return false; +} + +void Pointer::SetHeaderSize(const unsigned int Size) +{ + HeaderSize = Size; +} + +unsigned int Pointer::GetAddress(const unsigned int Address) const +{ + return GetMachineAddress(Address); +} + +unsigned int Pointer::GetMachineAddress(unsigned int Address) const +{ + Address -= HeaderSize; + + switch(AddressType) + { + case LINEAR: + return Address; + case LOROM00: + return GetLoROMAddress(Address); + case LOROM80: + return GetLoROMAddress(Address) + 0x800000; + case HIROM: + return GetHiROMAddress(Address); + case GB: + return GetGBAddress(Address); + default: + return Address; // Error handling + } +} + +unsigned int Pointer::GetLoROMAddress(unsigned int Offset) const +{ + char bankbyte = (char) ((Offset & 0xFF0000) >> 16); + unsigned short int Word = (unsigned short int) (Offset & 0xFFFF); + unsigned int Address = 0; + + if(Word >= 0x8000) + Address = bankbyte * 0x20000 + 0x10000 + Word; + else + Address = bankbyte * 0x20000 + Word + 0x8000; + + return Address; +} + +unsigned int Pointer::GetHiROMAddress(unsigned int Offset) const +{ + unsigned int Address = 0; + + Address = Offset + 0xC00000; + + return Address; +} + +unsigned int Pointer::GetGBAddress(unsigned int Offset) const +{ + unsigned int Address = 0; + unsigned short int Bank = 0; + unsigned short int Word = 0; + + Bank = Offset / 0x4000; + Word = Offset % ((Bank+1) * 0x4000); + + Address = Bank * 0x10000 + Word; + + return Address; +} + +unsigned char Pointer::GetUpperByte(const unsigned int ScriptPos) const +{ + return (GetAddress(ScriptPos) & 0xFF000000) >> 24; +} + + + +// #WBB(param) - Working + +unsigned char Pointer::GetBankByte(const unsigned int ScriptPos) const +{ + return (GetAddress(ScriptPos) & 0xFF0000) >> 16; +} + + + +// #WHB(param) - Working + +unsigned char Pointer::GetHighByte(const unsigned int ScriptPos) const +{ + return (GetAddress(ScriptPos) & 0xFF00) >> 8; +} + + + +// #WLB(param) - Working + +unsigned char Pointer::GetLowByte(const unsigned int ScriptPos) const +{ + return GetAddress(ScriptPos) & 0xFF; +} + + + +// #W16(param) - Working + +unsigned short Pointer::Get16BitPointer(const unsigned int ScriptPos) const +{ + return GetAddress(ScriptPos) & 0xFFFF; +} + + + +// #W24(param) - Working + +unsigned int Pointer::Get24BitPointer(const unsigned int ScriptPos) const +{ + return GetAddress(ScriptPos) & 0xFFFFFF; +} + + + +// #W32 - Working + +unsigned int Pointer::Get32BitPointer(const unsigned int ScriptPos) const +{ + return GetAddress(ScriptPos); +} + +// #WHW (Write High Word) - Working + +unsigned int Pointer::GetHighWord(const unsigned int ScriptPos) const +{ + return ((GetAddress(ScriptPos) & 0xFFFF0000) >> 16); +} + +//--------------------------- Custom Pointer ---------------------------------- +// \\ +// \\ + +bool CustomPointer::Init(__int64 Offsetting, unsigned int Size, unsigned int HeaderSize) +{ + this->Offsetting = Offsetting; + SetHeaderSize(HeaderSize); + switch(Size) + { + case 8: case 16: case 24: case 32: + this->Size = Size; + break; + default: + return false; + } + return true; +} + +unsigned int CustomPointer::GetSize() +{ + return Size; +} + +unsigned int CustomPointer::GetAddress(const unsigned int Address) const +{ + unsigned int Val; + Val = (unsigned int) ((__int64)GetMachineAddress(Address) - Offsetting); + switch(Size) + { + case 8: + return Val & 0xFF; + case 16: + return Val & 0xFFFF; + case 24: + return Val & 0xFFFFFF; + case 32: + return Val; + default: + Logger.BugReport(__LINE__, __FILE__, "Bad size in CustomPointer::GetAddress"); + return -1; + } +} + +//--------------------------- Embedded Pointer -------------------------------- +// \\ +// \\ + +EmbeddedPointer::EmbeddedPointer() +{ + TextPos = -1; + PointerPos = -1; + Size = 0; +} + +EmbeddedPointer::~EmbeddedPointer() +{ +} + +bool EmbeddedPointer::SetPointerPosition(const unsigned int Address) +{ + PointerPos = Address; + if(TextPos != -1) + return true; // Return true if pointer is ready to write + else + return false; +} + +bool EmbeddedPointer::SetTextPosition(const unsigned int Address) +{ + TextPos = Address; + if(PointerPos != -1) + return true; + else + return false; +} + +void EmbeddedPointer::SetSize(const unsigned int size) +{ + Size = size; +} + +unsigned int EmbeddedPointer::GetSize() const +{ + return Size; +} + +void EmbeddedPointer::SetOffsetting(const __int64 Offsetting) +{ + this->Offsetting = Offsetting; +} + +unsigned int EmbeddedPointer::GetPointer() const +{ + unsigned int Val = (unsigned int)(GetAddress(TextPos) - Offsetting); + switch(Size) + { + case 8: + return Val & 0xFF; + case 16: + return Val & 0xFFFF; + case 24: + return Val & 0xFFFFFF; + case 32: + return Val & 0xFFFFFFFF; + default: + Logger.BugReport(__LINE__, __FILE__, + "Bad embedded pointer size %d in EmbeddedPointer::GetTextPosition", Size); + return 0; + } +} + +unsigned int EmbeddedPointer::GetTextPosition() const +{ + return TextPos; +} + +unsigned int EmbeddedPointer::GetPointerPosition() const +{ + return PointerPos; +} + +//------------------------ Embedded Pointer Handler --------------------------- +// \\ +// \\ + +EmbeddedPointerHandler::EmbeddedPointerHandler() +{ + PtrSize = 0; + HdrSize = 0; + Offsetting = 0; +} + +EmbeddedPointerHandler::~EmbeddedPointerHandler() +{ +} + +void EmbeddedPointerHandler::SetListSize(int Size) +{ + PtrList.reserve(Size); + if((int)PtrList.size() < Size) + { + int j = Size - (int)PtrList.size(); + + EmbeddedPointer elem; + elem.SetAddressType(AddressType); + elem.SetSize(PtrSize); + elem.SetHeaderSize (HdrSize); + elem.SetOffsetting(Offsetting); + elem.SetPointerPosition(-1); + elem.SetTextPosition(-1); + + for(int i = 0; i < j; i++) + PtrList.push_back(elem); + } +} + +int EmbeddedPointerHandler::GetListSize() +{ + return (int)PtrList.size(); +} + +bool EmbeddedPointerHandler::GetPointerState(const unsigned int PointerNum, unsigned int& TextPos, + unsigned int& PointerPos) +{ + if(PtrList.size() < PointerNum) + { + TextPos = -1; + PointerPos = -1; + return false; + } + + TextPos = GetTextPosition(PointerNum); + PointerPos = GetPointerPosition(PointerNum); + + if(TextPos == -1 || PointerPos == -1) + return false; + else + return true; +} + +bool EmbeddedPointerHandler::SetType(std::string& AddressString, const __int64 Offsetting, const unsigned int PointerSize) +{ + this->Offsetting = Offsetting; + switch(PointerSize) + { + case 8: case 16: case 24: case 32: + PtrSize = PointerSize; + break; + default: // Bad size + return false; + } + return SetAddressType(AddressString); +} + +void EmbeddedPointerHandler::SetHeaderSize(const unsigned int HeaderSize) +{ + HdrSize = HeaderSize; +} + +unsigned int EmbeddedPointerHandler::GetDefaultSize() +{ + return PtrSize; +} + +unsigned int EmbeddedPointerHandler::GetSize(const unsigned int PointerNum) +{ + unsigned int i = PointerNum; + if(PtrList.size() < i) + return -1; + + return PtrList[i].GetSize(); +} + +bool EmbeddedPointerHandler::SetTextPosition(const unsigned int PointerNum, const unsigned int TextPos) +{ + unsigned int i = PointerNum; + if(PtrList.size() < i) + return false; + + // If still with default allocation + if(PtrList[i].GetTextPosition() == -1 && PtrList[i].GetPointerPosition() == -1) + { + PtrList[i].SetAddressType(AddressType); + PtrList[i].SetSize(PtrSize); + PtrList[i].SetHeaderSize(HdrSize); + PtrList[i].SetOffsetting(Offsetting); + } + + return PtrList[i].SetTextPosition(TextPos); +} + +bool EmbeddedPointerHandler::SetPointerPosition(const unsigned int PointerNum, const unsigned int PointerPos) +{ + unsigned int i = PointerNum; + if(PtrList.size() < i) + return false; + + // If still with default allocation + if(PtrList[i].GetTextPosition() == -1 && PtrList[i].GetPointerPosition() == -1) + { + PtrList[i].SetAddressType(AddressType); + PtrList[i].SetSize(PtrSize); + PtrList[i].SetHeaderSize(HdrSize); + PtrList[i].SetOffsetting(Offsetting); + } + + return PtrList[i].SetPointerPosition(PointerPos); +} + +unsigned int EmbeddedPointerHandler::GetPointerValue(const unsigned int PointerNum) +{ + if(PtrList.size() < PointerNum) + return -1; + + return PtrList[PointerNum].GetPointer(); +} + +unsigned int EmbeddedPointerHandler::GetTextPosition(const unsigned int PointerNum) +{ + if(PtrList.size() < PointerNum) + return -1; + + return PtrList[PointerNum].GetTextPosition(); +} + +unsigned int EmbeddedPointerHandler::GetPointerPosition(const unsigned int PointerNum) +{ + if(PtrList.size() < PointerNum) + return -1; + + return PtrList[PointerNum].GetPointerPosition(); +} + +bool EmbeddedPointerHandler::SetAddressType(std::string& Type) +{ + for(int i = 0; i < AddressTypeCount; i++) + { + if(Type == AddressTypes[i]) + { + AddressType = i; + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/Pointer.h b/Pointer.h new file mode 100644 index 0000000..671c51e --- /dev/null +++ b/Pointer.h @@ -0,0 +1,111 @@ +#pragma once +#include + +// MachineAddresses- The type of addressing the machine uses +static const unsigned int MA_INVALID = 0; +static const unsigned int LINEAR = 1; +static const unsigned int LOROM00 = 2; +static const unsigned int LOROM80 = 3; +static const unsigned int HIROM = 4; +static const unsigned int GB = 5; + +static const unsigned int AddressTypeCount = 6; +static const char* AddressTypes[AddressTypeCount] = { "INVALID", "LINEAR", "LOROM00", "LOROM80", + "HIROM", "GB" }; + +class Pointer +{ +public: + Pointer(); + ~Pointer(); + bool SetAddressType(std::string& AddressString); + bool SetAddressType(unsigned int Type); + void SetHeaderSize(const unsigned int Size); + + // Pointer writing functions + unsigned short Pointer::Get16BitPointer(const unsigned int ScriptPos) const; + unsigned int Pointer::Get24BitPointer(const unsigned int ScriptPos) const; + unsigned int Pointer::Get32BitPointer(const unsigned int ScriptPos) const; + + unsigned char Pointer::GetLowByte(const unsigned int ScriptPos) const; + unsigned char Pointer::GetHighByte(const unsigned int ScriptPos) const; + unsigned char Pointer::GetBankByte(const unsigned int ScriptPos) const; + unsigned char Pointer::GetUpperByte(const unsigned int ScriptPos) const; + unsigned int Pointer::GetHighWord(const unsigned int ScriptPos) const; + +protected: + unsigned int AddressType; + unsigned int HeaderSize; + virtual unsigned int GetAddress(const unsigned int Address) const; + unsigned int GetMachineAddress(unsigned int Address) const; + +private: + // Machine Address translation functions + unsigned int GetLoROMAddress(unsigned int Offset) const; + unsigned int GetHiROMAddress(unsigned int Offset) const; + unsigned int GetGBAddress(unsigned int Offset) const; +}; + +class CustomPointer : public virtual Pointer +{ +public: + bool Init(__int64 Offsetting, unsigned int Size, unsigned int HeaderSize); + unsigned int GetSize(); + unsigned int GetAddress(const unsigned int Address) const; +private: + __int64 Offsetting; + unsigned int Size; +}; + +class EmbeddedPointer : public virtual Pointer +{ +public: + EmbeddedPointer(); + ~EmbeddedPointer(); + + bool SetTextPosition(const unsigned int Address); + bool SetPointerPosition(const unsigned int Address); + void SetSize(const unsigned int size); + void SetOffsetting(const __int64 Offsetting); + + unsigned int GetTextPosition() const; + unsigned int GetPointer() const; + unsigned int GetPointerPosition() const; + unsigned int GetSize() const; +private: + __int64 Offsetting; + unsigned int TextPos; + unsigned int PointerPos; + unsigned int Size; +}; + +typedef std::list::iterator ListEmbPtrIt; + +class EmbeddedPointerHandler +{ +public: + EmbeddedPointerHandler(); + ~EmbeddedPointerHandler(); + + void SetListSize(int Size); + int GetListSize(); + bool GetPointerState(const unsigned int PointerNum, unsigned int& TextPos, unsigned int& PointerPos); + bool SetType(std::string& AddressString, const __int64 Offsetting, const unsigned int PointerSize); + unsigned int GetDefaultSize(); + bool SetTextPosition(const unsigned int PointerNum, const unsigned int TextPos); + bool SetPointerPosition(const unsigned int PointerNum, const unsigned int PointerPos); + void SetHeaderSize(const unsigned int HeaderSize); + bool SetAddressType(std::string& Type); + + unsigned int GetTextPosition(const unsigned int PointerNum); + unsigned int GetPointerPosition(const unsigned int PointerNum); + unsigned int GetPointerValue(const unsigned int PointerNum); + unsigned int GetSize(const unsigned int PointerNum); + +private: + std::vector PtrList; + unsigned int AddressType; + __int64 Offsetting; + unsigned int PtrSize; + unsigned int HdrSize; +}; \ No newline at end of file diff --git a/PointerHandler.cpp b/PointerHandler.cpp new file mode 100644 index 0000000..d955394 --- /dev/null +++ b/PointerHandler.cpp @@ -0,0 +1,364 @@ +#include "stdafx.h" +#include +#include +#include "PointerHandler.h" +#include "Pointer.h" +#include "AtlasLogger.h" +#include "GenericVariable.h" +#include "AtlasCore.h" +#include "AtlasTypes.h" + +using namespace std; + +PointerHandler::PointerHandler(VariableMap* Map) +{ + this->Map = Map; +} + +bool PointerHandler::CreatePointer(std::string& PtrId, std::string& AddressType, + __int64 Offsetting, unsigned int Size, unsigned int HeaderSize) +{ + CustomPointer* Ptr = (CustomPointer*)Map->GetVar(PtrId)->GetData(); + if(Ptr != NULL) // Already initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has already been allocated", PtrId.c_str()); + return false; + } + Ptr = new CustomPointer; + if(!Ptr->Init(Offsetting, Size, HeaderSize)) + { + Logger.ReportError(CurrentLine, "Invalid size parameter for CREATEPTR"); + return false; + } + if(!Ptr->SetAddressType(AddressType)) + { + Logger.ReportError(CurrentLine, "Invalid address type for CREATEPTR"); + return false; + } + + Map->SetVarData(PtrId, Ptr, P_CUSTOMPOINTER); + return true; +} + +unsigned int PointerHandler::GetPtrSize(string& PtrId) +{ + CustomPointer* Ptr = (CustomPointer*)Map->GetVar(PtrId)->GetData(); + if(Ptr == NULL) // Uninitialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with CREATEPTR", PtrId.c_str()); + return -1; + } + + return Ptr->GetSize(); +} + +unsigned int PointerHandler::GetPtrAddress(std::string& PtrId, unsigned int ScriptPos, + unsigned int& Size) +{ + CustomPointer* Ptr = (CustomPointer*)Map->GetVar(PtrId)->GetData(); + if(Ptr == NULL) // Uninitialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with CREATEPTR", PtrId.c_str()); + return -1; + } + unsigned int Address = Ptr->GetAddress(ScriptPos); + Size = Ptr->GetSize(); + return Address; +} + +bool PointerHandler::CreatePointerList(std::string& ListId, const char* Filename, + std::string& PtrId) +{ + PointerList* List = (PointerList*)Map->GetVar(ListId)->GetData(); + if(List != NULL) // Already initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has already been allocated", ListId.c_str()); + return false; + } + CustomPointer* Ptr = (CustomPointer*)Map->GetVar(PtrId)->GetData(); + if(Ptr == NULL) + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with CREATEPTR", PtrId.c_str()); + return false; + } + + List = new PointerList; + bool Success = List->Create(Filename, *Ptr); + Map->SetVarData(ListId, List, P_POINTERLIST); + return Success; +} + +unsigned int PointerHandler::GetListAddress(std::string& ListId, unsigned int ScriptPos, unsigned int& Size, unsigned int& WritePos) +{ + PointerList* List = (PointerList*)Map->GetVar(ListId)->GetData(); + if(List == NULL) // Not initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with PTRLIST", ListId.c_str()); + return -1; + } + return List->GetAddress(ScriptPos, Size, WritePos); +} + +bool PointerHandler::CreatePointerTable(std::string& TblId, unsigned int Start, + unsigned int Increment, std::string& PtrId) +{ + PointerTable* Tbl = (PointerTable*)Map->GetVar(TblId)->GetData(); + if(Tbl != NULL) // Already allocated + { + Logger.ReportError(CurrentLine, "Identifier %s has already been allocated", TblId.c_str()); + return false; + } + CustomPointer* Ptr = (CustomPointer*)Map->GetVar(PtrId)->GetData(); + if(Ptr == NULL) + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with CREATEPTR", PtrId.c_str()); + return false; + } + Tbl = new PointerTable; + Tbl->Create(Increment, Start, *Ptr); + Map->SetVarData(TblId, Tbl, P_POINTERTABLE); + return true; +} + +bool PointerHandler::CreateEmbPointerTable(string& TblId, unsigned int Start, unsigned int PtrCount, string& PtrId) +{ + EmbPointerTable* Tbl = (EmbPointerTable*)Map->GetVar(TblId)->GetData(); + if(Tbl != NULL) // Already allocated + { + Logger.ReportError(CurrentLine, "Identifier %s has already been allocated", TblId.c_str()); + return false; + } + CustomPointer* Ptr = (CustomPointer*)Map->GetVar(PtrId)->GetData(); + if(Ptr == NULL) + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with CREATEPTR", PtrId.c_str()); + return false; + } + + Tbl = new EmbPointerTable; + Tbl->Create(Start, PtrCount, *Ptr); + Map->SetVarData(TblId, Tbl, P_EMBPOINTERTABLE); + return true; +} + +unsigned int PointerHandler::GetEmbTableAddress(string& TblId, unsigned int ScriptPos, + unsigned int& Size, unsigned int& WritePos) +{ + EmbPointerTable* Tbl = (EmbPointerTable*)Map->GetVar(TblId)->GetData(); + if(Tbl == NULL) // Not initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with EMBPTRTBL", TblId.c_str()); + return -1; + } + return Tbl->GetAddress(ScriptPos, Size, WritePos); +} + +unsigned int PointerHandler::GetEmbTableAddress(string& TblId, unsigned int ScriptPos, unsigned int PtrNum, + unsigned int& Size, unsigned int& WritePos) +{ + EmbPointerTable* Tbl = (EmbPointerTable*)Map->GetVar(TblId)->GetData(); + if(Tbl == NULL) // Not initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with EMBPTRTBL", TblId.c_str()); + return -1; + } + return Tbl->GetAddress(ScriptPos, PtrNum, Size, WritePos); +} + +unsigned int PointerHandler::GetTableAddress(std::string& TblId, unsigned int ScriptPos, + unsigned int& Size, unsigned int& WritePos) +{ + PointerTable* Tbl = (PointerTable*)Map->GetVar(TblId)->GetData(); + if(Tbl == NULL) // Not initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with PTRTBL", TblId.c_str()); + return -1; + } + return Tbl->GetAddress(ScriptPos, Size, WritePos); +} + +unsigned int PointerHandler::GetTableAddress(std::string& TblId, unsigned int ScriptPos, unsigned int PtrNum, + unsigned int& Size, unsigned int& WritePos) +{ + PointerTable* Tbl = (PointerTable*)Map->GetVar(TblId)->GetData(); + if(Tbl == NULL) // Not initialized + { + Logger.ReportError(CurrentLine, "Identifier %s has not been initialized with PTRTBL", TblId.c_str()); + return -1; + } + return Tbl->GetAddress(ScriptPos, PtrNum, Size, WritePos); +} + +PointerList::PointerList() +{ + Location = 0; +} + +PointerList::~PointerList() +{ +} + +bool PointerList::Create(const char* Filename, CustomPointer& CustPointer) +{ + ifstream Input(Filename); + if(!Input.is_open()) + { + // File Error + return false; + } + + string Line; + size_t FirstPos = 0; + bool bRet = true; + unsigned int Res = 0; + + unsigned int CurLine = 1; + while(!Input.eof()) + { + getline(Input, Line); + FirstPos = Line.find_first_not_of(" \t", 0); + + if(FirstPos == string::npos) // Whitespace line + continue; + + if(Line.length() > FirstPos+1) + if(Line[FirstPos] == '/' && Line[FirstPos] == '/') // Comment + continue; + + // Trim trailing whitespace + size_t Last; + for(Last = Line.length() - 1; Last > 0; Last--) + if(Line[Last] != ' ' && Line[Last] != '\t') + break; + if(Last < Line.length()) + Line.erase(Last+1); + + if(Line[FirstPos] == '$') + { + FirstPos++; + if(string::npos == Line.find_first_not_of("0123456789ABCDEF", FirstPos)) + Res = strtoul(Line.substr(FirstPos, Line.length() - FirstPos).c_str(), NULL, 16); + else + { + Logger.ReportError(CurLine, "Error parsing %s in %s", Line.c_str(), Filename); + bRet = false; + } + } + else + { + if(string::npos == Line.find_first_not_of("0123456789", FirstPos)) + Res = strtoul(Line.substr(FirstPos, Line.length() - FirstPos).c_str(), NULL, 10); + else + { + Logger.ReportError(CurLine, "Error parsing %s in %s", Line.c_str(), Filename); + bRet = false; + } + } + + LocationList.push_back(Res); + CurLine++; + } + + Input.close(); + LocationIt = LocationList.begin(); + Location = 0; + Pointer = CustPointer; + return bRet; +} + +unsigned int PointerList::GetAddress(unsigned int TextPosition, unsigned int& Size, unsigned int& WritePos) +{ + if(Location < LocationList.size()) + { + Size = Pointer.GetSize(); + WritePos = *LocationIt; + LocationIt++; + Location++; + return Pointer.GetAddress(TextPosition); + } + else + return -1; +} + + + +PointerTable::PointerTable() +{ + Increment = 0; + CurOffset = 0; +} + +PointerTable::~PointerTable() +{ +} + +bool PointerTable::Create(unsigned int Inc, unsigned int StartOffset, CustomPointer& CustPointer) +{ + Increment = Inc; + CurOffset = StartOffset; + Pointer = CustPointer; + TableStart = StartOffset; + return true; +} + +unsigned int PointerTable::GetAddress(unsigned int TextPosition, unsigned int& Size, unsigned int& WritePos) +{ + Size = Pointer.GetSize(); + WritePos = CurOffset; + CurOffset += Increment; + return Pointer.GetAddress(TextPosition); +} + +unsigned int PointerTable::GetAddress(unsigned int TextPosition, unsigned int PtrNum, + unsigned int& Size, unsigned int& WritePos) +{ + Size = Pointer.GetSize(); + + WritePos = TableStart + (PtrNum * Increment); + CurOffset = WritePos + Increment; + return Pointer.GetAddress(TextPosition); +} + +EmbPointerTable::EmbPointerTable() +{ + TableStart = 0; + CurPointer = 0; + PtrCount = 0; +} + +EmbPointerTable::~EmbPointerTable() +{ +} + +bool EmbPointerTable::Create(unsigned int StartOffset, unsigned int PointerCount, CustomPointer& CustPointer) +{ + TableStart = StartOffset; + CurPointer = 0; + PtrCount = PointerCount; + Pointer = CustPointer; + return true; +} + +unsigned int EmbPointerTable::GetAddress(unsigned int TextPosition, unsigned int &Size, unsigned int &WritePos) +{ + if(CurPointer >= PtrCount) + return -1; // Out of bounds + Size = Pointer.GetSize(); + + WritePos = TableStart + CurPointer*(Size/8); + CurPointer++; + return Pointer.GetAddress(TextPosition); +} + +unsigned int EmbPointerTable::GetAddress(unsigned int TextPosition, unsigned int PtrNum, + unsigned int &Size, unsigned int &WritePos) +{ + if(PtrNum >= PtrCount) + return -1; // Out of bounds + + Size = Pointer.GetSize(); + + WritePos = TableStart + PtrNum * (Size/8); + CurPointer = PtrNum+1; + return Pointer.GetAddress(TextPosition); +} \ No newline at end of file diff --git a/PointerHandler.h b/PointerHandler.h new file mode 100644 index 0000000..82fa6d0 --- /dev/null +++ b/PointerHandler.h @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include "Pointer.h" +#include "GenericVariable.h" + +using namespace std; + +class PointerHandler +{ +public: + PointerHandler(VariableMap* Map); + bool CreatePointer(string& PtrId, string& AddressType, + __int64 Offsetting, unsigned int Size, unsigned int HeaderSize); + unsigned int GetPtrAddress(string& PtrId, unsigned int ScriptPos, unsigned int& Size); + bool CreatePointerList(string& ListId, const char* Filename, string& PtrId); + bool CreatePointerTable(string& TblId, unsigned int Start, unsigned int Increment, + string& PtrId); + bool CreateEmbPointerTable(string& TblId, unsigned int Start, unsigned int PtrCount, string& PtrId); + unsigned int GetListAddress(string& ListId, unsigned int ScriptPos, + unsigned int& Size, unsigned int& WritePos); + unsigned int GetTableAddress(string& TblId, unsigned int ScriptPos, + unsigned int& Size, unsigned int& WritePos); + unsigned int GetTableAddress(string& TblId, unsigned int ScriptPos, + unsigned int PtrNum, unsigned int& Size, unsigned int& WritePos); + + unsigned int GetEmbTableAddress(string& TblId, unsigned int ScriptPos, unsigned int& Size, unsigned int& WritePos); + unsigned int GetEmbTableAddress(string& TblId, unsigned int ScriptPos, unsigned int PtrNum, + unsigned int& Size, unsigned int& WritePos); + unsigned int GetPtrSize(string& PtrId); +private: + VariableMap* Map; +}; + +class PointerList +{ +public: + PointerList(); + ~PointerList(); + + bool Create(const char* Filename, CustomPointer& CustPointer); + unsigned int GetAddress(unsigned int TextPosition, unsigned int& Size, unsigned int& WritePos); +private: + std::list LocationList; + std::list::iterator LocationIt; + unsigned int Location; + CustomPointer Pointer; +}; + +class PointerTable +{ +public: + PointerTable(); + ~PointerTable(); + bool Create(unsigned int Inc, unsigned int StartOffset, CustomPointer& CustPointer); + unsigned int GetAddress(unsigned int TextPosition, unsigned int& Size, unsigned int& WritePos); + unsigned int GetAddress(unsigned int TextPosition, unsigned int PtrNum, unsigned int& Size, unsigned int& WritePos); + +private: + unsigned int Increment; + unsigned int CurOffset; + unsigned int TableStart; + CustomPointer Pointer; +}; + +class EmbPointerTable +{ +public: + EmbPointerTable(); + ~EmbPointerTable(); + + bool Create(unsigned int StartOffset, unsigned int PointerCount, CustomPointer& CustPointer); + unsigned int GetAddress(unsigned int TextPosition, unsigned int& Size, unsigned int& WritePos); + unsigned int GetAddress(unsigned int TextPosition, unsigned int PtrNum, + unsigned int &Size, unsigned int &WritePos); + +private: + unsigned int TableStart; + unsigned int CurPointer; + unsigned int PtrCount; + CustomPointer Pointer; +}; \ No newline at end of file diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000..06b5d1b --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1,32 @@ +======================================================================== + CONSOLE APPLICATION : Atlas Project Overview +======================================================================== + +AppWizard has created this Atlas application for you. +This file contains a summary of what you will find in each of the files that +make up your Atlas application. + + +Atlas.vcproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +Atlas.cpp + This is the main application source file. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named Atlas.pch and a precompiled types file named StdAfx.obj. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" comments to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/Table.cpp b/Table.cpp new file mode 100644 index 0000000..830983e --- /dev/null +++ b/Table.cpp @@ -0,0 +1,639 @@ +//----------------------------------------------------------------------------- +// Table - A table library by Klarth, http://rpgd.emulationworld.com/klarth +// email - stevemonaco@hotmail.com +// Open source and free to use +//----------------------------------------------------------------------------- + +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include "Table.h" + +using namespace std; + +Table::Table() +{ + TblEntries = 0; + memset(LongestText, 0, 256*4); + LongestText[(int)'<'] = 5; // Length of <$XX> + LongestText[(int)'('] = 5; // Length of ($XX) + LongestHex = 1; + StringCount = 0; + bAddEndToken = true; +} + +Table::~Table() +{ + // Clear Errors + if(!Errors.empty()) + Errors.clear(); + + // Clear the map + if(!LookupHex.empty()) + LookupHex.clear(); +} + + + +//----------------------------------------------------------------------------- +// EncodeStream() - Encodes text in a vector to the string tables +//----------------------------------------------------------------------------- + +unsigned int Table::EncodeStream(string& scriptbuf, unsigned int& BadCharOffset) +{ + TBL_STRING TblString; + TXT_STRING TxtString; + string hexstr; + string subtextstr; + unsigned char i; + unsigned int EncodedSize = 0; + bool bIsEndToken = false; + std::map::iterator mapit; + unsigned int BufOffset = 0; + + hexstr.reserve(LongestHex * 2); + + if(scriptbuf.empty()) + return 0; + + if(!StringTable.empty()) + { + TBL_STRING RestoreStr = StringTable.back(); + if(RestoreStr.EndToken.empty()) // No end string...restore and keep adding + { + StringTable.pop_back(); + TblString.Text = RestoreStr.Text; + TxtString = TxtStringTable.back(); + TxtStringTable.pop_back(); + } + } + + while(BufOffset < scriptbuf.size()) // Translate the whole buffer + { + bIsEndToken = false; + i = LongestText[(unsigned char)scriptbuf[BufOffset]]; // Use LUT + while(i) + { + subtextstr = scriptbuf.substr(BufOffset, i); + mapit = LookupHex.find(subtextstr); + if(mapit == LookupHex.end()) // if the entry isn't found + { + i--; + continue; + } + + hexstr = mapit->second; + TxtString.Text += subtextstr; + + // Search to see if it's an end token, if it is, add to the string table + for(unsigned int j = 0; j < EndTokens.size(); j++) + if(EndTokens[j] == subtextstr) + { + bIsEndToken = true; + if(bAddEndToken) + AddToTable(hexstr, &TblString); + + TxtString.EndToken = subtextstr; + TblString.EndToken = subtextstr; + EncodedSize += (unsigned int)TblString.Text.size(); + TxtStringTable.push_back(TxtString); + StringTable.push_back(TblString); + TxtString.EndToken = ""; + TxtString.Text.clear(); + TblString.EndToken = ""; + TblString.Text.clear(); + break; // Only once + } + + if(!bIsEndToken) + AddToTable(hexstr, &TblString); + + BufOffset += i; + break; // Entry is finished + } + if (i == 0) // no entries found + { + BadCharOffset = BufOffset; + return -1; + } + } + + // Encode any extra data that doesn't have an EndToken + if(!TblString.Text.empty()) + StringTable.push_back(TblString); + if(!TxtString.Text.empty()) + TxtStringTable.push_back(TxtString); + + EncodedSize += (unsigned int)TblString.Text.size(); + + scriptbuf.clear(); + + return EncodedSize; +} + +inline void Table::InitHexTable() +{ + char textbuf[16]; + char hexbuf[16]; + + for(unsigned int i = 0; i < 0x100; i++) + { + sprintf(textbuf, "<$%02X>", i); + sprintf(hexbuf, "%02X", i); + LookupHex.insert(map::value_type(string(textbuf), string(hexbuf))); + // WindHex style hex codes + sprintf(textbuf, "($%02X)", i); + LookupHex.insert(map::value_type(string(textbuf), string(hexbuf))); + } + for(unsigned int i = 0x0A; i < 0x100; i += 0x10) + { + for(unsigned int j = 0; j < 6; j++) + { + sprintf(textbuf, "<$%02x>", i+j); + sprintf(hexbuf, "%02X", i+j); + LookupHex.insert(map::value_type(string(textbuf), string(hexbuf))); + // WindHex style hex codes (shouldn't be necessary for lowercase, though) + sprintf(textbuf, "($%02x)", i); + LookupHex.insert(map::value_type(string(textbuf), string(hexbuf))); + } + } +} + +//----------------------------------------------------------------------------- +// GetHexValue() - Returns a Hex value from a Text string from the table +//----------------------------------------------------------------------------- + +inline string& Table::GetHexValue(string& Textstring) +{ + return (LookupHex.find(Textstring))->second; +} + +//----------------------------------------------------------------------------- +// OpenTable() - Opens, Parses, and Loads a file to memory +//----------------------------------------------------------------------------- + +int Table::OpenTable(const char* TableFilename) +{ + string HexVal; + char testchar; + string TextString; + + LineNumber = 1; + LookupHex.clear(); + InitHexTable(); + + ifstream TblFile(TableFilename); + if(!TblFile.is_open()) // File can't be opened + return TBL_OPEN_ERROR; + + unsigned char utfheader[4]; + // Detect UTF-8 header + if(TblFile.peek() == 0xEF) + { + TblFile.read((char*)utfheader, 3); + if(utfheader[0] != 0xEF || utfheader[1] != 0xBB || utfheader[2] != 0xBF) + TblFile.seekg(ios::beg); // Seek beginning, not a UTF-8 header + } + + // Read the Table File until eof + while(!TblFile.eof()) + { + HexVal.clear(); + TextString.clear(); + // Read the hex number, skip whitespace, skip equal sign + parsews(TblFile); + + TblFile.get(testchar); + if(TblFile.eof()) + break; + TblFile.seekg(-1, ios::cur); + + switch(testchar) + { + case '(': + if(parsebookmark(TblFile)) + break; + else + return TBL_PARSE_ERROR; + case '[': + parsescriptdump(TblFile); + break; + case '{': + parsescriptinsert(TblFile); + break; + case '/': + if(parseendstring(TblFile)) + { + TblEntries++; + break; + } + else + return TBL_PARSE_ERROR; + case '*': + if(parseendline(TblFile)) + { + TblEntries++; + break; + } + else + return TBL_PARSE_ERROR; + case '$': case '!': case '@': // Skip line, linked/dakuten/handakuten entries not supported + while(TblFile.get() != '\n' && !TblFile.eof()); + break; + default: + if(parseentry(TblFile)) + { + break; + TblEntries++; + } + else + return TBL_PARSE_ERROR; + } + + } // End table reading loop + + return TBL_OK; +} + +//----------------------------------------------------------------------------- +// parsebookmark() - Parses a bookmark like (8000h)Text1 +//----------------------------------------------------------------------------- + +inline bool Table::parsebookmark(ifstream& file) +{ + char testch; + string bookname; + string hexaddress; + unsigned int address; + + file.get(testch); // should be '(' + + while(true) + { + file.get(testch); + if((file.eof()) || (testch == 'h') || (testch == 'H') || (testch == '\n')) + break; + hexaddress += testch; + } + + // Convert a hex string to an unsigned long + address = strtoul(hexaddress.c_str(), NULL, 16); + + parsews(file); + file.get(testch); // should be ')' + if(testch != ')') + return false; + + parsews(file); + + // Get the name + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof()) + break; + bookname += testch; + } + + TBL_BOOKMARK bookmark; + bookmark.address = address; + bookmark.description = bookname; + + Bookmarks.push_back(bookmark); + + return true; +} + +//----------------------------------------------------------------------------- +// parseendline() - parses a break line table value: ex, *FE +// You can also define messages like *FE= +//----------------------------------------------------------------------------- + +inline bool Table::parseendline(ifstream& file) +{ + char testch; + string hexstr, textstr; + + file.get(testch); // the * + parsews(file); + + // Get the hex + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof() || (testch == '=')) + break; + hexstr += testch; + } + + if(testch != '=') // normal entry + { + // Add to the map + LookupHex[DefEndString] = hexstr; + } + else + { + // Get what the string is + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof()) + break; + textstr += testch; + } + + // Add custom message to the map + LookupHex[textstr] = hexstr; + } + + return true; +} + +//----------------------------------------------------------------------------- +// parseendstring() - parses a string break table value +// Only ones like /FF= +// and / (gives a blank string value) +//----------------------------------------------------------------------------- + +inline bool Table::parseendstring(ifstream& file) +{ + char testch; + string hexstr, textstr; + + file.get(testch); // the / + parsews(file); + + // Get the first part + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof() || (testch == '=')) + break; + hexstr += testch; + } + + if(testch == '\n' || file.eof()) // Must be a blank value string (/) + { + size_t Pos = hexstr.find_first_of("0123456789ABCDEF"); + if(Pos == 0) + return false; + textstr = hexstr; + hexstr.clear(); + } + else if(testch == '=') // Must be a /FF= type string + { + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof()) + break; + textstr += testch; + } + } + else + return false; + + // Add custom string to the map + LookupHex[textstr] = hexstr; + EndTokens.push_back(textstr); + + return true; +} + + + +//----------------------------------------------------------------------------- +// parseentry() - parses a hex = text line +//----------------------------------------------------------------------------- + +inline bool Table::parseentry(ifstream& file) +{ + char testch; + string Hex, Text; + + // get the hex + while(true) + { + file.get(testch); + if((testch == SPACE) || (testch == '=')) + break; + else + Hex += testch; + } + + // get the equal sign + if(testch != '=') + { + parsews(file); + file.get(testch); + if(testch != '=') + return false; // bad formatting + } + + // get the value + file.get(testch); + while(!file.eof() && testch != '\n') + { + Text += testch; + file.get(testch); + } + + // Hex entries are strings, so divide the length by two to get the bytes + if(Hex.length() & 1) // Not a 8n bit hex number + Hex.insert(0, "0"); + if((Hex.length() >> 1) > LongestHex) + LongestHex = ((unsigned int)Hex.length() / 2); + + // Get the longest text string + if(Text.length() > LongestText[(unsigned char)Text[0]]) + LongestText[(unsigned char)Text[0]] = (int)Text.length(); + + LookupHex.insert(std::map::value_type(Text, Hex)); + + return true; +} + + + +//----------------------------------------------------------------------------- +// parsescriptdump() - Parses a script dump entry, like [8000h-8450h]Block 1 +//----------------------------------------------------------------------------- + +inline bool Table::parsescriptdump(ifstream& file) +{ + char testch; + unsigned int HexAddr1, HexAddr2; + string HexOff1, HexOff2, Description; + TBL_DUMPMARK dumpmark; + + file.get(testch); // the '[' + + // The first hex entry + while(true) + { + file.get(testch); + if((file.eof()) || (testch == '-') || (testch == '\n')) + break; + HexOff1 += testch; + } + + HexAddr1 = strtoul(HexOff1.c_str(), NULL, 16); + parsews(file); + + // The second hex entry + while(true) + { + file.get(testch); + if((file.eof()) || (testch == ']') || (testch == '\n')) + break; + HexOff2 += testch; + } + + HexAddr2 = strtoul(HexOff2.c_str(), NULL, 16); + parsews(file); + + // The name of the scriptdump + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof()) + break; + Description += testch; + } + + if(HexAddr1 <= HexAddr2) + { + dumpmark.StartAddress = HexAddr1; + dumpmark.EndAddress = HexAddr2; + } + else + { + dumpmark.StartAddress = HexAddr2; + dumpmark.EndAddress = HexAddr1; + } + + dumpmark.description = Description; + + Dumpmarks.push_back(dumpmark); + + return 1; +} + + + +//----------------------------------------------------------------------------- +// parsescriptinsert() - Parses script insert bookmarks +// ex - {8000h-TextDump.txt}Block-e 1 +//----------------------------------------------------------------------------- + +inline bool Table::parsescriptinsert(ifstream& file) +{ + char testch; + int HexAddress; + string HexOff, FileName, Description; + TBL_INSMARK insertmark; + + file.get(testch); // { + + // Get the hex offset + while(true) + { + file.get(testch); + if((file.eof()) || (testch == '-') || (testch == '\n')) + break; + HexOff += testch; + } + + HexAddress = strtoul(HexOff.c_str(), NULL, 16); + parsews(file); + + // Get the filename + while(true) + { + file.get(testch); + if((file.eof()) || (testch == ')') || (testch == '\n')) + break; + HexOff += testch; + } + + parsews(file); + + // Get the description + while(true) + { + file.get(testch); + if((testch == '\n') || file.eof()) + break; + Description += testch; + } + + insertmark.address = HexAddress; + insertmark.filename = FileName; + insertmark.description = Description; + + Insertmarks.push_back(insertmark); + + return 1; +} + + + +//----------------------------------------------------------------------------- +// parsews() - Eats all blanks and eoln's until a valid character or eof +//----------------------------------------------------------------------------- + +inline void Table::parsews(ifstream& file) +{ + char testch; + do{ + file.get(testch); + if(testch == '\n') + LineNumber++; + }while(((testch == SPACE) || (testch == '\n')) && (!file.eof())); + + if((!file.eof()) || (testch != '\n')) + file.seekg(-1, ios::cur); +} + + + +//----------------------------------------------------------------------------- +// HexToDec(...) - Converts a hex character to its dec equiv +//----------------------------------------------------------------------------- + +inline unsigned short HexToDec(char HexChar) +{ + switch(HexChar) + { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + } + + // Never gets here + printf("badstr"); + return 15; +} + +inline void Table::AddToTable(string& Hexstring, TBL_STRING* TblStr) +{ + for(unsigned int k = 0; k < Hexstring.length(); k+=2) + TblStr->Text += (HexToDec(Hexstring[k+1]) | (HexToDec(Hexstring[k]) << 4)); +} \ No newline at end of file diff --git a/Table.h b/Table.h new file mode 100644 index 0000000..b3fee4a --- /dev/null +++ b/Table.h @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// Return Messages +//----------------------------------------------------------------------------- +#pragma once + +#define TBL_OK 0x00 // Success +#define TBL_OPEN_ERROR 0x01 // Cannot open the Table properly +#define TBL_PARSE_ERROR 0x02 // Cannot parse how the Table is typed +#define NO_MATCHING_ENTRY 0x10 // There was an entry that cannot be matched in the table + +// Other +#define SPACE 0x20 + +#include "stdafx.h" +#include +#include +#include +#include + +// Structure for errors +typedef struct TBL_ERROR +{ + unsigned int LineNo; // The line number which the error occurred + std::string ErrorDesc; // A description of what the error was +} TBL_ERROR; + +// Data Structure for a table bookmark +typedef struct TBL_BOOKMARK +{ + unsigned int address; + std::string description; +} TBL_BOOKMARK; + +// Data Structure for a script dump bookmark +typedef struct TBL_DUMPMARK +{ + unsigned int StartAddress; + unsigned int EndAddress; + std::string description; +} TBL_DUMPMARK; + +// Data Structure for a script insertion bookmark +typedef struct TBL_INSMARK +{ + unsigned int address; + std::string filename; + std::string description; +} TBL_INSMARK; + +// Data Structure for a script string +typedef struct TBL_STRING +{ + std::string Text; + std::string EndToken; +} TBL_STRING; + +// Data Structure for an unencoded (text) string +typedef struct TXT_STRING +{ + std::string Text; + std::string EndToken; +} TXT_STRING; + +typedef std::map StrStrMap; +typedef std::list::iterator ListTblStringIt; +typedef std::list::iterator ListTxtStringIt; + +//----------------------------------------------------------------------------- +// Table Interfaces +//----------------------------------------------------------------------------- + +class Table +{ +public: + Table(); + ~Table(); + + int OpenTable(const char* TableFilename); + unsigned int EncodeStream(std::string& scriptbuf, unsigned int& BadCharOffset); + + std::vector Errors; // Errors + std::vector Bookmarks; // Normal bookmarks + std::vector Dumpmarks; // Script dump bookmarks + std::vector Insertmarks; // Insertion bookmarks + std::list StringTable; // (Encoded) String table + std::list TxtStringTable; // Text String Table + std::vector EndTokens; // String end tokens + + std::map LookupHex; // for looking up hex values. (insertion) + + unsigned int StringCount; + bool bAddEndToken; + +private: + inline void InitHexTable(); + + inline bool parsebookmark(std::ifstream& file); + inline bool parseendline(std::ifstream& file); + inline bool parseendstring(std::ifstream& file); + inline bool parseentry(std::ifstream& file); + inline bool parsescriptinsert(std::ifstream& file); + inline bool parsescriptdump(std::ifstream& file); + inline void parsews(std::ifstream& file); + + inline std::string& GetHexValue(std::string& Textstring); + + inline void AddToTable(std::string& Hexstring, TBL_STRING* TblStr); + + std::string DefEndLine; + std::string DefEndString; + unsigned int LineNumber; // The line number that the library is reading + unsigned int TblEntries; // The number of table entries + unsigned int LongestHex; // The longest hex entry, in bytes + + unsigned int LongestText[256]; +}; + +inline unsigned short HexToDec(char HexChar); \ No newline at end of file diff --git a/UpgradeLog.XML b/UpgradeLog.XML new file mode 100644 index 0000000..adecc1b --- /dev/null +++ b/UpgradeLog.XML @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UpgradeLog2.XML b/UpgradeLog2.XML new file mode 100644 index 0000000..9d85cf5 --- /dev/null +++ b/UpgradeLog2.XML @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..0ce6784 --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// Atlas.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..090c6d5 --- /dev/null +++ b/stdafx.h @@ -0,0 +1,25 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include + +// TODO: reference additional headers your program requires here +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file