diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index bc6d24b951..0ad91ff308 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -66,6 +66,8 @@ import { FeaturedService } from './FeaturedService.js';
 import { FanoutTimelineService } from './FanoutTimelineService.js';
 import { ChannelFollowingService } from './ChannelFollowingService.js';
 import { RegistryApiService } from './RegistryApiService.js';
+import { ReversiService } from './ReversiService.js';
+
 import { ChartLoggerService } from './chart/ChartLoggerService.js';
 import FederationChart from './chart/charts/federation.js';
 import NotesChart from './chart/charts/notes.js';
@@ -80,6 +82,7 @@ import PerUserFollowingChart from './chart/charts/per-user-following.js';
 import PerUserDriveChart from './chart/charts/per-user-drive.js';
 import ApRequestChart from './chart/charts/ap-request.js';
 import { ChartManagementService } from './chart/ChartManagementService.js';
+
 import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
 import { AntennaEntityService } from './entities/AntennaEntityService.js';
 import { AppEntityService } from './entities/AppEntityService.js';
@@ -112,6 +115,9 @@ import { UserListEntityService } from './entities/UserListEntityService.js';
 import { FlashEntityService } from './entities/FlashEntityService.js';
 import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
 import { RoleEntityService } from './entities/RoleEntityService.js';
+import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
+import { ReversiMatchingEntityService } from './entities/ReversiMatchingEntityService.js';
+
 import { ApAudienceService } from './activitypub/ApAudienceService.js';
 import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
 import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -199,6 +205,7 @@ const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', use
 const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
 const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
 const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
