Jump to content

[M2Dev] Advanced Files To Build Your Server: Src & Server Files & Client [x64 / DX9Ex / CMake / Python3.14 / FreeType / XChaCha20-Poly1305]


Recommended Posts

  • Active+ Member

I really don't know what's goin on with that invisible objects bug. I don't have that bug but I'm surely interested in finding out what it is...

  • Active+ Member
39 minutes ago, Volvox said:

Could it be related with dx9 ?

That's something every copy has in the source... The question is why some people have this issue while others don't? 🤔

  • Good 1
  • Active+ Member
59 minutes ago, Mind Rapist said:

That's something every copy has in the source... The question is why some people have this issue while others don't? 🤔

I have previously found this  issue due to LF and CRLF differences, I can't  remember how I fixed it exactly, I just know it was line ending related.

  • Metin2 Dev 1
  • Active+ Member
5 minutes ago, soforyou said:

I have previously found this  issue due to LF and CRLF differences, I can't  remember how I fixed it exactly, I just know it was line ending related.

That's actually a useful start but isn't the IDE or editor supposed to auto convert the line endings? Or should it be saved in order for that to happen? Do you remember anything else?

  • Active+ Member
Just now, Mind Rapist said:

That's actually a useful start but isn't the IDE or editor supposed to auto convert the line endings? Or should it be saved in order for that to happen? Do you remember anything else?

It's more likely then not git configuration related, I think my stuff is forced to  LF and  that can cause trouble

  • Metin2 Dev 1
  • Active+ Member
2 minutes ago, soforyou said:

It's more likely then not git configuration related, I think my stuff is forced to  LF and  that can cause trouble

I think this could be it. I set this to something like "Let Windows decide" or "Let the editor decide" or something like that when I was installing. It would make a lot of sense if this issue was only present to systems that have configured Git to enforce LF line endings.

Do you remember if client files must also be fixed (py/msm/mse/etc...) or just the source?

  • Active+ Member
Just now, Mind Rapist said:

I think this could be it. I set this to something like "Let Windows decide" or "Let the editor decide" or something like that when I was installing. It would make a lot of sense if this issue was only present to systems that have configured Git to enforce LF line endings.

Do you remember if client files must also be fixed (py/msm/mse/etc...) or just the source?

Back then I just remember adding support for LF line endings, I think I found a previous PR on a different project: 

This is the hidden content, please

  • Metin2 Dev 18
  • Good 4
  • Love 14
  • Active+ Member
15 minutes ago, soforyou said:

Back then I just remember adding support for LF line endings, I think I found a previous PR on a different project: 

This is the hidden content, please

This could be it. I was gonna ask anyone who is facing the issue if they are receiving the error message (Traceback) that the function gives but since this client source doesn't produce any ErrorLogs, syserrs, or logs of any kind if you could please try this out and let us know here if the issue is fixed. For those too lazy to click the link:

In GameLib/Property.cpp:

find:

bool CProperty::ReadFromMemory(const void * c_pvData, int iLen, const char * c_pszFileName)

change:

if (*pcData != '\r' || *(pcData + 1) != '\n')
{
	TraceError("CProperty::ReadFromMemory: File format error after FourCC: %s\n", c_pszFileName);
	return false;
}

into this:

if ((*pcData == '\r' && *(pcData + 1) == '\n'))
	pcData += 2;
else if (*pcData == '\n')
	pcData += 1;
else {
	TraceError("CProperty::ReadFromMemory: File format error after FourCC: %s\n", c_pszFileName);
	return false;
}

 

Now if that doesn't work you can try the following script:

Spoiler
import os
import argparse
from typing import List

# LFToCRLFConverter.py

# List of file extensions that are typically text-based and should have line endings converted.
# Binary files (like .dds, .tga, .png, .mp4, .zip, etc.) are explicitly excluded by not being in this list.
TEXT_FILE_EXTENSIONS: List[str] = [
	# Source Code (C/C++/Python/etc.)
	'.c', '.cpp', '.cc', '.h', '.hpp', '.inc', '.py', '.java', '.cs', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs',

	# Configuration and Project Files
	'.txt', '.md', '.markdown', '.xml', '.json', '.yaml', '.yml', '.ini', '.cfg', '.conf',
	'.cmake', 'cmakelists.txt', '.gitattributes', '.gitignore',
	'.vcxproj', '.filters', '.sln', '.plist',

	# Web/Markup
	'.html', '.css', '.scss', '.less', '.xml', '.svg',

	# Other Scripting/Data
	'.sh', '.bat', '.ps1', '.sql', '.log'
]

