C/C++ Code Snippets / Discussion [EHM 2007]

Discuss all aspects of editing the data and databases in EHM here. Have a question about the EHM Editor, EHM Assistant, editing the .cfg files, hex editing the .dat or .db files? Want to tweak the EHM exe file to change league rules/structure, start date etc? This is the place!
Forum rules
This is the forum to discuss all aspects of editing the EHM data and tweaking the game.

Have a bug or feature request for the EHM Editor? Post them in the EHM Editor thread. Please start a new thread or post in another thread if you have a question about how to use the EHM Editor.

Given the large number of questions on similar topics, we ask that you start a new thread for a new question unless you can locate a similar question in an existing thread. This will hopefully ensure that similar questions do not get buried in large threads.

Useful links: EHM 1 Assistant (Download) | EHM 1 Editor (Download) | EHM 1 Editor Tutorials | Editing Rules & Structures Guide | Converting EHM 2004 / 2005 DBs to EHM 1 | Converting an EHM 2007 DB to EHM 1 | Extra_config.cfg | Import_config.cfg | Player Roles
User avatar
Manimal
TBL Admin Team
Posts: 6344
Joined: Thu Apr 24, 2008 4:01 am
Custom Rank: EHM Rosters Man
Favourite Team: Djurgårdens IF
Location: Karlstad, Sweden

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by Manimal »

NingDynasty wrote:The saved game structure would be awesome for future versions of rosters (saved game distribution instead of pre-game) we could distribute a 2012 saved game with authentic stats to start with instead of all this custom date stuff. Of course it'd need some type of online editing and an army to get it done.
Distributing a saved game is not so fun. The variables would turn out the same every time you play.
NingDynasty
Junior League
Posts: 27
Joined: Wed Feb 08, 2012 10:36 pm

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by NingDynasty »

Sure but if we have access to editing all that data you could just hit a quick executable that would put some randomization to the prospects in the file +/-30 PA or what not, so that you could have even more control over the potential ranges instead of just the standard -15,-14...-9,-8 ect.
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

NingDynasty wrote:Sure but if we have access to editing all that data you could just hit a quick executable that would put some randomization to the prospects in the file +/-30 PA or what not, so that you could have even more control over the potential ranges instead of just the standard -15,-14...-9,-8 ect.
Would that give us any benefit over editing the DB? At present, we tend to give players PAs in the negative ranges (i.e. -1 through to -15) in order to give that very same randomisation you mention. Each time you start up a new game, EHM random generates a precise PA from the negative numbers (e.g. a PA of -8 in the DB will result in EHM generating a PA between 130-160). The different random ranges of PAs tend to fit in reasonably well with the ranges of CAs we'd expect from different levels of player.

I really do agree that editing the saved game has some very neat potential, but I'm thinking more of fixing regens, exporting data and possibly creating a new form of CSD Patch. Editing data that is already accessible via the DB is probably not worth it because of the complications involved in accessing and modifying saved game data (i.e. the user will have to apply a patch each time he starts a new game rather than simply downloading a ready-made DB). Using saved games rather than a DB for distribution / roster updating is well worth avoiding, IMO. As Manimal says, you would lose all of the variation that you get from game to game (such as the random PAs).

I'll post the saved game structure later today. It's incomplete, but it's enough to at least get started with things.
NingDynasty
Junior League
Posts: 27
Joined: Wed Feb 08, 2012 10:36 pm

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by NingDynasty »

I guess I'm just thinking of a more complex algorithm. Has anyone done any work on the ISS Rankings formula?I'm sure there is some randomization to it but, I'm tired of seeing PK Subban as a top 5 ranked player. Reputation, CA and PA seem to be the key contributors though I might run a couple tests.
NingDynasty
Junior League
Posts: 27
Joined: Wed Feb 08, 2012 10:36 pm

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by NingDynasty »

Took me almost all day but I think I've started to get a handle on most of this. So I've created (probably something that already exists) a small program to dump the necessary info from the dat files to create a full list of the players in the format that Archi's EHM updater takes. Should allow for some very quick massive updates to any database that is available. It isn't anything special as it was just a project to get my feet under me. Once I figured out vectors are like php arrays it was smooth sailing.

I added two columns to the end of the file, first the team name and second the league name, which to this point is just a place holder as I haven't looked to see where/how the league name is linked to the team. Do anyone know how the leagues and teams are linked? Is it through the division/conference/league hierarchy? I figure having this with the filtering would allow for quick editing across the leagues.

Anyway if there are any tools that you guys would like to see for the pre-game databases I've got this stuff figured out I think. Archie do you have the info to get me started in playing with the sav files?

PS I hate C++/CLI :-p
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

Yeah vectors are the way to go. I rarely use arrays unless I absolutely have to. I don't think I have any suggestions in relation to editing the DB, but there is plenty that remains to be explored in the saved games.

I've been working on tidying up my sav file code. I should have it with you today. I thought it would be helpful if I posted my extractor code as well as the structure so as to give you a head start (so I'm tidying up the code so it's a little more readable/helpful). :thup:

In relation to the link between players, team names and league names, they're all linked via ID numbers. You'll find that this is the case for all links in the database. SI helpfully labelled all of these links with xxx_PTR. So whenever you see a field type ending in _PTR you know it links to an ID number from another structure. For example, if you look in the STAFF structure, you'll see three fields entitled:

Code: Select all

CLUBS_PTR	 StaffNationContracted;
CLUBS_PTR	 StaffClubContracted;
CLUBS_PTR	 StaffClubPlaying;
The CLUBS_PTR means that it points/links to the ID number from the CLUBS structure. If you look under the CLUB structure, you'll see the first field is entitled ClubID - and this is what the _PTR field from STAFF points to. Three important notes:

1) Specifically with regards to the CLUBS records, note that there are two files with this structure: club.dat and nat_club.dat. The first holds the standard teams and the second holds the international teams (e.g. Team Canada, etc). The ID numbers continue from club.dat and move over to nat_club.dat. E.g. if the ClubID for the final record in club.dat is 1,005 then the first record in nat_club.dat will have a ClubID of 1,006. Strictly speaking, you don't need to worry about nat_club.dat unless you want to output the StaffNationContracted field or if you want to add additional teams to the club.dat file (because the ClubIDs in the nat_club.dat file will need to be incremented in order to accommodate additional entries in club.dat). This same relationship/principle applies to club_comp.dat & nat_comp.dat and club_comp_history.dat & nation_comp_history.dat.

2) The first ID number in any struct is always zero.

3) If a _PTR field for a particular record is null, it is usually set to -1. Sometimes if there is more than one type of null, it may also be set to -2. For example, if a player is a UFA, his StaffClubContracted and StaffClubPlaying fields will each be set to -1.