+const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
 
 const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
 const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -247,6 +254,8 @@ const $UserListEntityService: Provider = { provide: 'UserListEntityService', use
 const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
 const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
 const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
+const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
+const $ReversiMatchingEntityService: Provider = { provide: 'ReversiMatchingEntityService', useExisting: ReversiMatchingEntityService };
 
 const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
 const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -336,6 +345,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FanoutTimelineEndpointService,
 		ChannelFollowingService,
 		RegistryApiService,
+		ReversiService,
+
 		ChartLoggerService,
 		FederationChart,
 		NotesChart,
@@ -350,6 +361,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PerUserDriveChart,
 		ApRequestChart,
 		ChartManagementService,
+
 		AbuseUserReportEntityService,
 		AntennaEntityService,
 		AppEntityService,
@@ -382,6 +394,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FlashEntityService,
 		FlashLikeEntityService,
 		RoleEntityService,
+		ReversiGameEntityService,
+		ReversiMatchingEntityService,
+
 		ApAudienceService,
 		ApDbResolverService,
 		ApDeliverManagerService,
@@ -466,6 +481,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FanoutTimelineEndpointService,
 		$ChannelFollowingService,
 		$RegistryApiService,
+		$ReversiService,
+
 		$ChartLoggerService,
 		$FederationChart,
 		$NotesChart,
@@ -480,6 +497,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PerUserDriveChart,
 		$ApRequestChart,
 		$ChartManagementService,
+
 		$AbuseUserReportEntityService,
 		$AntennaEntityService,
 		$AppEntityService,
@@ -512,6 +530,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FlashEntityService,
 		$FlashLikeEntityService,
 		$RoleEntityService,
+		$ReversiGameEntityService,
+		$ReversiMatchingEntityService,
+
 		$ApAudienceService,
 		$ApDbResolverService,
 		$ApDeliverManagerService,
@@ -597,6 +618,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FanoutTimelineEndpointService,
 		ChannelFollowingService,
 		RegistryApiService,
+		ReversiService,
+
 		FederationChart,
 		NotesChart,
 		UsersChart,
@@ -610,6 +633,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PerUserDriveChart,
 		ApRequestChart,
 		ChartManagementService,
+
 		AbuseUserReportEntityService,
 		AntennaEntityService,
 		AppEntityService,
@@ -642,6 +666,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FlashEntityService,
 		FlashLikeEntityService,
 		RoleEntityService,
+		ReversiGameEntityService,
+		ReversiMatchingEntityService,
+
 		ApAudienceService,
 		ApDbResolverService,
 		ApDeliverManagerService,
@@ -726,6 +753,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FanoutTimelineEndpointService,
 		$ChannelFollowingService,
 		$RegistryApiService,
+		$ReversiService,
+
 		$FederationChart,
 		$NotesChart,
 		$UsersChart,
@@ -739,6 +768,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PerUserDriveChart,
 		$ApRequestChart,
 		$ChartManagementService,
+
 		$AbuseUserReportEntityService,
 		$AntennaEntityService,
 		$AppEntityService,
@@ -771,6 +801,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FlashEntityService,
 		$FlashLikeEntityService,
 		$RoleEntityService,
+		$ReversiGameEntityService,
+		$ReversiMatchingEntityService,
+
 		$ApAudienceService,
 		$ApDbResolverService,
 		$ApDeliverManagerService,
diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts
new file mode 100644
index 0000000000..64c0e25334
--- /dev/null
+++ b/packages/backend/src/core/ReversiService.ts
@@ -0,0 +1,126 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import { In } from 'typeorm';
+import { ModuleRef } from '@nestjs/core';
+import type {
+	MiReversiGame,
+	MiRole,
+	MiRoleAssignment,
+	RoleAssignmentsRepository,
+	RolesRepository,
+	UsersRepository,
+} from '@/models/_.js';
+import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
+import type { MiUser } from '@/models/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
+import { CacheService } from '@/core/CacheService.js';
+import type { RoleCondFormulaValue } from '@/models/Role.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { IdService } from '@/core/IdService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import type { Packed } from '@/misc/json-schema.js';
+import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import { NotificationService } from '@/core/NotificationService.js';
+import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
+
+@Injectable()
+export class ReversiService implements OnApplicationShutdown, OnModuleInit {
+	private notificationService: NotificationService;
+
+	constructor(
+		private moduleRef: ModuleRef,
+
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
+		@Inject(DI.redisForSub)
+		private redisForSub: Redis.Redis,
+
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.rolesRepository)
+		private rolesRepository: RolesRepository,
+
+		@Inject(DI.roleAssignmentsRepository)
+		private roleAssignmentsRepository: RoleAssignmentsRepository,
+
+		private metaService: MetaService,
+		private cacheService: CacheService,
+		private userEntityService: UserEntityService,
+		private globalEventService: GlobalEventService,
+		private idService: IdService,
+		private moderationLogService: ModerationLogService,
+		private fanoutTimelineService: FanoutTimelineService,
+	) {
+	}
+
+	async onModuleInit() {
+		this.notificationService = this.moduleRef.get(NotificationService.name);
+	}
+
+	@bindThis
+	public async getModerators(includeAdmins = true): Promise<MiUser[]> {
+		const ids = await this.getModeratorIds(includeAdmins);
+		const users = ids.length > 0 ? await this.usersRepository.findBy({
+			id: In(ids),
+		}) : [];
+		return users;
+	}
+
+	@bindThis
+	public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
+		const date = new Date();
+		const created = await this.rolesRepository.insert({
+			id: this.idService.gen(date.getTime()),
+			updatedAt: date,
+			lastUsedAt: date,
+			name: values.name,
+			description: values.description,
+			color: values.color,
+			iconUrl: values.iconUrl,
+			target: values.target,
+			condFormula: values.condFormula,
+			isPublic: values.isPublic,
+			isAdministrator: values.isAdministrator,
+			isModerator: values.isModerator,
+			isExplorable: values.isExplorable,
+			asBadge: values.asBadge,
+			canEditMembersByModerator: values.canEditMembersByModerator,
+			displayOrder: values.displayOrder,
+			policies: values.policies,
+		}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
+
+		this.globalEventService.publishInternalEvent('roleCreated', created);
+
+		if (moderator) {
+			this.moderationLogService.log(moderator, 'createRole', {
+				roleId: created.id,
+				role: created,
+			});
+		}
+
+		return created;
+	}
+
+	@bindThis
+	public dispose(): void {
+	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
+}
diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts
new file mode 100644
index 0000000000..0bf02990d7
--- /dev/null
+++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts
@@ -0,0 +1,81 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { ReversiGamesRepository } from '@/models/_.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/json-schema.js';
+import type { } from '@/models/Blocking.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiReversiGame } from '@/models/ReversiGame.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+import { UserEntityService } from './UserEntityService.js';
+
+@Injectable()
+export class ReversiGameEntityService {
+	constructor(
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private userEntityService: UserEntityService,
+		private idService: IdService,
+	) {
+	}
+
+	@bindThis
+	public async pack(
+		src: MiReversiGame['id'] | MiReversiGame,
+		me?: { id: MiUser['id'] } | null | undefined,
+		options?: {
+			detail?: boolean;
+			skipHide?: boolean;
+		},
+	): Promise<Packed<'ReversiGame'>> {
+		const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+
+		return await awaitAll({
+			id: game.id,
+			createdAt: this.idService.parse(game.id).date.toISOString(),
+			startedAt: game.startedAt && game.startedAt.toISOString(),
+			isStarted: game.isStarted,
+			isEnded: game.isEnded,
+			form1: game.form1,
+			form2: game.form2,
+			user1Accepted: game.user1Accepted,
+			user2Accepted: game.user2Accepted,
+			user1Id: game.user1Id,
+			user2Id: game.user2Id,
+			user1: this.userEntityService.pack(game.user1Id, me),
+			user2: this.userEntityService.pack(game.user2Id, me),
+			winnerId: game.winnerId,
+			winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
+			surrendered: game.surrendered,
+			black: game.black,
+			bw: game.bw,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			...(options?.detail ? {
+				logs: game.logs.map(log => ({
+					at: log.at.toISOString(),
+					color: log.color,
+					pos: log.pos,
+				})),
+				map: game.map,
+			} : {}),
+		});
+	}
+
+	@bindThis
+	public packMany(
+		xs: MiReversiGame[],
+		me?: { id: MiUser['id'] } | null | undefined,
+	) {
+		return Promise.all(xs.map(x => this.pack(x, me)));
+	}
+}
+
diff --git a/packages/backend/src/core/entities/ReversiMatchingEntityService.ts b/packages/backend/src/core/entities/ReversiMatchingEntityService.ts
new file mode 100644
index 0000000000..1b39679ec1
--- /dev/null
+++ b/packages/backend/src/core/entities/ReversiMatchingEntityService.ts
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { MiReversiMatching, ReversiMatchingsRepository } from '@/models/_.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/json-schema.js';
+import type { } from '@/models/Blocking.js';
+import type { MiUser } from '@/models/User.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+import { UserEntityService } from './UserEntityService.js';
+
+@Injectable()
+export class ReversiMatchingEntityService {
+	constructor(
+		@Inject(DI.reversiMatchingsRepository)
+		private reversiMatchingsRepository: ReversiMatchingsRepository,
+
+		private userEntityService: UserEntityService,
+		private idService: IdService,
+	) {
+	}
+
+	@bindThis
+	public async pack(
+		src: MiReversiMatching['id'] | MiReversiMatching,
+		me?: { id: MiUser['id'] } | null | undefined,
+	): Promise<Packed<'ReversiMatching'>> {
+		const matching = typeof src === 'object' ? src : await this.reversiMatchingsRepository.findOneByOrFail({ id: src });
+
+		return await awaitAll({
+			id: matching.id,
+			createdAt: this.idService.parse(matching.id).date.toISOString(),
+			parentId: matching.parentId,
+			parent: this.userEntityService.pack(matching.parentId, me, {
+				detail: true,
+			}),
+			childId: matching.childId,
+			child: this.userEntityService.pack(matching.childId, me, {
+				detail: true,
+			}),
+		});
+	}
+
+	@bindThis
+	public packMany(
+		xs: MiReversiMatching[],
+		me?: { id: MiUser['id'] } | null | undefined,
+	) {
+		return Promise.all(xs.map(x => this.pack(x, me)));
+	}
+}
+
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 176978d35f..cabee95dad 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -39,6 +39,8 @@ import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
 import { packedSigninSchema } from '@/models/json-schema/signin.js';
 import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
 import { packedAdSchema } from '@/models/json-schema/ad.js';
+import { packedReversiGameSchema } from '@/models/json-schema/reversi-game.js';
+import { packedReversiMatchingSchema } from '@/models/json-schema/reversi-matching.js';
 
 export const refs = {
 	UserLite: packedUserLiteSchema,
@@ -78,6 +80,8 @@ export const refs = {
 	Signin: packedSigninSchema,
 	RoleLite: packedRoleLiteSchema,
 	Role: packedRoleSchema,
+	ReversiGame: packedReversiGameSchema,
+	ReversiMatching: packedReversiMatchingSchema,
 };
 
 export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts
new file mode 100644
index 0000000000..fc7cdb063d
--- /dev/null
+++ b/packages/backend/src/models/json-schema/reversi-game.ts
@@ -0,0 +1,135 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedReversiGameSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		createdAt: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'date-time',
+		},
+		startedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		isStarted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isEnded: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		form1: {
+			type: 'any',
+			optional: false, nullable: true,
+		},
+		form2: {
+			type: 'any',
+			optional: false, nullable: true,
+		},
+		user1Accepted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user2Accepted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user1Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user2Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user1: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+		user2: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+		winnerId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		winner: {
+			type: 'object',
+			optional: false, nullable: true,
+			ref: 'User',
+		},
+		surrendered: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		black: {
+			type: 'number',
+			optional: false, nullable: true,
+		},
+		bw: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		isLlotheo: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canPutEverywhere: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		loopedBoard: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		logs: {
+			type: 'array',
+			optional: true, nullable: false,
+			items: {
+				type: 'object',
+				optional: true, nullable: false,
+				properties: {
+					at: {
+						type: 'string',
+						optional: false, nullable: false,
+						format: 'date-time',
+					},
+					color: {
+						type: 'boolean',
+						optional: false, nullable: false,
+					},
+					pos: {
+						type: 'number',
+						optional: false, nullable: false,
+					},
+				},
+			},
+		},
+		map: {
+			type: 'array',
+			optional: true, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+	},
+} as const;
diff --git a/packages/backend/src/models/json-schema/reversi-matching.ts b/packages/backend/src/models/json-schema/reversi-matching.ts
new file mode 100644
index 0000000000..a98b0a1644
--- /dev/null
+++ b/packages/backend/src/models/json-schema/reversi-matching.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedReversiMatchingSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		createdAt: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'date-time',
+		},
+		parentId: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		parent: {
+			type: 'object',
+			optional: false, nullable: true,
+			ref: 'User',
+		},
+		childId: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		child: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+	},
+} as const;