def is_text_file(filename: str) -> bool:
	"""Checks if a file has a whitelisted extension or is a known configuration file."""
	# Check for file extension (case-insensitive)
	for ext in TEXT_FILE_EXTENSIONS:
		if filename.lower().endswith(ext):
			return True
	
	# Handle files with no extension (e.g., Dockerfile, LICENSE)
	if os.path.basename(filename).lower() in ('dockerfile', 'license', 'makefile'):
		return True
		
	return False


def convert_file_endings(filepath: str) -> bool:
	"""
	Reads a file with universal newline mode ('r'), replaces LF with CRLF,
	and writes the content back to the file.
	Returns True if a conversion was performed, False otherwise.
	"""
	try:
		# Use 'rb' to read bytes and check for presence of LF without CRLF first
		with open(filepath, 'rb') as f:
			content_bytes = f.read()
			
		# Check if the file contains LF (\n) that is NOT preceded by CR (\r)
		# We look for \r\n (CRLF) and replace it with just a marker, then check for \n (LF)
		# This prevents converting files that are already CRLF.
		
		# 1. Temporarily replace all existing CRLF (\r\n) with a placeholder (\0)
		# 2. Check if any raw LF (\n) remains.
		temp_content = content_bytes.replace(b'\r\n', b'\0')
		
		if b'\n' in temp_content:
			# Conversion is needed: replace LF with CRLF
			new_content = content_bytes.replace(b'\n', b'\r\n')
			
			# Write the converted content back to the file (use 'wb' for binary write)
			with open(filepath, 'wb') as f:
				f.write(new_content)

			return True
		
		return False # No LF found that needed conversion

	except Exception as e:
		print(f"ERROR: Could not process {filepath}. Skipping. Error: {e}")

		return False

def process_directory(root_dir: str):
	"""Recursively walks through a directory and converts line endings."""
	print(f"Starting conversion in: {root_dir}")
	converted_count = 0
	skipped_count = 0

	for dirpath, _, filenames in os.walk(root_dir):
		for filename in filenames:
			filepath = os.path.join(dirpath, filename)

			if is_text_file(filename):
				if convert_file_endings(filepath):
					print(f"CONVERTED: {filepath}")
					converted_count += 1
				# else:
				#	 print(f"SKIPPED (No LF needed): {filepath}")
			else:
				skipped_count += 1

	print("\n--- Summary ---")
	print(f"Successfully converted: {converted_count} file(s)")
	print(f"Files skipped (binary or already CRLF): {skipped_count} file(s)")
	print("Conversion complete.")

def run_reconnaissance(root_dir: str) -> None:
	"""
	Recursively scans the directory and prints new and existing extensions found.
	"""
	print(f"Starting reconnaissance scan in: {root_dir}")
	all_extensions_found: Set[str] = set()
	total_files = 0

	for dirpath, _, filenames in os.walk(root_dir):
		for filename in filenames:
			total_files += 1
			# Get the file extension, including the dot (e.g., '.cpp', '.exe')
			_, ext = os.path.splitext(filename)

			if ext:
				all_extensions_found.add(ext.lower())

	# Use set mathematics to categorize extensions
	existing_extensions = all_extensions_found.intersection(TEXT_FILE_EXTENSIONS)
	new_extensions = all_extensions_found.difference(TEXT_FILE_EXTENSIONS)

	print("\n--- Smart Reconnaissance Results ---")
	print(f"Total files scanned: {total_files}")
	
	# Helper to print lists nicely
	def print_extensions(ext_set):
		sorted_exts = sorted(list(ext_set))

		if not sorted_exts:
			print("    None found.")

			return

		for i in range(0, len(sorted_exts), 5):
			# 1. Use a list comprehension to wrap each extension in single quotes: ['.dds', '.dll', ...]
			quoted_extensions = ["'" + ext + "'" for ext in sorted_exts[i:i+5]]
			
			# 2. Join them with the desired separator: ', '
			print("    " + ", ".join(quoted_extensions) + ",") # Added trailing comma for easy list appending

	print("\n✅ Existing Text Extensions Found (Already Whitelisted):")
	print_extensions(existing_extensions)

	print("\n⚠️ New Extensions Found (Check for Text Files to Add):")
	print_extensions(new_extensions)

	print("\nAction: If any extension under 'New Extensions' is a text file, add it to the `TEXT_FILE_EXTENSIONS` set in the script before converting.")


