Compare commits
2 Commits
275d09001b
...
9828b967a1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9828b967a1 | ||
|
|
a3bcbd073e |
@@ -541,6 +541,44 @@ public class ErrorPacket
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 파티
|
||||
// ============================================================
|
||||
|
||||
public enum PartyUpdateType
|
||||
{
|
||||
CREATE,
|
||||
DELETE,
|
||||
JOIN,
|
||||
LEAVE
|
||||
}
|
||||
|
||||
// REQUEST_PARTY (클라 -> 서버) - CREATE: PartyName 사용 / JOIN·LEAVE·DELETE: PartyId 사용
|
||||
[ProtoContract]
|
||||
public class RequestPartyPacket
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public PartyUpdateType Type
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ProtoMember(2)]
|
||||
public int PartyId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} // JOIN, LEAVE, DELETE 시 사용
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string PartyName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} // CREATE 시 사용
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 채팅
|
||||
// ============================================================
|
||||
@@ -598,40 +636,6 @@ public class ChatPacket
|
||||
// 파티
|
||||
// ============================================================
|
||||
|
||||
public enum PartyUpdateType
|
||||
{
|
||||
CREATE,
|
||||
DELETE,
|
||||
JOIN,
|
||||
LEAVE
|
||||
}
|
||||
|
||||
// REQUEST_PARTY (클라 -> 서버) - CREATE: PartyName 사용 / JOIN·LEAVE·DELETE: PartyId 사용
|
||||
[ProtoContract]
|
||||
public class RequestPartyPacket
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public PartyUpdateType Type
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ProtoMember(2)]
|
||||
public int PartyId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} // JOIN, LEAVE, DELETE 시 사용
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string PartyName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} // CREATE 시 사용
|
||||
}
|
||||
|
||||
// UPDATE_PARTY (서버 -> 클라) - 파티 생성/삭제: LeaderId 사용 / 파티원 추가/제거: PlayerId 사용
|
||||
[ProtoContract]
|
||||
public class UpdatePartyPacket
|
||||
|
||||
@@ -251,7 +251,7 @@ public class GameServer : ServerBase
|
||||
Channel.Channel channel = cm.GetChannel(channelId);
|
||||
Player? myPlayer = channel.GetPlayer(hashKey);
|
||||
|
||||
// 1. 새 유저에게: 자신을 제외한 기존 채널 유저 목록 전송
|
||||
// 1. 새 유저에게: 자신을 제외한 기존 채널 유저 목록 + 파티 목록 전송
|
||||
IntoChannelPacket response = new IntoChannelPacket { ChannelId = channelId };
|
||||
foreach (int userId in channel.GetConnectUsers())
|
||||
{
|
||||
@@ -267,6 +267,16 @@ public class GameServer : ServerBase
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PartyInfo party in channel.GetPartyManager().GetAllParties())
|
||||
{
|
||||
response.Parties.Add(new PartyInfoData
|
||||
{
|
||||
PartyId = party.PartyId,
|
||||
LeaderId = party.LeaderId,
|
||||
MemberPlayerIds = new List<int>(party.PartyMemberIds),
|
||||
});
|
||||
}
|
||||
|
||||
byte[] toNewUser = PacketSerializer.Serialize<IntoChannelPacket>((ushort)PacketCode.INTO_CHANNEL, response);
|
||||
SendTo(peer, toNewUser);
|
||||
|
||||
|
||||
@@ -119,6 +119,12 @@ public class PartyManager
|
||||
return true;
|
||||
}
|
||||
|
||||
// 전체 파티 목록 조회
|
||||
public IEnumerable<PartyInfo> GetAllParties()
|
||||
{
|
||||
return parties.Values;
|
||||
}
|
||||
|
||||
// 조회
|
||||
public PartyInfo? GetParty(int partyId)
|
||||
{
|
||||
|
||||
@@ -210,6 +210,32 @@ public class LoadChannelPacket
|
||||
} = new List<ChannelInfo>();
|
||||
}
|
||||
|
||||
// 채널 내 파티 정보 (INTO_CHANNEL 응답에 포함)
|
||||
[ProtoContract]
|
||||
public class PartyInfoData
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int PartyId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ProtoMember(2)]
|
||||
public int LeaderId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ProtoMember(3)]
|
||||
public List<int> MemberPlayerIds
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new List<int>();
|
||||
}
|
||||
|
||||
// INTO_CHANNEL 클라->서버: 입장할 채널 ID / 서버->클라: 채널 내 나 이외 플레이어 목록
|
||||
[ProtoContract]
|
||||
public class IntoChannelPacket
|
||||
@@ -227,6 +253,13 @@ public class IntoChannelPacket
|
||||
get;
|
||||
set;
|
||||
} = new List<PlayerInfo>(); // 서버->클라: 채널 내 플레이어 목록
|
||||
|
||||
[ProtoMember(3)]
|
||||
public List<PartyInfoData> Parties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new List<PartyInfoData>(); // 서버->클라: 채널 내 파티 목록
|
||||
}
|
||||
|
||||
// UPDATE_CHANNEL_USER 유저 접속/나감
|
||||
|
||||
@@ -6,19 +6,17 @@ using Serilog;
|
||||
namespace ServerLib.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// 릴리즈 빌드 크래시 덤프 핸들러
|
||||
///
|
||||
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
||||
///
|
||||
/// 생성 파일 (crashes/ 폴더):
|
||||
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
|
||||
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
|
||||
/// crash_YYYY-MM-DD_HH-mm-ss.dmp ← 메모리 덤프 추가
|
||||
/// 릴리즈 빌드 크래시 덤프 핸들러
|
||||
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
||||
/// 생성 파일 (crashes/ 폴더):
|
||||
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
|
||||
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
|
||||
/// crash_YYYY-MM-DD_HH-mm-ss.dmp ← 메모리 덤프 추가
|
||||
/// </summary>
|
||||
public static class CrashDumpHandler
|
||||
{
|
||||
private const string CRASH_DIR = "crashes";
|
||||
private static int registered = 0;
|
||||
private static int registered;
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
@@ -29,7 +27,7 @@ public static class CrashDumpHandler
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
|
||||
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
|
||||
}
|
||||
@@ -38,31 +36,34 @@ public static class CrashDumpHandler
|
||||
|
||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// 예외 컨텍스트가 살아있는 지금 즉시 캡처
|
||||
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
|
||||
Exception? ex = e.ExceptionObject as Exception;
|
||||
bool isTerminating = e.IsTerminating;
|
||||
|
||||
string tag = $"[CrashDump] UnhandledException (IsTerminating={isTerminating})";
|
||||
WriteCrash(tag, ex);
|
||||
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
|
||||
WriteCrash(tag, ex, exceptionPointers);
|
||||
}
|
||||
|
||||
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
// Task 예외는 파이널라이저 스레드에서 통보되므로 포인터가 없을 수 있음
|
||||
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
|
||||
e.SetObserved(); // 프로세스 종료 방지
|
||||
|
||||
string tag = "[CrashDump] UnobservedTaskException";
|
||||
WriteCrash(tag, e.Exception);
|
||||
WriteCrash(tag, e.Exception, exceptionPointers);
|
||||
}
|
||||
|
||||
// ─── 핵심 처리 ───────────────────────────────────────────────────────
|
||||
|
||||
private static void WriteCrash(string tag, Exception? ex)
|
||||
private static void WriteCrash(string tag, Exception? ex, IntPtr exceptionPointers)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(CRASH_DIR);
|
||||
|
||||
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
string basePath = Path.Combine(CRASH_DIR, $"crash_{timestamp}");
|
||||
string basePath = Path.Combine(CRASH_DIR, $"crash_{timestamp}");
|
||||
|
||||
// 1. 크래시 로그 작성
|
||||
string logPath = $"{basePath}.log";
|
||||
@@ -73,7 +74,7 @@ public static class CrashDumpHandler
|
||||
#if !DEBUG
|
||||
// 2. 메모리 덤프 작성 (Release only)
|
||||
string dmpPath = $"{basePath}.dmp";
|
||||
WriteDumpFile(dmpPath);
|
||||
WriteDumpFile(dmpPath, exceptionPointers);
|
||||
Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
|
||||
#endif
|
||||
}
|
||||
@@ -118,7 +119,7 @@ public static class CrashDumpHandler
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendException(sb, ex, depth: 0);
|
||||
AppendException(sb, ex, 0);
|
||||
}
|
||||
|
||||
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
|
||||
@@ -126,7 +127,7 @@ public static class CrashDumpHandler
|
||||
|
||||
private static void AppendException(StringBuilder sb, Exception ex, int depth)
|
||||
{
|
||||
string indent = new string(' ', depth * 2);
|
||||
string indent = new(' ', depth * 2);
|
||||
sb.AppendLine($"{indent} Type : {ex.GetType().FullName}");
|
||||
sb.AppendLine($"{indent} Message : {ex.Message}");
|
||||
sb.AppendLine($"{indent} Source : {ex.Source}");
|
||||
@@ -159,16 +160,27 @@ public static class CrashDumpHandler
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
// Windows MiniDumpWriteDump P/Invoke
|
||||
// ─── Windows P/Invoke ────────────────────────────────────────────────
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct MinidumpExceptionInformation
|
||||
{
|
||||
public uint ThreadId;
|
||||
public IntPtr ExceptionPointers;
|
||||
public int ClientPointers; // 0 = 서버 자신의 주소 공간
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("dbghelp.dll", SetLastError = true)]
|
||||
private static extern bool MiniDumpWriteDump(
|
||||
IntPtr hProcess, uint processId, IntPtr hFile,
|
||||
uint dumpType, IntPtr exceptionParam,
|
||||
IntPtr userStreamParam, IntPtr callbackParam);
|
||||
|
||||
private const uint MiniDumpWithFullMemory = 0x00000002;
|
||||
private const uint MINI_DUMP_WITH_FULL_MEMORY = 0x00000002;
|
||||
|
||||
private static void WriteDumpFile(string path)
|
||||
private static void WriteDumpFile(string path, IntPtr exceptionPointers)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
@@ -177,19 +189,43 @@ public static class CrashDumpHandler
|
||||
}
|
||||
|
||||
using Process process = Process.GetCurrentProcess();
|
||||
using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
using FileStream fs = new(path, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
bool success = MiniDumpWriteDump(
|
||||
process.Handle,
|
||||
(uint)process.Id,
|
||||
fs.SafeFileHandle.DangerousGetHandle(),
|
||||
MiniDumpWithFullMemory,
|
||||
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
if (!success)
|
||||
IntPtr exInfoPtr = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
|
||||
if (exceptionPointers != IntPtr.Zero)
|
||||
{
|
||||
MinidumpExceptionInformation exInfo = new()
|
||||
{
|
||||
ThreadId = GetCurrentThreadId(),
|
||||
ExceptionPointers = exceptionPointers,
|
||||
ClientPointers = 0
|
||||
};
|
||||
exInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(exInfo));
|
||||
Marshal.StructureToPtr(exInfo, exInfoPtr, false);
|
||||
}
|
||||
|
||||
bool success = MiniDumpWriteDump(
|
||||
process.Handle,
|
||||
(uint)process.Id,
|
||||
fs.SafeFileHandle.DangerousGetHandle(),
|
||||
MINI_DUMP_WITH_FULL_MEMORY,
|
||||
exInfoPtr, // 크래시 원인 스레드 & 예외 정보
|
||||
IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (exInfoPtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(exInfoPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user