If you want to see the league name of a player's contracted club, you effectively need to daisy-chain staff.dat, club.dat and club_comp.dat together. So the first step would be to load each field into vectors. Then you could do something like this (note I'm writing this straight into this post - I haven't tested it - let me know if you try the below and you get any errors):

Code: Select all

std::vector<CLUBS> clubs;
std::vector<CLUB_COMPS> club_comps;
std::vector<STAFF> staff;

//--- INSERT CODE TO LOAD THE APPROPRIATE DAT FILES INTO THE ABOVE VECTORS ---//

// Loop through every record in the staff vector to display each staff member's club and league (note this is going to be a fairly slow program because there are approx 40,000 staff in the DB and the below code outputs each record to the screen - the outputting to the screen slows things down)
for(auto itr = staff.begin(); itr < staff.end(); ++itr)
{
   // Check that the staff's club contracted ID is within range (this will prevent any crashes)
   if(itr.StaffClubContracted < 0 || itr.StaffClubContracted >= clubs.size())
   {
      std::cout << "UFA";
   }

   else
   {
      std::cout << clubs[itr.StaffClubContracted].ClubName;

      // Check that the league ID for the club is within range (this will prevent any crashes)
      if(clubs[itr.StaffClubContracted].ClubDivision < 0 || clubs[itr.StaffClubContracted].ClubDivision >= club_comps.size())
      {
         std::cout << " " << "N/A";
      }

      else
      {
         std::cout << " " << club_comps[clubs[itr.StaffClubContracted].ClubDivision].ClubCompName;
      }
   }

   std::cout << std::endl;

   break; // This will break the loop after the first iteration so as to avoid having to wait for 40,000 lines of text to be displayed. Delete this line if you do want to loop through all of the records in the DB
}
You'll see I've used an auto type to create an iterator for looping through the staff vector. Using an iterator is a lot easier than the other way. Using an iterator is the equivalent of doing this:

Code: Select all