def main():
	parser = argparse.ArgumentParser(
		description="Recursively converts LF line endings to CRLF for whitelisted text file types.",
		epilog=f"Targets the current directory ('.') by default. Targets text file extensions: {', '.join(TEXT_FILE_EXTENSIONS)}"
	)

	parser.add_argument(
		'path',
		nargs='?', # Makes the path optional
		default='.',
		help="The root directory to start scanning from (e.g., '.', '../src', or 'C:/project'). Defaults to current directory."
	)

	parser.add_argument(
		'--recon', '-r',
		action='store_true',
		help="Run reconnaissance mode: prints all unique file extensions found, but skips conversion."
	)

	args = parser.parse_args()
	
	if not os.path.isdir(args.path):
		print(f"Error: Directory not found at '{args.path}'")
		return

	root_path = os.path.abspath(args.path)
	
	if args.recon:
		run_reconnaissance(root_path)
	else:
		process_directory(root_path)

if __name__ == "__main__":
	main()

 

Save as a python file.

Usage:

  • py LFToCRLFConverter.py    (automatically converts all files in current directory (including sub directories, sub-sub directories, etc...) with extensions included in TEXT_FILE_EXTENSIONS)
  • py LFToCRLFConverter.py my/custom/folder    (automatically converts all files in specified directory (including sub directories, sub-sub directories, etc...) with extensions included in TEXT_FILE_EXTENSIONS)

 

  • py LFToCRLFConverter.py --recon    (prints the extensions from all files in current directory (including sub directories, sub-sub directories, etc...) not included in TEXT_FILE_EXTENSIONS, ready to copy and paste into the List at the beginning of the script. Works also as py LFToCRLFConverter.py -r)
  • py LFToCRLFConverter.py -r my/custom/folder    (prints the extensions from all files in specified directory (including sub directories, sub-sub directories, etc...) not included in TEXT_FILE_EXTENSIONS, ready to copy and paste into the List at the beginning of the script)

If you have the invisible objects issue please try one of these solutions and let us know if and how well it worked!

  • Flame 1
  • Love 2

Anyone experiencing state leak issues upon death? (Monster movement, general effects, return to normal upon standing)

Edited by Klaus
  • Active+ Member
12 minutes ago, Klaus said:

Anyone experiencing state leak issues upon death? (Monster movement, general effects, return to normal upon standing)

I don't follow... Can you explain with a bit more detail what the problem is or attach a syserr (or both) please?

  • Active+ Member
On 11/17/2025 at 4:25 PM, rrrrrrr said:

@Mind Rapist Can someone upload the customer's package? Maybe I'm packing something wrong?

In your server find install.py (where start.py and stop.py are).

Replace install.py with this: 

This is the hidden content, please

Make sure your server is not running and execute the script (python3 install.py)

 

UPDATE:

After the PR merges of November 21 the pre-installed script "install.py" includes support for the package folder and automatic symlink creation. If you came across this solution now, make sure that you have pulled the latest version from the official repo mentioned in the initial post of this topic and run it.

Edited by Mind Rapist
  • Metin2 Dev 6
  • Good 4
  • Love 7
  • Active+ Member

Since I liked the interest and activity I saw here today I would like to share my progress so far regarding bugs I found. I am very open to suggestions as I am stuck in a lot of them as well as feedback in order to do a better, more effective job and every bit of active help is also deeply appreciated. If you don't see your bug on the list please comment it here and someone will take a look at it.

  1. ✅ DB Crashes upon creating new characters
    Spoiler

    This issue has already been addressed as you may have seen in previous comments but since I have not yet PR'd it I am also mentioning it here.

  2. ✅ Negative HP value when dying
    Spoiler

    This fix is public like most of the fixes I know but there are so many out there and it is one of the easiest fixes.

  3. ✅ Perfecting the Messenger functionality
    Spoiler

    This took time as I am unexperienced. I found some public fixes on this one, the initial idea was to make the messenger auto-update for a character when they get deleted by a friend. I quickly discovered other minor improvements and although it wasn't the easiest one to fix I can say that I really delivered on this one.

    New features of the messenger:

    • Auto-update friends list on both clients when one deletes the other.
    • Auto-update target menu for both characters, hiding/revealing the "Friend" button whenever one accepts/deletes the other.
    • Disabling the "Whisper" and "Remove" buttons in uiMessenger whenever the character deletes the selected friend.
    • Chat info whenever a character attempts to send a second friend request while the first one is still unanswered.
    • Chat info whenever a character has an unanswered friend request FROM someone and attempts to create a new friend request to that person.

    This fix is complete. I had to figure out some parts myself but most of it came from Ken's fix in this forum combined with another one that fixes a query in input_main.cpp and a complimentary one from another forum.

  4. ✅ "Negative value in command" CORE DOWNER
    Spoiler

    A public fix from this forum that I never would have guessed. Attempting a command like

    /m 101 -1

    is actually a core downer generating that nasty game.core we all love to hate. The fix is complete and it's gonna be included in the next PR.

  5. ✅ Fix stats values in character select screen
    Spoiler

    This was a forgotten bug that me and some other guys from this forum actually solved some years ago. Revisited today and applied. The result is:

    • The stats bars now go full when a stat is at max (90 points)
    • No issues with buffed stats (like from riding). Everything shows normal.
    • Supposedly fixes that "naked character flash" other clients would see sometimes when the character logs out.
    • No issues with costumes (sash system NOT TESTED!)
    • Apparently fixed an existing issue that caused character stats to always show the initial values (from Lv. 1) even in-game.
  6. ⚙️Untranslated locale string keys
    Spoiler

    For this I have created some scripts and although it's a rather time consuming process I will translate all the strings from the source that are not being added into the locale_string.txt (for English only).

    It will however take a team effort to add all the missing pieces since I don't speak 16 languages yet. The good news is that most of these untranslated strings are already being translated in locale_string.txt using a different key. After all is translated (most are copy pastes) I will use a tool to extract duplicates and update the source files, filtering out the extra work for y'all. When I PR the changes I will also provide the file with the missing translations only (without duplicates) in English so you can commit or share the missing keys translated in your language.

  7. ⚙️ Skill cooldown slot refresh
    Spoiler

    This one has really started to piss me off 😅. It's been about 3 days that I cannot figure this one out. I used this post:

    but it's not quite there, it has issues. I am testing with a BM Sura with the 2 togglables. Everything works perfectly except the following issues in Skills page only (Taskbar works like a charm!):

    • /setsk: for all levels of Skills, when I do that there is an 80% chance that the remaining cooldown shade will be removed from the slot
    • Changing pages (or going to the Horse tab and back): mostly affects P skills. Removes cooldown shade.
    • Mounting/unmounting: mostly affects P skills. Removes cooldown shade.

    Toggle outline works completely normal.

    If someone has some extra insight here is my __RefreshSkillPage:

    Spoiler
    	def __RefreshSkillPage(self, name, slotCount):
    		global SHOW_LIMIT_SUPPORT_SKILL_LIST
    
    		skillPage = self.skillPageDict[name]
    		startSlotIndex = skillPage.GetStartIndex()
    
    		if "ACTIVE" == name:
    			if self.PAGE_HORSE == self.curSelectedSkillGroup:
    				startSlotIndex += slotCount
    
    		getSkillType=skill.GetSkillType
    		getSkillIndex=player.GetSkillIndex
    		getSkillGrade=player.GetSkillGrade
    		getSkillLevel=player.GetSkillLevel
    		getSkillLevelUpPoint=skill.GetSkillLevelUpPoint
    		getSkillMaxLevel=skill.GetSkillMaxLevel
    
    		for i in xrange(slotCount + 1):
    			slotIndex = i + startSlotIndex
    			skillIndex = getSkillIndex(slotIndex)
    
    			for j in xrange(skill.SKILL_GRADE_COUNT):
    				if app.FIX_REFRESH_SKILL_COOLDOWN: #and j != player.GetSkillGrade(slotIndex):
    					skillPage.ClearSlot(self.__GetRealSkillSlot(j, i))
    
    			if 0 == skillIndex:
    				continue
    
    			skillGrade = getSkillGrade(slotIndex)
    			skillLevel = getSkillLevel(slotIndex)
    			skillType = getSkillType(skillIndex)
    
    			## 승마 스킬 예외 처리
    			if player.SKILL_INDEX_RIDING == skillIndex:
    				if 1 == skillGrade:
    					skillLevel += 19
    				elif 2 == skillGrade:
    					skillLevel += 29
    				elif 3 == skillGrade:
    					skillLevel = 40
    
    				skillPage.SetSkillSlotNew(slotIndex, skillIndex, max(skillLevel-1, 0), skillLevel)
    				skillPage.SetSlotCount(slotIndex, skillLevel)
    
    			## ACTIVE
    			elif skill.SKILL_TYPE_ACTIVE == skillType:
    				for j in xrange(skill.SKILL_GRADE_COUNT):
    					realSlotIndex = self.__GetRealSkillSlot(j, slotIndex)
    					skillPage.SetSkillSlotNew(realSlotIndex, skillIndex, j, skillLevel)
    					skillPage.SetCoverButton(realSlotIndex)
    
    					if (skillGrade == skill.SKILL_GRADE_COUNT) and j == (skill.SKILL_GRADE_COUNT-1):
    						skillPage.SetSlotCountNew(realSlotIndex, skillGrade, skillLevel)
    					elif (not self.__CanUseSkillNow()) or (skillGrade != j):
    						skillPage.SetSlotCount(realSlotIndex, 0)
    						skillPage.DisableCoverButton(realSlotIndex)
    
    						if app.FIX_REFRESH_SKILL_COOLDOWN:
    							skillPage.DeactivateSlot(realSlotIndex)
    					else:
    						skillPage.SetSlotCountNew(realSlotIndex, skillGrade, skillLevel)
    
    					if app.FIX_REFRESH_SKILL_COOLDOWN:
    						if player.IsSkillActive(slotIndex) and (skillGrade == j or (skillGrade >= skill.SKILL_GRADE_COUNT) and j == (skill.SKILL_GRADE_COUNT - 1)):
    							skillPage.ActivateSlot(realSlotIndex)
    
    						if player.IsSkillCoolTime(slotIndex) and skillGrade != j:
    							skillPage.TransferSlotCoolTime(realSlotIndex, self.__GetRealSkillSlot(skillGrade, i))
    
    			## 그외
    			else:
    				if not SHOW_LIMIT_SUPPORT_SKILL_LIST or skillIndex in SHOW_LIMIT_SUPPORT_SKILL_LIST:
    					realSlotIndex = self.__GetETCSkillRealSlotIndex(slotIndex)
    					skillPage.SetSkillSlot(realSlotIndex, skillIndex, skillLevel)
    					skillPage.SetSlotCountNew(realSlotIndex, skillGrade, skillLevel)
    
    					if skill.CanUseSkill(skillIndex):
    						skillPage.SetCoverButton(realSlotIndex)
    
    			skillPage.RefreshSlot()
    
    			if app.FIX_REFRESH_SKILL_COOLDOWN:
    				self.__RestoreSlotCoolTime(skillPage)

     

    If you need more information please let me know which part you would like to see. I appreciate any sort of information or tip especially for this one, it's almost done!

  8. ⏳ LPheart (lag occured)
    Spoiler

    I don't know much about this but I would like to fix it. It would be nice to know that you won't wait 60 seconds for the client to login to the server after you start it. Plus everyone loves a clean syserr.

  9. ⏳ Sequence (PONG)
    Spoiler

    I tried a few things like increasing the size sent to the server, nothing worked. Tried to set it to false at packet_info.cpp, don't try it. I turned it back on as soon as it started kicking me randomly. I am back in square 1 on this one.

  10. ⏳ Bodyguard (mob 20373) motion 0 missing
    Spoiler

    I traced back that in the client this mob gets everything related to appearance and motion from the folder jinno_patrol_spear. No 'bodyguard' folder anywhere in client or server. No 'jinno_patrol_spear' in server either, only 'jinno_guard_spear'. I tried renaming and I still get the issue. Back to square 1 here too. And yes I wanna fix this dude, who tf does he think he is?

  11. ⏳ ZSTD compiled output is a virus
    Spoiler

    Yes it is flagged as a Gen:Variant.Lazy371377 (zstd.exe only). The source code is ok but the compiled program is not so safe after all. Getting LINK errors when compiling, however both client and PackMaker compile just fine. I tried removing the folder from vendor and git cloning the official repo, still it contains malicious code when compiled.

  12. ⏳ Cannot open a storage with a character at the NPC LOL
    Spoiler

    I did not expect this. The NPC gives me only 2 options: Open a storage or Enter the Mall. Opening a storage prompts the player to pay 500 Yang and when the NPC finishes as usual nothing has happened, these are again the only 2 options. Mall opens normally. Haven't started working on this one yet.

  13. ⏳ Skill reset scroll: add affect removal and send training letter 
    Spoiler

    When using item 71002 while having an affect like Aura or something I think it would be nice if it gets removed. I tried with a togglable and while it applies, it stops consuming SP from the character. Plus it has no grade anymore I don't know if that counts togglable skill affect performance. And of course sending the training letter would be nice so the character doesn't have to relog. Haven't started yet and I am open to suggestions and opinions regarding the affect removal part.

  14. ⏳ Actor/monster synchronization across different connected clients
    Spoiler

    This is a bug as ancient as time itself but I would like to see a source delivering the fix as a part of it. I've heard other devs attempting it so if you have any clue on where to start I'd much appreciate it.

  15. ⏳ Character enters game with low HP
    Spoiler

    This is happening only to the first GM that came with the serverfiles. I don't know if it has to do with the equipment or some hp value being mistyped in the source but it should be a rather easy fix. Haven't started working on this one yet.

  16. ⏳ Multiple refreshing upon loading finish if the character is mounting
    Spoiler

    I don't know what that is but I would like to find out. I suspect it may be the old mounting system (seal takes it's time to unequip when unmounting as well). Haven't started looking into it yet.

  17. ⏳ Invisibility fix (shining, hiding from mini map)
    Spoiler

    I know that this is a clean source and we are not gonna be committing any systems, but although this was in the official update 17, this isn't a system or a feature. It's an improvement for an already existing feature: the invisibility skill and command. I will soon start working on it 🙂

 