for(unsigned int i = 0; i < staff.size(); ++i)
{
    // Check that the staff's club contracted ID is within range (this will prevent any crashes)
   if(staff[i].StaffClubContracted < 0 || if(staff[i].StaffClubContracted >= clubs.size())
   {
      [...]
}
Putting square brackets within square brackets to find the appropriate ID numbers and names for different things might be a little off-putting. If you find this confusing, you could alternatively do something like this:

Code: Select all

long club_contracted = itr.StaffClubContracted;
long club_contracted_division = itr[club_contracted].ClubDivision;

// AND THIS WOULD ALLOW YOU TO DO THIS:
std::cout << " " << club_comps[club_contracted_division].ClubCompName;

// RATHER THAN THIS:
std::cout << " " << club_comps[clubs[itr.StaffClubContracted].ClubDivision].ClubCompName;
Later today I'll post some code to help you read and write to dat files and csv files. I've written some generic code for these functions which saves a lot of time when coding.
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

The saved game structure is a little harder to get into a postable state than I thought (I've separated everything into classes). It's going to take some additional time to get it in a state which I can share.

In the mean time, here is a collection of functions I wrote for editing EHM 2007 I've called lib_ehm07. I've written some simple examples to show how the functions work. Hopefully these functions will save you a lot of time (e.g. you can load and save from/to the database using just one line of code). Follow the steps below:

IMPORTANT NOTE: You'll see once you've followed the instructions below that at the bottom of lib_ehm07.h there are implementations of the ReadDB and WriteDB function templates. The list isn't comprehensive. If you want to open or save a dat file which isn't listed there, you'll need to add an implementation for it. For example, if you wanted to open the currencies.dat file (which uses the CURRENCIES struct), you'd add this for ReadDB (and you'd need to add one for WriteDB if you want to use this function with currencies.dat):

Code: Select all

template bool ehm::ReadDB(const char [], std::vector<CURRENCIES> &);
1) Create a new project in MS Visual C++ (or whichever IDE you're using). Add a new cpp file to it and call it something like main.cpp. Then paste the following code into main.cpp:

Code: Select all

#define _WIN32_WINNT _WIN32_WINNT_WINXP // Helps with Windows XP compatibility
#pragma pack(1)

#include "lib_ehm07.h"

void exDateToEHM();
void exEHMToDate();
void exDBLoad();
void exDBSave();
void exCreateCSV();
void exLoadCSV();
void exRecalculateIndex();

int main()
{
	std::cout << "Examples of how to use the lib_ehm07 functions by Archibalduk" << std::endl
			  << "www.ehmtheblueline.com" << std::endl
			  << std::endl;

	std::cout << std::endl << "DATE TO EHM EXAMPLE:" << std::endl;
	exDateToEHM();

	std::cout << std::endl << "EHM DATE TO STANDARD DATE EXAMPLE:" << std::endl;
	exEHMToDate();

	std::cout << std::endl << "LOADING THE DATABASE EXAMPLE:" << std::endl;
	exDBLoad();

	std::cout << std::endl << "SAVING THE DATABASE EXAMPLE:" << std::endl;
	exDBSave();

	std::cout << std::endl << "LOADING A CSV EXAMPLE:" << std::endl;
	exLoadCSV();

	std::cout << std::endl << "RECALCULATE INDEX.DAT EXAMPLE:" << std::endl;
	exRecalculateIndex();

	std::cout << std::endl
			  << std::endl
			  << "Press ENTER to close this window.";

	std::cin.get();

	return 0;
}

// This is an example of how to convert a normal date to the SI DATE format.
void exDateToEHM()
{
	// This is where we'll store the EHM date once we've created it
	SI_DATE example;

	// Here is an example date (15 March 2013)
	short day = 15;
	short month = 3;
	short year = 2013;

	std::cout << "Standard date: " << day << " / " << month << " / " << year << std::endl;

	// We first need to check whether or not the year is a leap year. We can save this to our struct
	example.leap_year = ehm::LeapYearCheck(year);

	// Here we calculate the EHM date using the day, month and whether or not it is a leap year
	example.day = ehm::EHMDate(day, month, example.leap_year);

	// Finally, we need to add the year to the example struct
	example.year = year;
	
	std::cout << "EHM date: " << example.day << " - " << example.year << " - " << example.leap_year << std::endl;

	return;
}

// This is an example of how to convert an SI/EHM date to a standard date
void exEHMToDate()
{
	// These are the vectors in which we'll store the data
	std::vector<STAFF> staff;

	// This is how we load the database files into the vector
	ehm::ReadDB("staff.dat", staff);

	// This is where we'll store the converted date and month
	short day;
	short month;

	// Using the first record in staff.dat, we'll convert the staff member's DOB from SI/EHM format to a standard date
	ehm::StandardDate(staff[0].StaffDateOfBirth.day, day, month, staff[0].StaffDateOfBirth.leap_year);

	std::cout << "Standard date: " << day << " / " << month << " / " << staff[0].StaffDateOfBirth.year << std::endl; 

	return;
}

// This is an example of how to load a couple of database files to a vector.
void exDBLoad()
{
	// These are the vectors in which we'll store the data
	std::vector<STAFF> staff;
	std::vector<NAMES> first_names;
	std::vector<NAMES> second_names;
	std::vector<CLUBS> clubs;

	// This is how we load the database files into the vector
	ehm::ReadDB("staff.dat", staff);
	ehm::ReadDB("first_names.dat", first_names);
	ehm::ReadDB("second_names.dat", second_names);

	// Here we are first loading club.dat and then afterwards loading nat_club.dat and adding the national club data to the club vector
	ehm::ReadDB("club.dat", clubs);
	ehm::ReadDB("nat_club.dat", clubs);

	// Just as an example, let's output the first 10 entries from the vector onto the screen
	for(int i = 0; i < 10; ++i)
		std::cout << first_names[staff[i].StaffFirstName].Name << " " << second_names[staff[i].StaffSecondName].Name << std::endl;

	// The text stored in the database can appear with strange characters because of the way the game handles certain characters (e.g. carons)
	// To resolve this problem, you can convert EHM text to an ASCII string like this:
	std::string ascii_text; // This is where we'll put the converted ASCII text
	ascii_text = ehm::ASCII((std::string)first_names[0].Name, false); // The (std::string) cast must be included because the text in the EHM database are char arrays. The false means that the text won't be converted to lowercase. Setting this to false will change all of the text to lowercase

	std::cout << "ASCII Text Example: " << ascii_text << std::endl;

	return;
}

// This is an example of how to save data to a .dat file
void exDBSave()
{
	// This the vector in which we'll store the data
	std::vector<STAFF> staff;

	// This is how we load the database file into the vector
	ehm::ReadDB("staff.dat", staff);

	// --- DO WHATEVER YOU WANT WITH THE DATA HERE --- //

	// This is how we save the vector back into the database file
	// By adding true in the function, the dat file will be erased and replaced with the data from the vector (i.e. truncated)
	// If we added false to the function, the data from the vector would be added to the end to the dat file (i.e. appended)
	ehm::WriteDB("staff.dat", staff, true);

	// If have changed the number of files in the dat file, you MUST update index.dat like this:
	ehm::IndexUpdate();

	return;
}

// This just creates an example csv file for use with the LoadCSV() function.
void exCreateCSV()
{
	std::fstream file("example.csv", std::ios::out | std::ios::trunc);

	if(!file.is_open())
		return;

	char d = ehm::DelimiterLocale();

	file << "FIRST NAME" << d << "SURNAME" << d << "TEAM" << std::endl
		 << "Partick" << d << "Roy" << d << "Colorado Avalanche" << std::endl
		 << "Martin" << d << "Brodeur" << d << "New Jersey Devils" << std::endl
		 << "Byron" << d << "Dafoe" << d << "Boston Bruins" << std::endl
		 << "Stephane" << d << "Fiset" << d << "Los Angeles Kings" << std::endl
		 << "John" << d << "Vanbiesbrouck" << d << "Florida Panthers" << std::endl;

	file.close();

	return;
}

// This is an example of how to load a csv file and store the data into a vector.
void exLoadCSV()
{
	// For the purpose of this example, we first need to create a sample csv file to load.
	exCreateCSV();

	std::vector<std::vector<std::string>> buffer; // We need a vector of vectors in order to store the rows and columns of data

	// This loads example.csv and stores it in the vector named "buffer". This also states that there are 3 columns and 1 header row (so it knows to skip the first row in the csv)
	ehm::ReadCSV("example.csv", buffer, 3, 1);

	std::cout << "ROW 1: Column 1: " << buffer[0][0] << "\tCol 2: " << buffer[0][1] << "\tCol 3: " << buffer[0][2] << std::endl
			  << "ROW 2: Column 1: " << buffer[1][0] << "\tCol 2: " << buffer[1][1] << "\tCol 3: " << buffer[1][2] << std::endl
			  << "ROW 3: Column 1: " << buffer[2][0] << "\tCol 2: " << buffer[2][1] << "\tCol 3: " << buffer[2][2] << std::endl
			  << "ROW 4: Column 1: " << buffer[3][0] << "\tCol 2: " << buffer[3][1] << "\tCol 3: " << buffer[3][2] << std::endl
			  << "ROW 5: Column 1: " << buffer[4][0] << "\tCol 2: " << buffer[4][1] << "\tCol 3: " << buffer[4][2] << std::endl;

	// PLEASE NOTE: The data loaded to csv is stored as string text. If you want to convert parts of this text to a number, you'll need to use functions such as atoi() like this:
	// atoi(buffer[0][0].c_str());

	return;
}

// This is an example of how to update the index.dat file once you have finished adding/deleting records to the database.
void exRecalculateIndex()
{
	ehm::IndexUpdate();

	return;
}
2) Add the usual database_flags.h and database_types.h to your project. In MS Visual C++ you can do this by clicking on the Project menu -> Add Existing Item.

3) Add a new .h file and call it lib_ehm07.h. Paste in the following code:

Code: Select all

#ifndef LIB_EHM07_H
#define LIB_EHM07_H

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "database_flags.h"
#include "database_types.h"

// ehm:: namespace is used for all functions below
namespace ehm
{
	// CSV Functions
	char DelimiterDetect(const std::string &);
	char DelimiterLocale();
	void Parse(std::string[], const int &, const std::string &, const char &);
	void Parse(std::vector<std::string> &, const short &, const std::string &, const char &);
	bool ReadCSV(const char [], std::vector<std::vector<std::string>> &, const short &, const short &, const bool & = false);

	// Database I/O Functions
	template <typename T> bool ReadDB(const char [], std::vector<T> &);
	template <typename T> bool WriteDB(const char [], const std::vector<T> &, const bool & = false);

	// Date Functions
	short EHMDate(short &, short &, const bool &);
	bool LeapYearCheck(const short &);
	void StandardDate(short, short &, short &, const bool &);

	// General Functions
	long IndexSize(char [], unsigned int);
	bool IndexUpdate();

	// Text Functions
	std::string ASCII(std::string &, const bool & = true);
	void LowerCase(std::string &);
	std::string StripText(std::string &, const bool & = true);
}

/* ============================================================== */
/*      DATABASE I/O FUNCTIONS (IMPLEMENTATION OF TEMPLATES)      */
/* ============================================================== */

// Load a database .dat file for reading into a vector of structs
template bool ehm::ReadDB(const char [], std::vector<CITIES> &);
template bool ehm::ReadDB(const char [], std::vector<CLUBS> &);
template bool ehm::ReadDB(const char [], std::vector<CLUB_COMP_HISTORIES> &);
template bool ehm::ReadDB(const char [], std::vector<CLUB_COMPS> &);
template bool ehm::ReadDB(const char [], std::vector<DB_CLUB_RECORDS> &);
template bool ehm::ReadDB(const char [], std::vector<DB_PLAYER_INFO> &);
template bool ehm::ReadDB(const char [], std::vector<DB_PLAYER_RIGHTS> &);
template bool ehm::ReadDB(const char [], std::vector<DB_STAFF_INFO> &);
template bool ehm::ReadDB(const char [], std::vector<DRAFTED_PLAYERS> &);
template bool ehm::ReadDB(const char [], std::vector<NAMES> &);
template bool ehm::ReadDB(const char [], std::vector<NATIONS> &);
template bool ehm::ReadDB(const char [], std::vector<NON_PLAYERS> &);
template bool ehm::ReadDB(const char [], std::vector<OFFICIALS> &);
template bool ehm::ReadDB(const char [], std::vector<PLAYERS> &);
template bool ehm::ReadDB(const char [], std::vector<STAFF> &);
template bool ehm::ReadDB(const char [], std::vector<STAFF_COMPS> &);
template bool ehm::ReadDB(const char [], std::vector<STAFF_COMP_HISTORIES> &);
template bool ehm::ReadDB(const char [], std::vector<STAFF_HISTORIES> &);
template bool ehm::ReadDB(const char [], std::vector<STAFF_LANGUAGES> &);
template bool ehm::ReadDB(const char [], std::vector<STAFF_PREFERENCES> &);

// Write the vector of structs to a database .dat file
template bool ehm::WriteDB(const char [], const std::vector<CLUBS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<CLUB_COMP_HISTORIES> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<DB_CLUB_RECORDS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<DB_PLAYER_INFO> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<DB_PLAYER_RIGHTS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<DB_STAFF_INFO> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<DRAFTED_PLAYERS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<NAMES> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<NATIONS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<NON_PLAYERS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<OFFICIALS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<PLAYERS> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<STAFF> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<STAFF_COMP_HISTORIES> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<STAFF_HISTORIES> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<STAFF_LANGUAGES> &, const bool &);
template bool ehm::WriteDB(const char [], const std::vector<STAFF_PREFERENCES> &, const bool &);

#endif
4. Add a new .cpp file and call it lib_ehm07.cpp. Paste in the following code:

Code: Select all

#define _WIN32_WINNT _WIN32_WINNT_WINXP // Helps with Windows XP compatibility
#pragma pack(1)

#include "lib_ehm07.h"

/* ======================= */
/*      CSV FUNCTIONS      */
/* ======================= */

// --- Detect delimiter based up a string of text --- //
// ehm::DelimiterDetect(string of text)
char ehm::DelimiterDetect(const std::string &text)
{
	unsigned short count = 0;

	for(auto itr = text.begin(); itr < text.end(); ++itr)
		if(*itr == ';')
			++count;

	// If the number of semicolons is greater than zero, return the delimiter as semicolon; otherwise return it as comma
	return (count > 0) ? ';' : ','; 
}

// --- Determine default delimiter based upon system locale --- //
char ehm::DelimiterLocale()
{
	return (std::use_facet<std::numpunct<char>>(std::cout.getloc()).decimal_point() == '.') ? ',' : ';';
}

// --- Parse a string of delimited text (array method) --- //
void ehm::Parse(std::string string_buffer[], const int &max_element, const std::string &csv_string, const char &delimiter)
{
	std::stringstream csv_line(csv_string);

	// Parse each delimiter until the final delimiter
	for(int i = 0; i < (max_element-1); ++i)
		std::getline(csv_line, string_buffer[i], delimiter);

	// After the final delimiter, get the remaining text from the line
	getline(csv_line, string_buffer[max_element-1]);

	return;
}

// --- Parse a string of delimited text (vector method) --- //
void ehm::Parse(std::vector<std::string> &string_buffer, const short &columns, const std::string &csv_string, const char &delimiter)
{
	// Clear and resize the buffer before proceeding
	string_buffer.clear();
	string_buffer.resize(columns);

	// Stringstream the string of text
	std::stringstream csv_line(csv_string);

	// Parse each delimiter until the final delimiter
	for(int i = 0; i < columns; ++i)
		std::getline(csv_line, string_buffer[i], delimiter);

	// After the final delimiter, get the remaining text from the line
	getline(csv_line, string_buffer[columns-1]);

	return;
}

// --- Read a CSV delimited file into vector of strings --- //
// ehm::ReadCSV("filename.csv", vector to store unformatted string data, number of columns in csv, number of header rows in csv to ignore, false)
bool ehm::ReadCSV(const char filename[], std::vector<std::vector<std::string>> &vect, const short &columns, const short &header_rows, const bool &setting)
{
	std::cout << "Loading " + (std::string) filename << "\t";

	std::fstream file (filename, std::ios::in);

	// Abort if the file could not be opened
	if(!file.is_open())
	{
		std::cout << "ERROR" << std::endl;
		return false;
	}

	// Calculate file size
	file.seekg (0, std::ios::end);
	std::streamoff filesize = file.tellg();
	file.seekg (0, std::ios::beg);

	// Buffers
	std::string csv_string;				// Line of text from the csv file
	std::vector<std::string> buffer;	// Buffer for storing the parsed data
	buffer.reserve(columns);			// Reserve the correct number of elements in the buffer according to the number columns of data
	char d	= ',';						// Delimiter type. Default as comma to start with. It can be changed later using DelimiterDetect().

	if(setting == false)
	{
		// Skip the two header rows
		for(short i = 0; i < header_rows; ++i)
			std::getline(file, csv_string);

		// Detect the d based upon the header row
		d = ehm::DelimiterDetect(csv_string);
	}
	else
	{
		// Get the first header row...
		std::getline(file, csv_string);

		// Detect the d based upon the header row
		d = ehm::DelimiterDetect(csv_string);

		// ...and check the first column for the setting text
		ehm::Parse(buffer, columns, csv_string, d);

		// Skip and remaining header rows
		for(short i = 1; i < header_rows; ++i)
			std::getline(file, csv_string);
	}

	while(file.tellg() < filesize)
	{
		// Get the line of csv and parse it
		std::getline(file, csv_string);
		ehm::Parse(buffer, columns, csv_string, d);

		// Only add the data to the vector if both the first two columns of data are present (i.e. not empty)
		if(!buffer[0].empty() && !buffer[1].empty())
			vect.push_back(buffer);
	}

	file.close();

	std::cout << vect.size() << "\t" << "DONE" << std::endl;

	return true;
}


/* ================================ */
/*      DATABASE I/O FUNCTIONS      */
/* ================================ */

// --- Load a database .dat file and populate a vector of the relevant struct
// ehm::ReadDB("filename.dat", vector of structs);
template <typename T> bool ehm::ReadDB(const char filename[], std::vector<T> &vect)
{
	std::cout << "Loading " + (std::string) filename << "\t";

	std::fstream file (filename, std::ios::in | std::ios::binary);

	// Calculate file size
	file.seekg (0, std::ios::end);
	std::streamoff filesize = file.tellg();
	file.seekg (0, std::ios::beg);

	// Struct buffer
	T struct_buffer;

	// Abort if the file cannot be opened
	if(!file.is_open())
	{
		std::cout << "ERROR" << std::endl;
		return false;
	}

	// Add the data to the vector of structs
	while(file.tellg() < filesize)
	{
		file.read((char*)&struct_buffer, sizeof(T));
		vect.push_back(struct_buffer);
	}

	file.close();

	std::cout << vect.size() << "\t" << "DONE" << std::endl;

	return true;
}

// --- Open a database .dat file for writing and write a vector of structs to the file --- //
// ehm::WriteDB("filename.dat", vector of structs, true = erase existing contents / false = add contents of vector to end of file)
template <typename T> bool ehm::WriteDB(const char filename[], const std::vector<T> &vect, const bool &truncate)
{
	std::cout << "Saving " + (std::string) filename << "\t";

	// Open the file
	std::fstream file;

	// Truncate and write
	if(truncate == true)
		file.open(filename, std::fstream::out | std::fstream::trunc | std::ios::binary);

	// Append and write
	else
		file.open(filename,  std::ios::in | std::fstream::out | std::fstream::ate | std::ios::binary);

	// Abort if the file cannot be opened
	if(!file.is_open())
	{
		std::cout << "ERROR" << std::endl;
		return false;
	}

	// Write the vector of structs
	for(unsigned long i = 0; i < vect.size(); ++i)
		file.write((char*)&vect[i], sizeof(T));

	file.close();

	std::cout << vect.size() << "\t" << "DONE" << std::endl;

	return true;
}


/* ======================== */
/*      DATE FUNCTIONS      */
/* ======================== */

// --- Convert a standard date to an EHM date --- //
// ehm::EHMDate(date of month, month, true = the year is a leap year / false = not a leap year)
short ehm::EHMDate(short &day, short &month, const bool &leap_year)
{
	short month_lengths[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

	if(leap_year == true)
		month_lengths[1] = 29;

	short date = 0;

	// Prevent any dates out of range prior to processing.
	if(day < 1) day = 1;
	else if(day > 31) day = 31;

	if(month < 1) month = 1;
	else if(month > 12) month = 12;

	for(short i = 0; i < (month-1); ++i) date += month_lengths[i];

	// The first day of the year is zero. Therefore you must subtract one from the date.
	day += (date - 1);

	// Prevent any dates out of range after processing.
	if(day < 0) day = 0;
	else if(day > 365) day = 365;

	return day;
}

// --- Check whether a given year is a leap year --- //
// ehm::LeapYearCheck(year)
bool ehm::LeapYearCheck(const short &year)
{
	return((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
}

// --- Convert an EHM date to a standard date. ehm_day is passed by value because temporary changes must be made to it during the calculation process --- //
// ehm::StandardDate(ehm day value, destination for storing the day, destination for storing the month, ehm leap year value)
void ehm::StandardDate(short ehm_day, short &day, short &month, const bool &leap_year)
{
	short month_length[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	short month_days = 0;
	month = 0;

	if(leap_year == true) month_length[1] = 29;

	// The first SI_DATE day of the year is zero. Therefore you must add one to the si_days before conversion.
	++ehm_day;

	while(ehm_day > month_days) month_days += month_length[(month)++];

	day = ehm_day - month_days + month_length[(month)-1];

	// Prevent any dates out of range (well, at least the day won't exceed 31).
	if(day < 0) day = 0;
	else if(day > 31) day = 31;

	if(month < 1) month = 1;
	else if(month > 12) month = 12;

	return;
}


/* =========================== */
/*      GENERAL FUNCTIONS      */
/* =========================== */

// -- Calculate number of entries in a database file for updating index.dat --- //
// ehm::IndexSize("filename.dat", sizeof(STRUCT) - this is used via IndexUpdate() - you shouldn't really need to use this function directly
long ehm::IndexSize(char filename[], unsigned int structure)
{
	std::fstream file (filename, std::ios::in | std::ios::binary);

	if(!file.is_open())
		return -1;

	file.seekg (0, std::ios::end);
	return static_cast<unsigned int>(file.tellg()) / structure;
}

// -- Update index.dat --- //
bool ehm::IndexUpdate()
{
	// Array to store index values for each database file
	long index[35];

	std::cout << "Updating index.dat" << "\t";

	// Calculate index values for each database file
	index[0] = ehm::IndexSize("club.dat", sizeof(CLUBS));
	index[1] = ehm::IndexSize("nat_club.dat", sizeof(CLUBS));
	index[2] = ehm::IndexSize("colour.dat", sizeof(COLOURS));
	index[3] = ehm::IndexSize("continent.dat", sizeof(CONTINENTS));
	index[4] = ehm::IndexSize("nation.dat", sizeof(NATIONS));
	index[5] = ehm::IndexSize("officials.dat", sizeof(OFFICIALS));
	index[6] = ehm::IndexSize("stadium.dat", sizeof(ARENAS));
	index[7] = ehm::IndexSize("staff.dat", sizeof(STAFF));
	index[8] = ehm::IndexSize("nonplayer.dat", sizeof(NON_PLAYERS));
	index[9] = ehm::IndexSize("player.dat", sizeof(PLAYERS));
	index[10] = ehm::IndexSize("staff_comp.dat", sizeof(STAFF_COMPS));
	index[11] = ehm::IndexSize("city.dat", sizeof(CITIES));
	index[12] = ehm::IndexSize("club_comp.dat", sizeof(CLUB_COMPS));
	index[13] = ehm::IndexSize("nation_comp.dat", sizeof(CLUB_COMPS));
	index[14] = ehm::IndexSize("first_names.dat", sizeof(NAMES));
	index[15] = ehm::IndexSize("second_names.dat", sizeof(NAMES));
	index[16] = ehm::IndexSize("staff_history.dat", sizeof(STAFF_HISTORIES));
	index[17] = ehm::IndexSize("staff_comp_history.dat", sizeof(STAFF_COMP_HISTORIES));
	index[18] = ehm::IndexSize("club_comp_history.dat", sizeof(CLUB_COMP_HISTORIES));
	index[19] = ehm::IndexSize("nation_comp_history.dat", sizeof(CLUB_COMP_HISTORIES));
	index[20] = ehm::IndexSize("affiliations.dat", sizeof(AFFILIATIONS));
	index[21] = ehm::IndexSize("retired_numbers.dat", sizeof(RETIRED_NUMBERS));
	index[22] = ehm::IndexSize("states_provinces.dat", sizeof(STATES_PROVINCES));
	index[23] = ehm::IndexSize("injuries.dat", sizeof(INJURIES));
	index[24] = ehm::IndexSize("staff_preferences.dat", sizeof(STAFF_PREFERENCES));
	index[25] = ehm::IndexSize("currencies.dat", sizeof(CURRENCIES));
	index[26] = ehm::IndexSize("club_records.dat", sizeof(DB_CLUB_RECORDS));
	index[27] = ehm::IndexSize("club_histories.dat", sizeof(CLUB_HISTORIES));
	index[28] = ehm::IndexSize("drafts.dat", sizeof(DRAFTS));
	index[29] = ehm::IndexSize("drafted_players.dat", sizeof(DRAFTED_PLAYERS));
	index[30] = ehm::IndexSize("player_rights.dat", sizeof(DB_PLAYER_RIGHTS));
	index[31] = ehm::IndexSize("stage_names.dat", sizeof(STAGE_NAMES));
	index[32] = ehm::IndexSize("staff_languages.dat", sizeof(STAFF_LANGUAGES));
	index[33] = ehm::IndexSize("player_info.dat", sizeof(DB_PLAYER_INFO));
	index[34] = ehm::IndexSize("staff_info.dat", sizeof(DB_STAFF_INFO));

	std::cout << "N/A" << "\t";

	// Check for any errors returned from the IndexSize function (and abort before writing to index.dat)
	for(short i = 0; i < 35; ++i)
	{
		if(index[i] < 0)
		{
			std::cout << "ERROR" << std::endl << "Unable to open all .dat files" << std::endl;
			return false;
		}
	}

	// Write to index.dat
	std::fstream file_index ("index.dat", std::ios::in | std::ios::out | std::ios::binary);

	if(!file_index.is_open())
	{
		std::cout << "ERROR" << std::endl << "Unable to open index.dat" << std::endl;
		return false;
	}

	// File position marker
	std::streamoff pos = 63;

	for(short i = 0; i < 35; ++i)
	{
		file_index.seekp (pos, std::ios::beg);
		file_index.write ((char*)&index[i], sizeof(index[i]));
		pos += 63;
	}

	file_index.close();

	std::cout << "DONE" << std::endl;

	return true;
}


/* ======================== */
/*      TEXT FUNCTIONS      */
/* ======================== */

// --- Convert SI Font text to ASCII text. The bLowerCase bool enables conversion to lower case --- //
// ehm::ASCII(string of text, true = convert to lowercase / false = leave the case as it is)
std::string ehm::ASCII(std::string &ehm_text, const bool &lower_case)
{
	// Don't process the string if it is empty
	if(ehm_text.empty())
		return ehm_text;

	// Multidimensional array of SI font characters ([i][0]) and ASCII characters ([i][1])
	const int font[][2] =
	{		
		{ '\xBC', '\x43' },	// C - Caron - Uppercase
		{ '\xA0', '\x63' },	// c - Caron - Lowercase
		{ '\xBE', '\x44' },	// D - Caron - Uppercase
		{ '\xBB', '\x64' },	// d - Caron - Lowercase
		{ '\x86', '\x45' },	// E - Caron - Uppercase
		{ '\x90', '\x65' }, // e - Caron - Lowercase
		{ '\xB1', '\x4E' },	// N - Caron - Uppercase
		{ '\x8F', '\x6E' },	// n - Caron - Lowercase
		{ '\xB3', '\x52' },	// R - Caron - Uppercase
		{ '\xA7', '\x72' },	// r - Caron - Lowercase		
		{ '\x8A', '\x53' },	// S - Caron - Uppercase
		{ '\xBD', '\x53' },	// S - Caron - Uppercase
		{ '\x9A', '\x73' },	// s - Caron - Lowercase
		{ '\x87', '\x55' },	// U - Ring Above - Uppercase
		{ '\x9E', '\x7A' },	// z - Caron - Lowercase
		{ '\x9F', '\x7A' },	// z - Caron - Lowercase		
		{ '\x8E', '\x5A' },	// Z - Caron - Uppercase		
		{ ',', '_'},		// Comma delimiter
		{ ';', '_'}			// Semicolon delimiter
	};

	short font_size = sizeof(font)/sizeof(*font);

	// Check for SI Font characters
	for(short i = 0; i < font_size; ++i)
		replace(ehm_text.begin(), ehm_text.end(), font[i][0], font[i][1]);

	// Remove trademark symbol if present at the end of the string
	if(ehm_text[ehm_text.length()-1] == '\x99')
		ehm_text.erase(ehm_text.length()-1);

	// Convert the text to lower case
	if(lower_case == true)
		ehm::LowerCase(ehm_text);

	return ehm_text;
}

// --- Convert text to lowercase --- //
// ehm::LowerCase(string of text)
void ehm::LowerCase(std::string &text)
{
	// Multidimensional array of uppercase ([i][0]) to lowercase characters ([i][1])
	const int font[][2] =
	{
		{ '\xC0', '\xE0' },	// A - Grave - Uppercase
		{ '\xC1', '\xE1' },	// A - Acute - Uppercase
		{ '\xC2', '\xE2' },	// A - Circumflex - Uppercase
		{ '\xC3', '\xE3' },	// A - Tilde - Uppercase
		{ '\xC4', '\xE4' },	// A - Umlauts - Uppercase
		{ '\xC5', '\xE5' },	// A - Ring Above - Uppercase
		{ '\xC6', '\xE6' },	// AE - Joined - Uppercase
		{ '\xC7', '\xE7' },	// C - Cedilla - Uppercase
		{ '\xC8', '\xE8' },	// E - Grave - Uppercase
		{ '\xC9', '\xE9' },	// E - Acute - Uppercase
		{ '\xCA', '\xEA' },	// E - Circumflex - Uppercase
		{ '\xCB', '\xEB' },	// E - Umlauts - Uppercase
		{ '\xCC', '\xEC' },	// I - Grave - Uppercase
		{ '\xCD', '\xED' },	// I - Acute - Uppercase
		{ '\xCE', '\xEE' },	// I - Circumflex - Uppercase
		{ '\xCF', '\xEF' },	// I - Umlauts - Uppercase
		{ '\xD1', '\xF1' },	// N - Tilde - Uppercase
		{ '\xD2', '\xF2' },	// O - Grave - Uppercase
		{ '\xD3', '\xF3' },	// O - Acute - Uppercase
		{ '\xD4', '\xF4' },	// O - Circumflex - Uppercase
		{ '\xD5', '\xF5' },	// O - Tilde - Uppercase
		{ '\xD6', '\xF6' },	// O - Umlauts - Uppercase
		{ '\xD8', '\xF8' },	// O - Stroke - Uppercase
		{ '\xD9', '\xF9' },	// U - Grave - Uppercase
		{ '\xDA', '\xFA' },	// U - Acute - Uppercase
		{ '\xDB', '\xFB' },	// U - Circumflex - Uppercase
		{ '\xDC', '\xFC' },	// U - Umlauts - Uppercase
		{ '\xDD', '\xFD' },	// Y - Acute - Uppercase
		{ '\x9F', '\xFF' },	// Y - Umlauts - Uppercase
		{ '\xDE', '\xFE' }	// ? - Thorn - Uppercase
	};

	short font_size = sizeof(font)/sizeof(*font);

	// Convert text to lowercase
	int(*pf)(int)=tolower;
	transform(text.begin(), text.end(), text.begin(), pf);

	// Convert additional accented characters to lowercase
	for(short i = 0; i < font_size; ++i)
		replace(text.begin(), text.end(), font[i][0], font[i][1]);

	return;
}

// --- Strip ASCII text by converting carons and removing trailing 'TM' symbols. The lower_case bool enables conversion to lower case --- //
// ehm::StripText(string of text, true = convert to lowercase / false = leave the case as it is)
std::string ehm::StripText(std::string &text, const bool &lower_case)
{
	// Don't process the string if it is empty
	if(text.empty())
		return text;

	// Before proceeding, check for and remove any text qualifiers. Some software encloses text within speech marks or apostrophes in csv files
	if((text[0] == '\"' && text[text.length()-1] == '\"') || (text[0] == '\'' && text[text.length()-1] == '\''))
	{
		text.erase(text.begin());
		text.erase(text.length()-1);
	}

	// Multidimensional array of strip characters ([i][0]) and replace characters ([i][1])
	const int font[][2] =
	{		
		{ '\x8A', '\x53' },	// S - Caron - Uppercase
		{ '\x9A', '\x73' },	// s - Caron - Lowercase
		{ '\x8E', '\x5A' },	// Z - Caron - Uppercase
		{ '\x9E', '\x7A' }	// z - Caron - Lowercase
	};

	short font_size = sizeof(font)/sizeof(*font);

	// Check for bad characters
	for(short i = 0; i < font_size; ++i)
		replace(text.begin(), text.end(), font[i][0], font[i][1]);

	// Remove trademark symbol if present at the end of the string
	if(text[text.length()-1] == '\x99')
		text.erase(text.length()-1);

	// Convert the text to lower case
	if(lower_case == true)
		ehm::LowerCase(text);

	// If the text string consists of solely a space then erase it. Single space strings are likely to be an artefact from text qualified cells that were subsequently blanked in the spreadsheet software.
	if(text == " ")
		text.clear();

	return text;
}
5. You will need to add an example EHM database (such as one of the Manimal DBs) to your project folder. If you go to the My Documents -> Projects in Windows, you'll find a sub-folder with the same name as your project. Go into this folder and you'll see a further sub-folder with the same name as your project. Go into this folder and put your database dat files. For example, I called the project "EHM 2007 Examples" and so I placed the Manimal v5.3d database here: My Documents\Visual Studio 2012\Projects\EHM 2007 Examples\EHM 2007 Examples\

6. Complile your code by pressing F5.
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

It's getting far too complicated to post any code in relation to my Saved Game Explorer. It was a while ago when I first wrote it and it still has an annoying bug in it. I don't think I'll be able to post any code on it until I've done a lot more work on it. At some point in the future, when I've tidied it all up and got it working correctly, I'll post some code for you. However, the principles are pretty much the same as editing DB files and so if you can do that, you shouldn't have any problem with the saved game files.

I've posted what I've figured out about the structure here: http://www.ehmtheblueline.com/forums/vi ... 57#p155357 - Hopefully this will help you.
NingDynasty
Junior League
Posts: 27
Joined: Wed Feb 08, 2012 10:36 pm

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by NingDynasty »

Thanks Archi! By the time you posted up your library there and what not I had already completed my trial exporter. Works like a charm and spits out a perfect format acceptable by your updater, less my two extra columns at the end I can easily delete out before using. I ended up using classes myself just to easily "daisy chain" as you called it the player, team, league associations. Works wonderfully. Took me a minute to figure out why it wouldn't run on my home pc then downloaded the redistributable for MSVS 12.

So I started playing with the hex editor and the .sav file today and I can see the pattern for sure. I believe you had one little error on your post as the number of files listing seems like it's 4 bytes (9-12) long, then the continent.dat address starts. It was pretty cool seeing the pattern lay out. I verified the continent.dat, staff.dat, players.dat and they all look to be structurally identical to the pre-game dat files.

So would a re-gen re-generator :joy: be the consensus top priority?
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

NingDynasty wrote:Thanks Archi! By the time you posted up your library there and what not I had already completed my trial exporter. Works like a charm and spits out a perfect format acceptable by your updater, less my two extra columns at the end I can easily delete out before using. I ended up using classes myself just to easily "daisy chain" as you called it the player, team, league associations. Works wonderfully. Took me a minute to figure out why it wouldn't run on my home pc then downloaded the redistributable for MSVS 12.

So I started playing with the hex editor and the .sav file today and I can see the pattern for sure. I believe you had one little error on your post as the number of files listing seems like it's 4 bytes (9-12) long, then the continent.dat address starts. It was pretty cool seeing the pattern lay out. I verified the continent.dat, staff.dat, players.dat and they all look to be structurally identical to the pre-game dat files.

So would a re-gen re-generator :joy: be the consensus top priority?
Oops! Thanks for pointing out the typo regarding the list of files. I've corrected my original post. :thup:

Glad you got it all working. =D> I think some sort of regen regenerator would definitely be top of the wish list! Yesterday I started rewriting my Saved Game Tool in the hope of making some edits to the saved game too. I've been going round in circles with some parts of the ground work (I just can't seem to use memcpy() to copy a portion of a vector of chars to a struct - I found a way around it eventually, but it's not quite as tidy as I'd like). I want to leave open the possibility of being able to add new records to any of the saved games (rather than being limited to just editing existing data) and so I need to deal with writing vectors of structs/classes to a big char buffer, re-indexing and then rewriting to the sav file.

Yeah I use classes as well to deal with the different .dat and .tmp (in the case of saved games) files. The thing I struggle with is how to deal with the structs - do you place turn the struct into a class like this:

Code: Select all

Class cNames {
private:
   char Name[STANDARD_TEXT_LENGTH];
   long NameID;
   long Nation;
   char count;
};
Or alternatively, do you leave the struct as a member of the class like this:

Code: Select all

Class cNames {
private:
   NAMES name;
};
I have been using the second option because it seems to be a lot easier to read and write the struct data to and from a file (e.g. file.write((char*)&name, sizeof(name));). Whereas with the first option, if you add any additional members to the class, I'm not sure if it'll write okay (because it'll try to write the additional members to the file). I need to check, though - perhaps it'll work okay if I try something like this: file.read((char*)&cNames, sizeof(NAMES)); - because it'll only write the first x bytes (being the size of the NAMES struct) and so it'll skip any additional members.
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

To answer my own question about classes, it is possible to use option #1 mentioned above. So long as the start of your class first lists all of the members in the correct order, you can add any additional members you want afterwards. For example:

Code: Select all

Class cNames {
private:
   char Name[STANDARD_TEXT_LENGTH];
   long NameID;
   long Nation;
   char count;

   // Additional non-saved game members
   std::string Name_str;
};
You can then do this to read and write. In this example, 'v' is the name of the vector of cNames class objects:

Code: Select all

// Read
cNames buffer;
file.read((char*)&buffer, sizeof(NAMES));
v.push_back(buffer);

// Write
for(unsigned long i = 0; i < v.size(); ++i)
   file.write((char*)&v[i], sizeof(NAMES));
It seems that as you're reading and writing the size of NAMES rather than the size of cNames, it will read to / write from the first x bytes of each class object (x bytes being the size of the NAMES struct in this example). This makes life a lot easier indeed.
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

Is anybody at all able to do me a favour? I am looking into adding a GUI to the EHM Updater so that there is no need to use the DOS command prompt all of the time. I'm trying out something called Qt to write it. However, when I try a simple program just to test it out I can't get it to run on my virtual copy of Windows XP. I need to find out whether this is a problem with Windows XP or whether it doesn't work on any computer other than mine (and annoyingly I don't have a second computer to test it out on). Is there anybody with Windows 7 who'd be willing to download my application and try running it? :-)

All you have to do is download this file, extract it somewhere temporary and then double-click on Notepad.exe. If all works ok, a window should appear like this:

Image
User avatar
nino33
Mr. Goalie
Posts: 6088
Joined: Sat Aug 07, 2010 3:37 am
Custom Rank: Retro Rosters Specialist
Favourite Team: 1970s hockey

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by nino33 »

archibalduk wrote:extract it somewhere temporary and then double-click on Notepad.exe. If all works ok, a window should appear
I tried and got the following error message Image
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

Thanks for trying it out! :-) That's the same error I get on my XP installation. So it must be a problem with my program and not something specific to XP.
User avatar
Manimal
TBL Admin Team
Posts: 6344
Joined: Thu Apr 24, 2008 4:01 am
Custom Rank: EHM Rosters Man
Favourite Team: Djurgårdens IF
Location: Karlstad, Sweden

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by Manimal »

I could not start it and I got an error saying Qt5Core.dll is missing from the computer
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

Try this one: https://docs.google.com/file/d/0BwA0Nrq ... sp=sharing

It's been a lot of messing around, but I think this should do it!
User avatar
nino33
Mr. Goalie
Posts: 6088
Joined: Sat Aug 07, 2010 3:37 am
Custom Rank: Retro Rosters Specialist
Favourite Team: 1970s hockey

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by nino33 »

archibalduk wrote:Try this one: https://docs.google.com/file/d/0BwA0Nrq ... sp=sharing
It worked for me!
I didn't get a folder full of files this time, just the notepad2.exe...but it opened when I downloaded and hit "Run" :-)
NingDynasty
Junior League
Posts: 27
Joined: Wed Feb 08, 2012 10:36 pm

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by NingDynasty »

Archi - Weird you and I are on the same wave length too much. Every project I start you start and we don't even know it. Anyway I've been working on a GUI C# application and it's coming along. So far it's just a glorified spreadsheet but it'll decompile the Index.dat file, allow you to view and edit it spreadsheet style then save back to the Index.dat file. More of an introductory course into C# for me but it's functioning and it's progressing pretty quickly. Now if I can only talk the wife into letting me bring the laptop on our cruise...

Ideally a mixture of the existing pre-game editor and the EHM scout would be what I'd want in the end. Then add in a bunch of functions to do massive edits and some other ideas I have planned.

Image
User avatar
archibalduk
TBL Admin Team
Posts: 20373
Joined: Tue Jul 06, 2004 8:44 pm
Custom Rank: Seaside + Fruit Juice Mode
Favourite Team: Guildford (EPL) / Invicta (NIHL)
Location: United Kingdom
Contact:

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by archibalduk »

nino33 wrote:
archibalduk wrote:Try this one: https://docs.google.com/file/d/0BwA0Nrq ... sp=sharing
It worked for me!
I didn't get a folder full of files this time, just the notepad2.exe...but it opened when I downloaded and hit "Run" :-)
Great! Thanks for all of your help. :-) The dll files included with the previous zip file were this time incorporated into the Notepad application itself.
NingDynasty wrote:Archi - Weird you and I are on the same wave length too much. Every project I start you start and we don't even know it. Anyway I've been working on a GUI C# application and it's coming along. So far it's just a glorified spreadsheet but it'll decompile the Index.dat file, allow you to view and edit it spreadsheet style then save back to the Index.dat file. More of an introductory course into C# for me but it's functioning and it's progressing pretty quickly. Now if I can only talk the wife into letting me bring the laptop on our cruise...

Ideally a mixture of the existing pre-game editor and the EHM scout would be what I'd want in the end. Then add in a bunch of functions to do massive edits and some other ideas I have planned.
Hehe yes it's funny we seem keep thinking the same things! :-D

C# was something I was considering when looking at GUIs. It came down to either that or the QT library for C++. I've been having a lot of difficulty getting QT applications to deploy onto other computers (hence my earlier requests for others to try running my test notepad app) but I think I've fixed it now. I'll probably stick with QT as I can use C++ with it and I can also create applications that will run on both Windows and Linux (and potentially OS X) - this isn't really relevant for EHM, but it will be useful for the FHM Editor/Updater I've started work on.

The basics of C# GUI (aka WPF) development are nice and simple (as is QT). The only thing holding me back with C# is having to learn a new language again. I did make a start on a C#/WPF app, but I couldn't find a simple way of reading and writing to/from binary files and the structs. From what I could gather, there isn't a C# equivalent to file.read((char*)&cNames, sizeof(NAMES));. The recommendation mentioned on other forums was to manually read into each element of the struct, but I thought this would be far to much work given the number of structs we're dealing with. Did you figure out a way of doing this in C#?

Best of luck with your application. It's already clearly taking some big steps in the right direction. 8-)
User avatar
Manimal
TBL Admin Team
Posts: 6344
Joined: Thu Apr 24, 2008 4:01 am
Custom Rank: EHM Rosters Man
Favourite Team: Djurgårdens IF
Location: Karlstad, Sweden

Re: C/C++ Code Snippets / Discussion [EHM 2007]

Post by Manimal »

nino33 wrote:
archibalduk wrote:Try this one: https://docs.google.com/file/d/0BwA0Nrq ... sp=sharing
It worked for me!
I didn't get a folder full of files this time, just the notepad2.exe...but it opened when I downloaded and hit "Run" :-)
Worked for me aswell
Post Reply