What bugs did you find so far? What fixes have you implemented in your sources? Let's fix everything together 😄

Thank you for all your help and feedback so far. If someone is in a rush about my fixes let me know and I will share it here before the PR.

  • Metin2 Dev 2
  • muscle 1
  • Love 1
  • Active+ Member

Kinda funny but I'm stuck in uiCharacter. I'm trying to remove any cooldowns from skills without a level (no points). I tried this:

if net.GetMainActorSkillGroup() == 0 or skillLevel < 1:
	slotWindow.SetSlotCoolTime(slotIndex, 0)

in various locations inside def __RefreshSkillPage(self, name, slotCount) but so far all I caused is crashes. Error logs like syserrs are not generating in client (need to fix this at some point as well) so I was wondering if someone more experienced with the system and structure can correct me.

Thanks! 🙏

  • Active+ Member

 ✅ Invisibility fix (shining, hiding from mini map)

This one is now done and will be uploaded in the next PR. What to expect:

  • True invisibility for INVISIBLE (/in, /inv), Ninja's Stealth skill affect and REVIVE_INVISIBILITY (public fix available)
  • No shinings, effects, GM logos, dust while walking around, nothing is visible (public fix available)
  • Completely hidden from the mini map (public fix available)
  • Updates in real time (public fix available)
  • Damage value turns back on after the affect is over (NEW!)
  • The visual effect of Ninja's stealth is visible to everybody (NEW!)

Everything is tested and works perfectly fine.

Note: some of the code may need a review from someone who is more experienced than me. I tried to overcome the obstacles I came across with the little experience I have and my work with this fix is done and so far really satisfying I am however open to suggestions and improvements for more experience and a more optimal, robust codebase

Sneak peak

 

Edited by Metin2 Dev International
Core X - External 2 Internal
  • Metin2 Dev 2
  • Love 4
  • Active Member
15 hours ago, Mind Rapist said:

 ✅ Invisibility fix (shining, hiding from mini map)

This one is now done and will be uploaded in the next PR. What to expect:

  • True invisibility for INVISIBLE (/in, /inv), Ninja's Stealth skill affect and REVIVE_INVISIBILITY (public fix available)
  • No shinings, effects, GM logos, dust while walking around, nothing is visible (public fix available)
  • Completely hidden from the mini map (public fix available)
  • Updates in real time (public fix available)
  • Damage value turns back on after the affect is over (NEW!)
  • The visual effect of Ninja's stealth is visible to everybody (NEW!)

Everything is tested and works perfectly fine.

Note: some of the code may need a review from someone who is more experienced than me. I tried to overcome the obstacles I came across with the little experience I have and my work with this fix is done and so far really satisfying I am however open to suggestions and improvements for more experience and a more optimal, robust codebase

Sneak peak

 

Amazing. Thank you !

Edited by Metin2 Dev International
Core X - External 2 Internal
  • Metin2 Dev 1
  • Honorable Member

Latest PRs merged.

  • Love 3

992404397646696589.png
Former C++ Developer at Gameloft on DML
Join my Discord: Distraught Labs

[PackageCryptInfo] %s is not crypt file. pass!  when start au and game  ,log show the error. why the package dir is empty? and need some files?  thanks

It’s a solid foundation for a clean project.

I’ve been working with it for a few days now, and thanks to 64-bit and DX9Ex, completely new shader possibilities are opening up.

  • Love 3
On 11/17/2025 at 1:21 PM, Mind Rapist said:

This could be it. I was gonna ask anyone who is facing the issue if they are receiving the error message (Traceback) that the function gives but since this client source doesn't produce any ErrorLogs, syserrs, or logs of any kind if you could please try this out and let us know here if the issue is fixed. For those too lazy to click the link:

In GameLib/Property.cpp:

find:

bool CProperty::ReadFromMemory(const void * c_pvData, int iLen, const char * c_pszFileName)

change:

if (*pcData != '\r' || *(pcData + 1) != '\n')
{
	TraceError("CProperty::ReadFromMemory: File format error after FourCC: %s\n", c_pszFileName);
	return false;
}

into this:

if ((*pcData == '\r' && *(pcData + 1) == '\n'))
	pcData += 2;
else if (*pcData == '\n')
	pcData += 1;
else {
	TraceError("CProperty::ReadFromMemory: File format error after FourCC: %s\n", c_pszFileName);
	return false;
}

 

Now if that doesn't work you can try the following script:

  Hide contents
import os
import argparse
from typing import List

# LFToCRLFConverter.py

# List of file extensions that are typically text-based and should have line endings converted.
# Binary files (like .dds, .tga, .png, .mp4, .zip, etc.) are explicitly excluded by not being in this list.
TEXT_FILE_EXTENSIONS: List[str] = [
	# Source Code (C/C++/Python/etc.)
	'.c', '.cpp', '.cc', '.h', '.hpp', '.inc', '.py', '.java', '.cs', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs',

	# Configuration and Project Files
	'.txt', '.md', '.markdown', '.xml', '.json', '.yaml', '.yml', '.ini', '.cfg', '.conf',
	'.cmake', 'cmakelists.txt', '.gitattributes', '.gitignore',
	'.vcxproj', '.filters', '.sln', '.plist',

	# Web/Markup
	'.html', '.css', '.scss', '.less', '.xml', '.svg',

	# Other Scripting/Data
	'.sh', '.bat', '.ps1', '.sql', '.log'
]

def is_text_file(filename: str) -> bool:
	"""Checks if a file has a whitelisted extension or is a known configuration file."""
	# Check for file extension (case-insensitive)
	for ext in TEXT_FILE_EXTENSIONS:
		if filename.lower().endswith(ext):
			return True
	
	# Handle files with no extension (e.g., Dockerfile, LICENSE)
	if os.path.basename(filename).lower() in ('dockerfile', 'license', 'makefile'):
		return True
		
	return False


def convert_file_endings(filepath: str) -> bool:
	"""
	Reads a file with universal newline mode ('r'), replaces LF with CRLF,
	and writes the content back to the file.
	Returns True if a conversion was performed, False otherwise.
	"""
	try:
		# Use 'rb' to read bytes and check for presence of LF without CRLF first
		with open(filepath, 'rb') as f:
			content_bytes = f.read()
			
		# Check if the file contains LF (\n) that is NOT preceded by CR (\r)
		# We look for \r\n (CRLF) and replace it with just a marker, then check for \n (LF)
		# This prevents converting files that are already CRLF.
		
		# 1. Temporarily replace all existing CRLF (\r\n) with a placeholder (\0)
		# 2. Check if any raw LF (\n) remains.
		temp_content = content_bytes.replace(b'\r\n', b'\0')
		
		if b'\n' in temp_content:
			# Conversion is needed: replace LF with CRLF
			new_content = content_bytes.replace(b'\n', b'\r\n')
			
			# Write the converted content back to the file (use 'wb' for binary write)
			with open(filepath, 'wb') as f:
				f.write(new_content)

			return True
		
		return False # No LF found that needed conversion

	except Exception as e:
		print(f"ERROR: Could not process {filepath}. Skipping. Error: {e}")

		return False

def process_directory(root_dir: str):
	"""Recursively walks through a directory and converts line endings."""
	print(f"Starting conversion in: {root_dir}")
	converted_count = 0
	skipped_count = 0

	for dirpath, _, filenames in os.walk(root_dir):
		for filename in filenames:
			filepath = os.path.join(dirpath, filename)

			if is_text_file(filename):
				if convert_file_endings(filepath):
					print(f"CONVERTED: {filepath}")
					converted_count += 1
				# else:
				#	 print(f"SKIPPED (No LF needed): {filepath}")
			else:
				skipped_count += 1

	print("\n--- Summary ---")
	print(f"Successfully converted: {converted_count} file(s)")
	print(f"Files skipped (binary or already CRLF): {skipped_count} file(s)")
	print("Conversion complete.")

def run_reconnaissance(root_dir: str) -> None:
	"""
	Recursively scans the directory and prints new and existing extensions found.
	"""
	print(f"Starting reconnaissance scan in: {root_dir}")
	all_extensions_found: Set[str] = set()
	total_files = 0

	for dirpath, _, filenames in os.walk(root_dir):
		for filename in filenames:
			total_files += 1
			# Get the file extension, including the dot (e.g., '.cpp', '.exe')
			_, ext = os.path.splitext(filename)

			if ext:
				all_extensions_found.add(ext.lower())

	# Use set mathematics to categorize extensions
	existing_extensions = all_extensions_found.intersection(TEXT_FILE_EXTENSIONS)
	new_extensions = all_extensions_found.difference(TEXT_FILE_EXTENSIONS)

	print("\n--- Smart Reconnaissance Results ---")
	print(f"Total files scanned: {total_files}")
	
	# Helper to print lists nicely
	def print_extensions(ext_set):
		sorted_exts = sorted(list(ext_set))

		if not sorted_exts:
			print("    None found.")

			return

		for i in range(0, len(sorted_exts), 5):
			# 1. Use a list comprehension to wrap each extension in single quotes: ['.dds', '.dll', ...]
			quoted_extensions = ["'" + ext + "'" for ext in sorted_exts[i:i+5]]
			
			# 2. Join them with the desired separator: ', '
			print("    " + ", ".join(quoted_extensions) + ",") # Added trailing comma for easy list appending

	print("\n Existing Text Extensions Found (Already Whitelisted):")
	print_extensions(existing_extensions)

	print("\n New Extensions Found (Check for Text Files to Add):")
	print_extensions(new_extensions)

	print("\nAction: If any extension under 'New Extensions' is a text file, add it to the `TEXT_FILE_EXTENSIONS` set in the script before converting.")


def main():
	parser = argparse.ArgumentParser(
		description="Recursively converts LF line endings to CRLF for whitelisted text file types.",
		epilog=f"Targets the current directory ('.') by default. Targets text file extensions: {', '.join(TEXT_FILE_EXTENSIONS)}"
	)

	parser.add_argument(
		'path',
		nargs='?', # Makes the path optional
		default='.',
		help="The root directory to start scanning from (e.g., '.', '../src', or 'C:/project'). Defaults to current directory."
	)

	parser.add_argument(
		'--recon', '-r',
		action='store_true',
		help="Run reconnaissance mode: prints all unique file extensions found, but skips conversion."
	)

	args = parser.parse_args()
	
	if not os.path.isdir(args.path):
		print(f"Error: Directory not found at '{args.path}'")
		return

	root_path = os.path.abspath(args.path)
	
	if args.recon:
		run_reconnaissance(root_path)
	else:
		process_directory(root_path)

if __name__ == "__main__":
	main()

 

Save as a python file.

Usage:

  • py LFToCRLFConverter.py    (automatically converts all files in current directory (including sub directories, sub-sub directories, etc...) with extensions included in TEXT_FILE_EXTENSIONS)
  • py LFToCRLFConverter.py my/custom/folder    (automatically converts all files in specified directory (including sub directories, sub-sub directories, etc...) with extensions included in TEXT_FILE_EXTENSIONS)

 

  • py LFToCRLFConverter.py --recon    (prints the extensions from all files in current directory (including sub directories, sub-sub directories, etc...) not included in TEXT_FILE_EXTENSIONS, ready to copy and paste into the List at the beginning of the script. Works also as py LFToCRLFConverter.py -r)
  • py LFToCRLFConverter.py -r my/custom/folder    (prints the extensions from all files in specified directory (including sub directories, sub-sub directories, etc...) not included in TEXT_FILE_EXTENSIONS, ready to copy and paste into the List at the beginning of the script)

If you have the invisible objects issue please try one of these solutions and let us know if and how well it worked!

I tried both solutions, but unfortunately neither of them worked. I still can’t see any objects on the map.

  • Love 1

fix color icon guild (The orange icon would turn blue, etc., etc.)

        m_kMark.m_apxBuf[i] = (uint32_t(A) << 24) | (uint32_t(R) << 16) | (uint32_t(G) << 8) | uint32_t(B);
		// for
        m_kMark.m_apxBuf[i] = (uint32_t(A) << 24) | (uint32_t(B) << 16) | (uint32_t(G) << 8) | uint32_t(R);

 

Edited by Klaus
  • Active+ Member
12 minutes ago, Klaus said:

fix color icon guild

        m_kMark.m_apxBuf[i] = (uint32_t(A) << 24) | (uint32_t(R) << 16) | (uint32_t(G) << 8) | uint32_t(B);
		// for
        m_kMark.m_apxBuf[i] = (uint32_t(A) << 24) | (uint32_t(B) << 16) | (uint32_t(G) << 8) | uint32_t(R);

 

What was wrong with it?

Don't use any images from : imgur, turkmmop, freakgamers, inforge, hizliresim... Or your content will be deleted without notice...
Use : https://metin2.download/media/add/

Please use https://metin2.download/ when uploading files smaller than 100MB, otherwise the approval will take longer due to manual upload.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.